first commit: rpi3 blinking led

This commit is contained in:
histausse 2022-10-11 23:53:12 +02:00
commit 0c7793fe1f
Signed by: histausse
GPG key ID: 67486F107F62E9E9
39 changed files with 1138 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target
kernel8.img
__pycache__/
.doit.db

7
Cargo.lock generated Normal file
View file

@ -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"

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "judas"
version = "0.1.0"
authors = ["Jean-Marie Mineau <histausse@protonmail.com>"]
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"

5
README.md Normal file
View file

@ -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.

Binary file not shown.

View file

@ -0,0 +1,3 @@
arm_64bit=1
init_uart_clock=48000000
enable_jtag_gpio=1

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,3 @@
arm_64bit=1
init_uart_clock=48000000
enable_jtag_gpio=1

Binary file not shown.

Binary file not shown.

4
build.rs Normal file
View file

@ -0,0 +1,4 @@
fn main() {
let linker_script = "linker.ld";
println!("cargo:rerun-if-changed={linker_script}");
}

BIN
doc/VideoCoreIV-AG100-R.pdf Normal file

Binary file not shown.

BIN
doc/as.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
doc/ld.pdf Normal file

Binary file not shown.

135
dodo.py Normal file
View file

@ -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

49
linker.ld Normal file
View file

@ -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)

3
rust-toolchain.toml Normal file
View file

@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
targets = ["aarch64-unknown-none-softfloat"]

35
src/boot.s Normal file
View file

@ -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

10
src/bsp/mod.rs Normal file
View file

@ -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;

85
src/bsp/qemu/console.rs Normal file
View file

@ -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<QemuOutputInner>,
}
/// 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
}

7
src/bsp/qemu/mod.rs Normal file
View file

@ -0,0 +1,7 @@
//! Implement the features specific to code running inside qemu.
//!
//! # TODO
//!
//! Move this somewhere else?
pub mod console;

101
src/bsp/rpi3/gpio.rs Normal file
View file

@ -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<PinFunction> 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::<u32>::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(())
}

220
src/bsp/rpi3/memory_map.rs Normal file
View file

@ -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 **

6
src/bsp/rpi3/mod.rs Normal file
View file

@ -0,0 +1,6 @@
//! Module specific to the raspberry pi 3 implementations.
#[allow(dead_code)]
pub(self) mod memory_map;
pub mod gpio;

View file

@ -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;

4
src/bsp/rpi4/mod.rs Normal file
View file

@ -0,0 +1,4 @@
//! Module specific to the raspberry pi 4 implementations.
#[allow(dead_code)]
pub(self) mod memory_map;

66
src/main.rs Normal file
View file

@ -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")
*/
}

42
src/panic.rs Normal file
View file

@ -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()
}

25
src/print.rs Normal file
View file

@ -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)*));
})
}

33
src/traits/console.rs Normal file
View file

@ -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) {}
}

12
src/traits/mod.rs Normal file
View file

@ -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;

117
src/traits/print.rs Normal file
View file

@ -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<QemuOutputInner>,
}
/// 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)*));
})
}

View file

@ -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<T: ?Sized> {
data: UnsafeCell<T>,
}
unsafe impl<T: ?Sized + Send> Send for DummyMutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for DummyMutex<T> {}
impl<T> DummyMutex<T> {
/// Constructor for [`DummyMutex`].
pub const fn new(data: T) -> Self {
// panic!("PseudoMutex is verry unsafe");
Self {
data: UnsafeCell::new(data),
}
}
}
impl<T> Mutex for DummyMutex<T> {
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)
}
}

83
src/utils/field.rs Normal file
View file

@ -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
}
}

3
src/utils/mod.rs Normal file
View file

@ -0,0 +1,3 @@
//! Utility structure an functions.
pub mod field;