commit 0c7793fe1fdd5271ef37f52c978b56b96eac6725 Author: Histausse Date: Tue Oct 11 23:53:12 2022 +0200 first commit: rpi3 blinking led diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9e287c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +kernel8.img +__pycache__/ +.doit.db diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3f0142d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "judas" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..df7d1ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "judas" +version = "0.1.0" +authors = ["Jean-Marie Mineau "] +edition = "2021" +description = "A OS for raspberry pi, for fun" +build = "build.rs" + +[features] +default = [] +target_rpi3 = [] +target_rpi4 = [] + +[profile.release] +# LLVM link time optimizations, obtimize the code, longer linking time +lto = true + +[[bin]] +name = "kernel" +path = "src/main.rs" diff --git a/README.md b/README.md new file mode 100644 index 0000000..71a805d --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Juda + +Juda is a toy raspberry pi bear metal project that maybe will become some sort of OS (or not). + +It is heavily inspired by [rust-raspberrypi-OS-tutorials](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials), although I'm trying to make it my own. diff --git a/bootloader_files/rpi3/bootcode.bin b/bootloader_files/rpi3/bootcode.bin new file mode 100644 index 0000000..c701fe4 Binary files /dev/null and b/bootloader_files/rpi3/bootcode.bin differ diff --git a/bootloader_files/rpi3/config.txt b/bootloader_files/rpi3/config.txt new file mode 100644 index 0000000..76849bf --- /dev/null +++ b/bootloader_files/rpi3/config.txt @@ -0,0 +1,3 @@ +arm_64bit=1 +init_uart_clock=48000000 +enable_jtag_gpio=1 diff --git a/bootloader_files/rpi3/fixup.dat b/bootloader_files/rpi3/fixup.dat new file mode 100644 index 0000000..1ba3e55 Binary files /dev/null and b/bootloader_files/rpi3/fixup.dat differ diff --git a/bootloader_files/rpi3/start.elf b/bootloader_files/rpi3/start.elf new file mode 100644 index 0000000..2a8a570 Binary files /dev/null and b/bootloader_files/rpi3/start.elf differ diff --git a/bootloader_files/rpi4/bcm2711-rpi-4-b.dtb b/bootloader_files/rpi4/bcm2711-rpi-4-b.dtb new file mode 100644 index 0000000..090a7d4 Binary files /dev/null and b/bootloader_files/rpi4/bcm2711-rpi-4-b.dtb differ diff --git a/bootloader_files/rpi4/config.txt b/bootloader_files/rpi4/config.txt new file mode 100644 index 0000000..76849bf --- /dev/null +++ b/bootloader_files/rpi4/config.txt @@ -0,0 +1,3 @@ +arm_64bit=1 +init_uart_clock=48000000 +enable_jtag_gpio=1 diff --git a/bootloader_files/rpi4/fixup4.dat b/bootloader_files/rpi4/fixup4.dat new file mode 100644 index 0000000..c91c2f0 Binary files /dev/null and b/bootloader_files/rpi4/fixup4.dat differ diff --git a/bootloader_files/rpi4/start4.elf b/bootloader_files/rpi4/start4.elf new file mode 100644 index 0000000..0c111d0 Binary files /dev/null and b/bootloader_files/rpi4/start4.elf differ diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..97f12e1 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + let linker_script = "linker.ld"; + println!("cargo:rerun-if-changed={linker_script}"); +} diff --git a/doc/VideoCoreIV-AG100-R.pdf b/doc/VideoCoreIV-AG100-R.pdf new file mode 100644 index 0000000..7cf90f7 Binary files /dev/null and b/doc/VideoCoreIV-AG100-R.pdf differ diff --git a/doc/as.pdf b/doc/as.pdf new file mode 100644 index 0000000..13b4c12 Binary files /dev/null and b/doc/as.pdf differ diff --git a/doc/bcm2711-peripherals-raspy4.pdf b/doc/bcm2711-peripherals-raspy4.pdf new file mode 100644 index 0000000..011a96d Binary files /dev/null and b/doc/bcm2711-peripherals-raspy4.pdf differ diff --git a/doc/bcm2837-peripherals-raspy3.pdf b/doc/bcm2837-peripherals-raspy3.pdf new file mode 100644 index 0000000..5e8d4d7 Binary files /dev/null and b/doc/bcm2837-peripherals-raspy3.pdf differ diff --git a/doc/ld.pdf b/doc/ld.pdf new file mode 100644 index 0000000..9df004a Binary files /dev/null and b/doc/ld.pdf differ diff --git a/dodo.py b/dodo.py new file mode 100644 index 0000000..25c9f34 --- /dev/null +++ b/dodo.py @@ -0,0 +1,135 @@ +""" The doit file. Because makefiles are old and hard to read. + Can't promise this will be more readable, but at least it wont be old :) +""" + +import os +from enum import Enum +from types import SimpleNamespace +from typing import Iterator +from pathlib import Path + +from doit.tools import Interactive + +class BoardTarget(Enum): + RPI3B = 1 + RPI4 = 2 + +## Target +BOARD_TARGET = BoardTarget.RPI3B + +## Common constants +consts = SimpleNamespace() +consts.WORK_DIR = os.getcwd() +# Rust equivalent to -Werror +consts.RUSTC_FLAGS = "-D warnings " +# Force documentation +consts.RUSTC_FLAGS += "-D missing_docs" +consts.CARGO_COMPILE_FLAGS = "--release" + +if BOARD_TARGET == BoardTarget.RPI3B: + consts.TARGET = "aarch64-unknown-none-softfloat" + consts.KERNEL_NAME = "kernel8.img" + consts.QEMU_CMD = "qemu-system-aarch64" + consts.QEMU_MACHINE = "raspi3b" + consts.QEMU_ARGS = "-serial stdio -display none" + consts.OBJDUMP_CMD = "aarch64-none-elf-objdump" + consts.OBJDUMP_ARGS = "-D" + consts.NM_CMD = "aarch64-none-elf-nm" + consts.READELF_CMD = "aarch64-none-elf-readelf" + consts.LD_SCRIPT_PATH = f"{consts.WORK_DIR}/linker.ld" + consts.RUSTC_FLAGS = f"-C target-cpu=cortex-a53 -C link-arg=--script={consts.LD_SCRIPT_PATH} {consts.RUSTC_FLAGS}" + consts.CARGO_COMPILE_FLAGS = f"--features target_rpi3 {consts.CARGO_COMPILE_FLAGS}" +elif BOARD_TARGET == BoardTarget.RPI4: + consts.TARGET = "aarch64-unknown-none-softfloat" + consts.KERNEL_NAME = "kernel8.img" + consts.QEMU_CMD = "qemu-system-aarch64" + consts.QEMU_MACHINE = None + consts.QEMU_ARGS = "-serial stdio -display none" + consts.OBJDUMP_CMD = "aarch64-none-elf-objdump" + consts.OBJDUMP_ARGS = "-D" + consts.NM_CMD = "aarch64-none-elf-nm" + consts.READELF_CMD = "aarch64-none-elf-readelf" + consts.LD_SCRIPT_PATH = f"{consts.WORK_DIR}/linker.ld" + consts.RUSTC_FLAGS = f"-C target-cpu=cortex-a72 -C link-arg=--script={consts.LD_SCRIPT_PATH} {consts.RUSTC_FLAGS}" + consts.CARGO_COMPILE_FLAGS = f"--features target_rpi4 {consts.CARGO_COMPILE_FLAGS}" + +DOIT_CONFIG = { + 'default_tasks': ['build'], +} + +def get_rust_files() -> Iterator[str]: + """ Get a list of the rust source files. + """ + return map(lambda p: str(p.resolve()), Path('./src').glob('**/*.rs')) + +def get_asm_files() -> Iterator[str]: + """ Get a list of the assembly source files. + """ + return map(lambda p: str(p.resolve()), Path('./src').glob('**/*.s')) + +def get_build_dep() -> list[str]: + """ Get a list of the files impacting the binary. + """ + # TODO: add dodo.py to the list? + return [ + consts.LD_SCRIPT_PATH, + *get_rust_files(), + *get_asm_files(), + ] + +def task_install_dep(): + """ Install some required tools. + """ + return { + 'actions': [ + 'cargo install cargo-binutils', + f'rustup target add {consts.TARGET}', + 'rustup component add llvm-tools-preview', + ], + } + +def task_compile(): + """ Compile the sources. + """ + return { + 'file_dep': get_build_dep(), + 'targets': [f'{consts.WORK_DIR}/target/{consts.TARGET}/release/kernel'], + 'actions': [f'RUSTFLAGS="{consts.RUSTC_FLAGS}" cargo rustc --target={consts.TARGET} {consts.CARGO_COMPILE_FLAGS}'], + 'clean': [f'cargo clean'] + } + +def task_build(): + """ Strip the binary from ELF format to raw binary. + """ + return { + 'file_dep': [f'{consts.WORK_DIR}/target/{consts.TARGET}/release/kernel'], + 'targets': [f'{consts.WORK_DIR}/consts.KERNEL_NAME'], + 'actions': [ + f'rust-objcopy --strip-all -O binary {consts.WORK_DIR}/target/{consts.TARGET}/release/kernel {consts.WORK_DIR}/{consts.KERNEL_NAME}', + ], + 'clean': True, + } + +def task_qemu(): + """ Run the kernel in qemu. + """ + if consts.QEMU_MACHINE is None: + raise Exception("qemu does not support emulation for this board yet") + r = { + 'file_dep': [f'{consts.WORK_DIR}/{consts.KERNEL_NAME}'], + 'uptodate': [False], # Always run the cmd + 'actions': [Interactive(f'{consts.QEMU_CMD} -M {consts.QEMU_MACHINE} {consts.QEMU_ARGS} -kernel {consts.KERNEL_NAME}')], + } + print(r) + return r + +def task_objdump(): + """ Inspect the ELF binary file. + """ + r = { + 'file_dep': [f'{consts.WORK_DIR}/target/{consts.TARGET}/release/kernel'], + 'uptodate': [False], # Always run the cmd + 'actions': [Interactive(f'{consts.OBJDUMP_CMD} {consts.OBJDUMP_ARGS} {consts.WORK_DIR}/target/{consts.TARGET}/release/kernel')] + } + print(r) + return r diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..de218ae --- /dev/null +++ b/linker.ld @@ -0,0 +1,49 @@ + +MEMORY { + sdram_for_arm_1 (rwx) : ORIGIN = 0x00000000, LENGTH = 256M /* Lenght should be 1G - gpu_mem */ +} + +__kernel_load_addr = 0x80000; /* 0x80000 because AArch64 (0x8000 is for AArch32) */ + +ENTRY(__kernel_load_addr) + +PHDRS +{ + segment_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + .core_stack (NOLOAD) : + { + __core_stack_start = .; + . += __kernel_load_addr; /* TODO: Don't like this, it only work because it starts at 0x0 */ + __core_stack_end = .; + + } > sdram_for_arm_1 : segment_core_stack + .text : + { + . = __kernel_load_addr; + KEEP(*(.text._start)) + *(.text._start_arg) /* Static read by _start() */ + *(.text._start_rust) /* Enty point in rust */ + *(.text*) /* The rest of the code */ + } > sdram_for_arm_1 :segment_code + .rodata : ALIGN(8) { *(.rodata) } > sdram_for_arm_1 :segment_code + /* TODO: not sure having the RODATA in an executable + segment is a good practice? */ + .data : { *(.data) } > sdram_for_arm_1 :segment_data + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end = .; + } > sdram_for_arm_1 :segment_data + + /* GOT? */ +} + +ENTRY(_start) diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a500664 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +targets = ["aarch64-unknown-none-softfloat"] diff --git a/src/boot.s b/src/boot.s new file mode 100644 index 0000000..b005657 --- /dev/null +++ b/src/boot.s @@ -0,0 +1,35 @@ +.section .text._start // Make sure the linker put this at the begining of the + // .text section thanks to the `KEEP(*(.text._start))` + +_start: + mrs x1, MPIDR_EL1 + and x1, x1, #3 + mov x2, #0 + cmp x1, x2 + b.ne ._exit // if core id != 0, wait forever + + // Set bss to 0 + ldr x0,=__bss_start + ldr x1,=__bss_end +._bss_loop: + cmp x0, x1 + b.eq ._set_sp + stp xzr, xzr, [x0], #16 + b ._bss_loop + + // Set the stack pointer +._set_sp: + ldr x1,=__core_stack_end // the stack goest down + mov sp, x1 + + // go to rust + b _start_rust + +._exit: + wfe // 'Wait For Event', put the cpu in low power mode + b ._exit + +// define _start as a function +.size _start, . - _start +.type _start, function +.global _start diff --git a/src/bsp/mod.rs b/src/bsp/mod.rs new file mode 100644 index 0000000..76fedd2 --- /dev/null +++ b/src/bsp/mod.rs @@ -0,0 +1,10 @@ +//! Board Support Package: module containing implementation +//! specific to a board. + +pub mod qemu; + +#[cfg(feature = "target_rpi3")] +pub mod rpi3; + +#[cfg(feature = "target_rpi4")] +pub mod rpi4; diff --git a/src/bsp/qemu/console.rs b/src/bsp/qemu/console.rs new file mode 100644 index 0000000..e51a30f --- /dev/null +++ b/src/bsp/qemu/console.rs @@ -0,0 +1,85 @@ +//! Implement the Qemu magic UART. + +use core::fmt; + +use crate::traits::console::{Console, Write}; + +use crate::traits::synchronization::{DummyMutex, Mutex}; + +/// The address for the magic qemu output +const QEMU_MAGIC_OUTPUT_ADDR: *mut u8 = 0x3F20_1000 as *mut u8; + +/// The unique qemu output allowing access to the qemu output. +static QEMU_OUTPUT: QemuOutput = QemuOutput::new(); + +/// A structure allowing access to the qemu magic output. +struct QemuOutput { + inner: DummyMutex, +} + +/// Inner Qemu output. +struct QemuOutputInner; + +impl QemuOutputInner { + /// Constructor for [`QemuOutputInner`]. + const fn new() -> Self { + Self {} + } + + /// Write a character to the output. + fn write_char(&mut self, c: char) { + unsafe { + core::ptr::write_volatile(QEMU_MAGIC_OUTPUT_ADDR, c as u8); + } + } +} + +impl QemuOutput { + /// Constructor for [`QemuOutput`]. + pub const fn new() -> Self { + Self { + inner: DummyMutex::new(QemuOutputInner::new()), + } + } +} + +/// Allow to use QemuOutputInner for print! and formating macros. +/// `write_str` needs `&mut self` (mutable ref), so we can implement +/// it only on the inner type, the `QemuOutput` need to be manipulable +/// using unmutable references (`&self`), so we will use a custom +/// interface for it. +impl fmt::Write for QemuOutputInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + // \n -> \r\n + if c == '\n' { + self.write_char('\r'); + } + self.write_char(c); + } + Ok(()) + } +} + +impl Write for QemuOutput { + fn write_char(&self, c: char) { + self.inner.lock(|q_out| q_out.write_char(c)) + } + + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result { + self.inner.lock(|q_out| fmt::Write::write_fmt(q_out, args)) + } + + /// Empty function, the qemu uart has no buffering afaik + fn flush(&self) { + // self.inner.lock(|q_out| q_out.flush()) + } +} + +impl Console for QemuOutput {} + +// TODO: move? +/// Return a reference to the Qemu Output. +pub fn console() -> &'static dyn Console { + &QEMU_OUTPUT +} diff --git a/src/bsp/qemu/mod.rs b/src/bsp/qemu/mod.rs new file mode 100644 index 0000000..452ae22 --- /dev/null +++ b/src/bsp/qemu/mod.rs @@ -0,0 +1,7 @@ +//! Implement the features specific to code running inside qemu. +//! +//! # TODO +//! +//! Move this somewhere else? + +pub mod console; diff --git a/src/bsp/rpi3/gpio.rs b/src/bsp/rpi3/gpio.rs new file mode 100644 index 0000000..100b5bc --- /dev/null +++ b/src/bsp/rpi3/gpio.rs @@ -0,0 +1,101 @@ +//! GPIO module. + +use super::memory_map; +use crate::println; + + +/// The number of pin for the raspberry 3. +const NUMBER_PIN: usize = 54; + +/// Value to select the INPUT function of a PIN. +const FSEL_INPUT: u32 = 0b000; +/// Value to select the OUTPUT function of a PIN. +const FSEL_OUTPUT: u32 = 0b001; +/// Value to select the Alternate function 0 of a PIN. +const FSEL_ALTF0: u32 = 0b100; +/// Value to select the Alternate function 1 of a PIN. +const FSEL_ALTF1: u32 = 0b101; +/// Value to select the Alternate function 2 of a PIN. +const FSEL_ALTF2: u32 = 0b110; +/// Value to select the Alternate function 3 of a PIN. +const FSEL_ALTF3: u32 = 0b111; +/// Value to select the Alternate function 4 of a PIN. +const FSEL_ALTF4: u32 = 0b011; +/// Value to select the Alternate function 5 of a PIN. +const FSEL_ALTF5: u32 = 0b010; + +/// The possible PIN functions. +pub enum PinFunction { + Input, + Output, + AltFunc0, + AltFunc1, + AltFunc2, + AltFunc3, + AltFunc4, + AltFunc5, +} + +/// The possible PIN state when in Output mode. +pub enum PinOutputState { + High, + Low, +} + +impl From for u32 { + fn from(function: PinFunction) -> u32 { + match function { + PinFunction::Input => FSEL_INPUT, + PinFunction::Output => FSEL_OUTPUT, + PinFunction::AltFunc0 => FSEL_ALTF0, + PinFunction::AltFunc1 => FSEL_ALTF1, + PinFunction::AltFunc2 => FSEL_ALTF2, + PinFunction::AltFunc3 => FSEL_ALTF3, + PinFunction::AltFunc4 => FSEL_ALTF4, + PinFunction::AltFunc5 => FSEL_ALTF5, + } + } +} + +// TODO: remove Result and use an enum instead of usize +/// Set the function of a Pin. +/// +/// # Result +/// +/// Return an error if the PIN does not exist. +pub fn set_pin_fonction(n: usize, function: PinFunction) -> Result<(), &'static str> { + if n >= NUMBER_PIN { + return Err("The pin does not exist."); + } + let field = memory_map::get_fsel(n); + // TODO: SYNC + let mut val = unsafe { core::ptr::read_volatile(field.get_address() as *mut u32) }; + val &= !field.get_mask(); + val |= Into::::into(function) << field.get_offset(); + let address = field.get_address(); + println!("Set {val:b} at 0x{address:x}"); + unsafe { core::ptr::write_volatile(address as *mut u32, val); } + Ok(()) +} + +// TODO: remove Result and use an enum instead of usize +/// Set the state of an output Pin. +/// +/// # Result +/// +/// Return an error if the PIN does not exist. +pub fn set_pin_output_state(n: usize, state: PinOutputState) -> Result<(), &'static str> { + if n >= NUMBER_PIN { + return Err("The pin does not exist."); + } + let field = match state { + PinOutputState::High => memory_map::get_set(n), + PinOutputState::Low => memory_map::get_clr(n), + }; + let val = field.get_mask(); + let address = field.get_address(); + println!("Set {val:b} at 0x{address:x}"); + // 0 has no effect on this field: no nead to read-modify-write + unsafe { core::ptr::write_volatile(address as *mut u32, val); } + Ok(()) +} diff --git a/src/bsp/rpi3/memory_map.rs b/src/bsp/rpi3/memory_map.rs new file mode 100644 index 0000000..f600600 --- /dev/null +++ b/src/bsp/rpi3/memory_map.rs @@ -0,0 +1,220 @@ +//! The memory map of the board. + +use crate::utils::field::Field; + +pub const GPIO_OFFSET: usize = 0x0020_0000; +pub const UART0_OFFSET: usize = 0x0020_1000; + +pub const START_PHYSICAL_ADDRESS: usize = 0x3F00_0000; + +pub const UART0_START: usize = START_PHYSICAL_ADDRESS + UART0_OFFSET; + +// ** GPIO addresses ** +/// Beginning of the GPIO adresses +pub const GPIO_START: usize = START_PHYSICAL_ADDRESS + GPIO_OFFSET; + +// Function Select +/// GPIO Function Select 0, R/W register. +/// bits 29-27: FSEL9 +/// bits 26-24: FSEL8 +/// bits 23-21: FSEL7 +/// bits 20-18: FSEL6 +/// bits 17-15: FSEL5 +/// bits 14-12: FSEL4 +/// bits 11-9: FSEL3 +/// bits 8-6: FSEL2 +/// bits 5-3: FSEL1 +/// bits 2-0: FSEL0 +pub const GPFSEL0: usize = GPIO_START + 0x00; +/// GPIO Function Select 1, R/W register. +/// bits 29-27: FSEL19 +/// bits 26-24: FSEL18 +/// bits 23-21: FSEL17 +/// bits 20-18: FSEL16 +/// bits 17-15: FSEL15 +/// bits 14-12: FSEL14 +/// bits 11-9: FSEL13 +/// bits 8-6: FSEL12 +/// bits 5-3: FSEL11 +/// bits 2-0: FSEL10 +pub const GPFSEL1:usize = GPIO_START + 0x04; +/// GPIO Function Select 2, R/W register. +/// bits 29-27: FSEL29 +/// bits 26-24: FSEL28 +/// bits 23-21: FSEL27 +/// bits 20-18: FSEL26 +/// bits 17-15: FSEL25 +/// bits 14-12: FSEL24 +/// bits 11-9: FSEL23 +/// bits 8-6: FSEL22 +/// bits 5-3: FSEL21 +/// bits 2-0: FSEL20 +pub const GPFSEL2:usize = GPIO_START + 0x08; +/// GPIO Function Select 3, R/W register. +/// bits 29-27: FSEL39 +/// bits 26-24: FSEL38 +/// bits 23-21: FSEL37 +/// bits 20-18: FSEL36 +/// bits 17-15: FSEL35 +/// bits 14-12: FSEL34 +/// bits 11-9: FSEL33 +/// bits 8-6: FSEL32 +/// bits 5-3: FSEL31 +/// bits 2-0: FSEL30 +pub const GPFSEL3:usize = GPIO_START + 0x0C; +/// GPIO Function Select 4, R/W register. +/// bits 29-27: FSEL49 +/// bits 26-24: FSEL48 +/// bits 23-21: FSEL47 +/// bits 20-18: FSEL46 +/// bits 17-15: FSEL45 +/// bits 14-12: FSEL44 +/// bits 11-9: FSEL43 +/// bits 8-6: FSEL42 +/// bits 5-3: FSEL41 +/// bits 2-0: FSEL40 +pub const GPFSEL4:usize = GPIO_START + 0x10; +/// GPIO Function Select 5, R/W register. +/// bits 11-9: FSEL53 +/// bits 8-6: FSEL52 +/// bits 5-3: FSEL51 +/// bits 2-0: FSEL50 +pub const GPFSEL5:usize = GPIO_START + 0x14; + +/// Return the field FSELn. +/// +/// # Panic +/// +/// Panic if the pin `n` does not exist. +pub const fn get_fsel(n: usize) -> Field { + if n > 53 { + panic!("The PIN does not exist"); + } + let address = [GPFSEL0, GPFSEL1, GPFSEL2, GPFSEL3, GPFSEL4, GPFSEL5][n/10]; + let offset = 3*(n%10); + let size = 3; + Field::new(address, offset, size) +} + +// Pin Output Set +/// Pin Output Set 0, W register. +/// bit 31: SET31 +/// ... +/// bit 0: SET0 +pub const GPSET0:usize = GPIO_START + 0x1C; +/// Pin Output Set 1, W register. +/// bit 21: SET53 +/// ... +/// bit 0: SET32 +pub const GPSET1:usize = GPIO_START + 0x20; + +/// Return the field SETn. +/// +/// # Panic +/// +/// Panic if the pin `n` does not exist. +pub const fn get_set(n: usize) -> Field { + if n > 53 { + panic!("The PIN does not exist"); + } + let (address, offset) = if n < 32 { + (GPSET0, n) + } else { + (GPSET1, n-32) + }; + let size = 1; + Field::new(address, offset, size) +} + +// Pin Output Clear +/// Pin Output Clear 0, W register. +/// bit 31: CLR31 +/// ... +/// bit 0: CLR0 +pub const GPCLR0:usize = GPIO_START + 0x28; +/// Pin Output Clear 1, W register. +/// bit 21: CLR53 +/// ... +/// bit 0: CLR32 +pub const GPCLR1:usize = GPIO_START + 0x2C; + +/// Return the field CLRn. +/// +/// # Panic +/// +/// Panic if the pin `n` does not exist. +pub const fn get_clr(n: usize) -> Field { + if n > 53 { + panic!("The PIN does not exist"); + } + let (address, offset) = if n < 32 { + (GPCLR0, n) + } else { + (GPCLR1, n-32) + }; + let size = 1; + Field::new(address, offset, size) +} + +// Pin Level +/// Pin Level 0, R register. +pub const GPLEV0:usize = GPIO_START + 0x34; +/// Pin Level 1, R register. +pub const GPLEV1:usize = GPIO_START + 0x38; + +// Pin Event Detect Status +/// Pin Event Detect Status 0, R/W register. +pub const GPEDS0:usize = GPIO_START + 0x40; +/// Pin Event Detect Status 1, R/W register. +pub const GPEDS1:usize = GPIO_START + 0x44; + +// Pin Rising Edge Detect Enable +/// Pin Rising Edge Detect Enable 0, R/W register. +pub const GPREN0:usize = GPIO_START + 0x4C; +/// Pin Rising Edge Detect Enable 1, R/W register. +pub const GPREN1:usize = GPIO_START + 0x50; + +// Pin Falling Edge Detect Enable +/// Pin Falling Edge Detect Enable 0, R/W register. +pub const GPFEN0:usize = GPIO_START + 0x58; +/// Pin Falling Edge Detect Enable 1, R/W register. +pub const GPFEN1:usize = GPIO_START + 0x5C; + +// Pin High Detect Enable +/// Pin High Detect Enable 0, R/W register. +pub const GPHEN0:usize = GPIO_START + 0x64; +/// Pin High Detect Enable 1, R/W register. +pub const GPHEN1:usize = GPIO_START + 0x68; + +// Pin Low Detect Enable +/// Pin Low Detect Enable 0, R/W register. +pub const GPLEN0:usize = GPIO_START + 0x70; +/// Pin Low Detect Enable 1, R/W register. +pub const GPLEN1:usize = GPIO_START + 0x74; + +// Pin Async, Rising Edge Detect +/// Pin Async, Rising Edge Detect 0, R/W register. +pub const GPAREN0:usize = GPIO_START + 0x7C; +/// Pin Async, Rising Edge Detect 1, R/W register. +pub const GPAREN1:usize = GPIO_START + 0x80; + +// Pin Async, Falling Edge Detect +/// Pin Async, Falling Edge Detect 0, R/W register. +pub const GPAFEN0:usize = GPIO_START + 0x88; +/// Pin Async, Falling Edge Detect1, R/W register. +pub const GPAFEN1:usize = GPIO_START + 0x8C; + +// Pin Pull-up/down Enable, R/W +/// Pin Pull-up/down Enable, R/W register. +pub const GPPUD:usize = GPIO_START + 0x94; + +// Pin Pull-up/down enable clock, R/W +/// Pin Pull-up/down enable clock 0, R/W register. +pub const GPPUDCLK0:usize = GPIO_START + 0x98; +/// Pin Pull-up/down enable clock 1, R/W register. +pub const GPPUDCLK1:usize = GPIO_START + 0x9C; + +// Test ?, R/W, 4 bits +/// Test register? only 4 bits long. +pub const GPIO_TEST:usize = GPIO_START + 0xB0; +// ** GPIO addresses ** diff --git a/src/bsp/rpi3/mod.rs b/src/bsp/rpi3/mod.rs new file mode 100644 index 0000000..493e0ac --- /dev/null +++ b/src/bsp/rpi3/mod.rs @@ -0,0 +1,6 @@ +//! Module specific to the raspberry pi 3 implementations. + +#[allow(dead_code)] +pub(self) mod memory_map; + +pub mod gpio; diff --git a/src/bsp/rpi4/memory_map.rs b/src/bsp/rpi4/memory_map.rs new file mode 100644 index 0000000..d4fe3fc --- /dev/null +++ b/src/bsp/rpi4/memory_map.rs @@ -0,0 +1,15 @@ +//! The memory map of the board. + +// use crate::utils::field::Field; + +pub const GPIO_OFFSET: usize = 0x0020_0000; +pub const UART0_OFFSET: usize = 0x0020_1000; + +pub const START_LOW_PERIPHERAL_MODE: usize = 0xFE00_0000; +pub const GPIO_START: usize = START_LOW_PERIPHERAL_MODE + GPIO_OFFSET; +pub const UART0_START: usize = START_LOW_PERIPHERAL_MODE + UART0_OFFSET; +pub const UART2_START: usize = START_LOW_PERIPHERAL_MODE + 0x0020_1400; +pub const UART3_START: usize = START_LOW_PERIPHERAL_MODE + 0x0020_1600; +pub const UART4_START: usize = START_LOW_PERIPHERAL_MODE + 0x0020_1800; +pub const UART5_START: usize = START_LOW_PERIPHERAL_MODE + 0x0020_1A00; + diff --git a/src/bsp/rpi4/mod.rs b/src/bsp/rpi4/mod.rs new file mode 100644 index 0000000..8df69a7 --- /dev/null +++ b/src/bsp/rpi4/mod.rs @@ -0,0 +1,4 @@ +//! Module specific to the raspberry pi 4 implementations. + +#[allow(dead_code)] +pub(self) mod memory_map; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c50e683 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,66 @@ +//! The Judas kernel for raspberry pi. +//! +//! This is a kernel written in rust for raspberry pi 3 and 4. +//! This project is heavily inspired by https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials, +//! but it's a project for discovering rust on embedded, so its architecture is less generic and +//! I'm trying to reuse knowledge from Telecom-Paris SE203 class as much as I can. + +#![no_main] +#![no_std] +#![feature(format_args_nl)] +#![feature(panic_info_message)] + +mod traits; +mod bsp; +mod utils; + +mod panic; +mod print; + +use core::arch::global_asm; +use core::arch::asm; + +// TODO: handle this with features +use crate::bsp::qemu::console::console; +use crate::bsp::rpi3::gpio; + +// TODO: move this to BSP +/// Pause the core with a infinit loop +#[inline(always)] +pub fn wait_forever() -> ! { + loop { + unsafe { asm!("wfe"); } + } +} + +// TODO: move this to BSP +global_asm!(include_str!("boot.s")); + +/// Start the rust part of the kernel +#[no_mangle] +pub unsafe fn _start_rust() -> ! { + match gpio::set_pin_fonction(21, gpio::PinFunction::Output) { + Ok(()) => println!("Successfully set pin to output"), + Err(err) => println!("Failled to set pin: {err}"), + } + loop { + match gpio::set_pin_output_state(21, gpio::PinOutputState::High) { + Ok(()) => println!("Successfully set pin to HIGH"), + Err(err) => println!("Failled to set pin: {err}"), + } + for _ in 0..5000000 { + asm!("nop"); + } + match gpio::set_pin_output_state(21, gpio::PinOutputState::Low) { + Ok(()) => println!("Successfully set pin to HIGH"), + Err(err) => println!("Failled to set pin: {err}"), + } + for _ in 0..5000000 { + asm!("nop"); + } + } + /* + println!("Hello there"); + panic!("Paniccccccc") + */ +} diff --git a/src/panic.rs b/src/panic.rs new file mode 100644 index 0000000..bfc50f0 --- /dev/null +++ b/src/panic.rs @@ -0,0 +1,42 @@ +//! The module handelling kernel panic. + +use core::panic::PanicInfo; + +use crate::println; +use crate::wait_forever; + +/// Avoid nested panic +fn panic_prevent_reenter() { + use core::sync::atomic::{AtomicBool, Ordering}; + // This code is safe to use with AArch64, if using another + // arch, check the safety first + #[cfg(not(target_arch = "aarch64"))] + compile_error!( + "The following code is safe for aarch64, \ + check the safety before using with another arch" + ); + static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false); + if !PANIC_IN_PROGRESS.load(Ordering::Relaxed) { + PANIC_IN_PROGRESS.store(true, Ordering::Relaxed); + return; + } + wait_forever() +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + panic_prevent_reenter(); + + let (location, line, column) = match info.location() { + Some(loc) => (loc.file(), loc.line(), loc.column()), + _ => ("???", 0, 0), + }; + + println!( + "Kernel panic!\n\n\ + Panic location:\n File: '{location}', line {line}, column {column}\n\n\ + {}", + info.message().unwrap_or(&format_args!("")) + ); + wait_forever() +} diff --git a/src/print.rs b/src/print.rs new file mode 100644 index 0000000..c7f1a3a --- /dev/null +++ b/src/print.rs @@ -0,0 +1,25 @@ +//! Module implementing the `print!`/`println!` macro. + +use core::fmt; + +use crate::console; + +/// The backend for printing to the console. +pub fn _print(args: fmt::Arguments) { + console().write_fmt(args).unwrap(); +} + +/// The printing macro. +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// The line printing macro. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ({ + $crate::print::_print(format_args_nl!($($arg)*)); + }) +} diff --git a/src/traits/console.rs b/src/traits/console.rs new file mode 100644 index 0000000..c0011ce --- /dev/null +++ b/src/traits/console.rs @@ -0,0 +1,33 @@ +//! The trait implemented by console structures. + +use core::fmt; + +/// Trait allowing a structure to be used as a console. +pub trait Console: Write {} + +/// Trait allowing to write to an object. +pub trait Write { + /// Write a single character. + fn write_char(&self, c: char); + + /// Write a Rust format string. + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; + + /// Block until the last buffered character has been consumed. + fn flush(&self); +} + +/// A Dummy Console. +/// +/// The DummyConsole implement the [`Console`] trait, and do nothing. +pub struct DummyConsole; + +impl Write for DummyConsole { + fn write_char(&self, _c: char) {} + + fn write_fmt(&self, _args: fmt::Arguments) -> fmt::Result { + Ok(()) + } + + fn flush(&self) {} +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs new file mode 100644 index 0000000..00561f6 --- /dev/null +++ b/src/traits/mod.rs @@ -0,0 +1,12 @@ +//! This module regroup the traits used accros the projet. +//! +//! Most of those traits come with a dummy implementation. +//! Actual implementations are either in generic modules or +//! in target specific modules. +//! +//! # TODO +//! +//! How to select the implementation to use? + +pub mod console; +pub mod synchronization; diff --git a/src/traits/print.rs b/src/traits/print.rs new file mode 100644 index 0000000..c30d9b4 --- /dev/null +++ b/src/traits/print.rs @@ -0,0 +1,117 @@ +//! The module implementing the printing features. +//! +//! For now, the console is implemented here, this may change in the future. +//! The console is implemented using the magic qemu output right now. + +use core::fmt; + +use crate::synchronization::PseudoLock; +use crate::synchronization::Mutex; + +/// This trait implement the same features as `core::fmt::Write`, +/// except it operate on shared references (`&self`) instead of +/// mutable references (`&mut self`). +pub trait Write { + /// Write a single character. + fn write_char(&self, c: char); + + /// Write a Rust format string. + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; + + /// Block until the last buffered character has been physically put on the TX wire. + fn flush(&self); +} + +/// Inner Qemu output. +struct QemuOutputInner; + +/// Qemu output, access to the inner ressources is protected by +/// a mutex. +struct QemuOutput { + inner: PseudoLock, +} + +/// The address for the magic qemu output +const QEMU_MAGIC_OUTPUT_ADDR: *mut u8 = 0x3F20_1000 as *mut u8; + +static QEMU_OUTPUT: QemuOutput = QemuOutput::new(); + +impl QemuOutputInner { + /// Constructor for QemuOutputInner. + const fn new() -> Self { + Self {} + } + + /// Write a character to the output. + fn write_char(&mut self, c: char) { + unsafe { + core::ptr::write_volatile(QEMU_MAGIC_OUTPUT_ADDR, c as u8); + } + } +} + +impl QemuOutput { + pub const fn new() -> Self { + Self { + inner: PseudoLock::new(QemuOutputInner::new()), + } + } +} + +/// Allow to use QemuOutputInner for print! and formating macros. +/// `write_str` needs `&mut self` (mutable ref), so we can implement +/// it only on the inner type, the `QemuOutput` need to be manipulable +/// using unmutable references (`&self`), so we will use a custom +/// interface for it. +impl fmt::Write for QemuOutputInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + // \n -> \r\n + if c == '\n' { + self.write_char('\r'); + } + self.write_char(c); + } + Ok(()) + } +} + +impl Write for QemuOutput { + fn write_char(&self, c: char) { + self.inner.lock(|q_out| q_out.write_char(c)) + } + + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result { + self.inner.lock(|q_out| fmt::Write::write_fmt(q_out, args)) + } + + /// Empty function, the qemu uart has no buffering afaik + fn flush(&self) { + // self.inner.lock(|q_out| q_out.flush()) + } +} + +/// Return a reference to the Qemu Output. +pub fn console() -> &'static dyn Write { + &QEMU_OUTPUT +} + +/// The backend for printing to the console. +pub fn _print(args: fmt::Arguments) { + console().write_fmt(args).unwrap(); +} + +/// The printing macro. +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// The line printing macro. +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ({ + $crate::print::_print(format_args_nl!($($arg)*)); + }) +} diff --git a/src/traits/synchronization.rs b/src/traits/synchronization.rs new file mode 100644 index 0000000..fad0440 --- /dev/null +++ b/src/traits/synchronization.rs @@ -0,0 +1,41 @@ +//! Traits for syncronization primitives. + +use core::cell::UnsafeCell; + +/// For the duration of the closure `f`, an object implementing this trait +/// guarantees exclusive access to the data wrapped by the Mutex object. +pub trait Mutex { + /// The type of the data wrapped by the mutex. + type Data; + /// Locks the mutex and grants access to the wrapped data to the closure. + fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R; +} + +/// Dummy Mutex that does not protect agains anythings. +/// This in only save in monocore monothread context, without +/// interrupt. +pub struct DummyMutex { + data: UnsafeCell, +} + +unsafe impl Send for DummyMutex {} +unsafe impl Sync for DummyMutex {} + +impl DummyMutex { + /// Constructor for [`DummyMutex`]. + pub const fn new(data: T) -> Self { + // panic!("PseudoMutex is verry unsafe"); + Self { + data: UnsafeCell::new(data), + } + } +} + +impl Mutex for DummyMutex { + type Data = T; + + fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R { + let data = unsafe { &mut *self.data.get() }; + f(data) + } +} diff --git a/src/utils/field.rs b/src/utils/field.rs new file mode 100644 index 0000000..94950e8 --- /dev/null +++ b/src/utils/field.rs @@ -0,0 +1,83 @@ +//! Implement the Field structure. + +/// Structure representing the location of a register. +/// This structure does not provide any kind of safety, it +/// is only descriptif. +/// +/// Fields represent a continuous set of bits in an alligned +/// `u32`. This means that the address is a multiple of 4, +/// the offset is < 32, and offset + size <= 32. +/// +/// The constructor of the is class ensure that the field is +/// always inside the `u32` at `address`, and that `address` +/// is alligned. +pub struct Field { + /// The address of the discribed field. + address: usize, + /// The offset of the first bit of the field relative to the + /// address. + offset: usize, + /// The size of the field in bit. + size: usize, + /// The mask for the field in the `u32` at `address`. + mask: u32 +} + +// TODO: test +impl Field { + /// Constructor for a Field. + /// The field is defined by the `size` bits at `offset` bits after + /// `address`. (**`address` is in bytes, not bits**) + pub const fn new(address: usize, offset: usize, size: usize) -> Self { + // align the address + let offset = offset + 8 * (address % 4); + let address = address - (address % 4); + // make sure the field is in the u32 at address + let address = address + 4 * (offset / 32); + let offset = offset % 32; + // make sure the field does not overlap with the next u32 + if offset + size > 32 { + panic!("A field can not overlap two aligned `u32`"); + } + + let mask = Self::compute_mask(offset, size); + + Self { address, offset, size, mask } + } + + /// Compute mask for the field. + const fn compute_mask(offset: usize, size: usize) -> u32 { + let mut mask = 0u32; + // Const functions don't allow for loops. + let mut i = 0; + while i < size { + mask <<= 1; + mask |= 1; + i += 1; + } + mask <<= offset; + mask + } + + /// Return the address of the `u32` containing the field. + pub const fn get_address(&self) -> usize { + self.address + } + + /// Return the offset of the field in relation to the address, in bits. + #[allow(dead_code)] + pub const fn get_offset(&self) -> usize { + self.offset + } + + /// Return the size of the field in bit. + #[allow(dead_code)] + pub const fn get_size(&self) -> usize { + self.size + } + + /// Return the mask of the field in the `u32` at `address`. + pub const fn get_mask(&self) -> u32 { + self.mask + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..4a819ac --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,3 @@ +//! Utility structure an functions. + +pub mod field;