first commit: rpi3 blinking led
This commit is contained in:
commit
0c7793fe1f
39 changed files with 1138 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
kernel8.img
|
||||||
|
__pycache__/
|
||||||
|
.doit.db
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal 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
20
Cargo.toml
Normal 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
5
README.md
Normal 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.
|
BIN
bootloader_files/rpi3/bootcode.bin
Normal file
BIN
bootloader_files/rpi3/bootcode.bin
Normal file
Binary file not shown.
3
bootloader_files/rpi3/config.txt
Normal file
3
bootloader_files/rpi3/config.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
arm_64bit=1
|
||||||
|
init_uart_clock=48000000
|
||||||
|
enable_jtag_gpio=1
|
BIN
bootloader_files/rpi3/fixup.dat
Normal file
BIN
bootloader_files/rpi3/fixup.dat
Normal file
Binary file not shown.
BIN
bootloader_files/rpi3/start.elf
Normal file
BIN
bootloader_files/rpi3/start.elf
Normal file
Binary file not shown.
BIN
bootloader_files/rpi4/bcm2711-rpi-4-b.dtb
Normal file
BIN
bootloader_files/rpi4/bcm2711-rpi-4-b.dtb
Normal file
Binary file not shown.
3
bootloader_files/rpi4/config.txt
Normal file
3
bootloader_files/rpi4/config.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
arm_64bit=1
|
||||||
|
init_uart_clock=48000000
|
||||||
|
enable_jtag_gpio=1
|
BIN
bootloader_files/rpi4/fixup4.dat
Normal file
BIN
bootloader_files/rpi4/fixup4.dat
Normal file
Binary file not shown.
BIN
bootloader_files/rpi4/start4.elf
Normal file
BIN
bootloader_files/rpi4/start4.elf
Normal file
Binary file not shown.
4
build.rs
Normal file
4
build.rs
Normal 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
BIN
doc/VideoCoreIV-AG100-R.pdf
Normal file
Binary file not shown.
BIN
doc/as.pdf
Normal file
BIN
doc/as.pdf
Normal file
Binary file not shown.
BIN
doc/bcm2711-peripherals-raspy4.pdf
Normal file
BIN
doc/bcm2711-peripherals-raspy4.pdf
Normal file
Binary file not shown.
BIN
doc/bcm2837-peripherals-raspy3.pdf
Normal file
BIN
doc/bcm2837-peripherals-raspy3.pdf
Normal file
Binary file not shown.
BIN
doc/ld.pdf
Normal file
BIN
doc/ld.pdf
Normal file
Binary file not shown.
135
dodo.py
Normal file
135
dodo.py
Normal 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
49
linker.ld
Normal 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
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
targets = ["aarch64-unknown-none-softfloat"]
|
35
src/boot.s
Normal file
35
src/boot.s
Normal 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
10
src/bsp/mod.rs
Normal 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
85
src/bsp/qemu/console.rs
Normal 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
7
src/bsp/qemu/mod.rs
Normal 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
101
src/bsp/rpi3/gpio.rs
Normal 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
220
src/bsp/rpi3/memory_map.rs
Normal 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
6
src/bsp/rpi3/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
//! Module specific to the raspberry pi 3 implementations.
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(self) mod memory_map;
|
||||||
|
|
||||||
|
pub mod gpio;
|
15
src/bsp/rpi4/memory_map.rs
Normal file
15
src/bsp/rpi4/memory_map.rs
Normal 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
4
src/bsp/rpi4/mod.rs
Normal 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
66
src/main.rs
Normal 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
42
src/panic.rs
Normal 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
25
src/print.rs
Normal 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
33
src/traits/console.rs
Normal 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
12
src/traits/mod.rs
Normal 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
117
src/traits/print.rs
Normal 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)*));
|
||||||
|
})
|
||||||
|
}
|
41
src/traits/synchronization.rs
Normal file
41
src/traits/synchronization.rs
Normal 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
83
src/utils/field.rs
Normal 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
3
src/utils/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
//! Utility structure an functions.
|
||||||
|
|
||||||
|
pub mod field;
|
Loading…
Reference in a new issue