8 Commits

Author SHA1 Message Date
aneuhaeuser abdef70198 use virtual workspaces for heap 2025-12-19 15:32:54 +01:00
aneuhaeuser 31e68b9bd2 update gitignore 2025-12-12 15:58:50 +01:00
aneuhaeuser 4e79832e00 update timer 2025-12-12 15:53:42 +01:00
aneuhaeuser 326a779692 update Readme 2025-09-14 18:15:48 +02:00
aneuhaeuser 27b185239f Re organize 2025-09-14 18:13:52 +02:00
aneuhaeuser f0f71ea490 Migrate to a struct based heap implementation 2025-09-14 17:59:50 +02:00
aneuhaeuser 4dbbfa1fcf Implement dealloc 2025-09-14 10:20:08 +02:00
aneuhaeuser 44cfcd9f69 Implement Maloc 2025-09-13 22:17:27 +02:00
43 changed files with 535 additions and 2656 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
KERNEL_NAME=kernel8.img
BUILD_PATH=target/aarch64-unknown-none/release
BINARY_NAME=nova
BINARY_NAME=BINARY_NAME
TFTP_PATH=/srv/tftp
REMOTE_USER=TFTP_HOST_USER
REMOTE_HOST=TFTP_HOST_IP
+3 -14
View File
@@ -21,7 +21,8 @@ jobs:
- name: Run format check
run: cargo fmt --check
- name: Run lint
run: cargo clippy --target aarch64-unknown-none -- -D warnings
run: cargo clippy -- -D warnings
build:
runs-on: ubuntu-latest
@@ -32,16 +33,4 @@ jobs:
- name: Add AArch64 Target
run: rustup target add aarch64-unknown-none
- name: Build
run: cargo build --verbose --target aarch64-unknown-none
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install rustfmt for nightly
run: rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt clippy
- name: Add AArch64 Target
run: rustup target add aarch64-unknown-none
- name: Heap Workspace Test
run: cargo test -p heap
run: cargo build --verbose
-7
View File
@@ -4,10 +4,3 @@ kernel8.img
sd.img
settings.json
.DS_Store
.venv
.nvimlog
__pycache__
.pytest_cache
build/
+4 -38
View File
@@ -3,7 +3,7 @@
"compounds": [
{
"name": "Run QEMU + Attach LLDB",
"configurations": ["LLDB"],
"configurations": ["Attach LLDB"],
"preLaunchTask": "Run QEMU"
}
],
@@ -33,47 +33,13 @@
],
"preLaunchTask": "Run QEMU"
},
{
"name": "Attach to QEMU (AArch64) wo. window",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova",
"miDebuggerServerAddress": "localhost:1234",
"miDebuggerPath": "gdb",
"cwd": "${workspaceFolder}",
"stopAtEntry": true,
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Show assembly on stop",
"text": "set disassemble-next-line on",
"ignoreFailures": true
}
],
"preLaunchTask": "Run QEMU wo window"
},
{
"name": "LLDB",
"type": "lldb",
"request": "attach",
"program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova",
"preLaunchTask": "Run QEMU",
"stopOnEntry": true,
"processCreateCommands": ["gdb-remote localhost:1234"]
},
{
"name": "NVIM LLDB",
"type": "codelldb",
"name": "Attach LLDB",
"type": "lldb",
"request": "attach",
"debugServer": 1234,
"program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova",
"preLaunchTask": "Run QEMU",
"stopOnEntry": true,
"processCreateCommands": ["gdb-remote localhost:1234"]
}
+2 -31
View File
@@ -14,38 +14,9 @@
{
"label": "Run QEMU",
"type": "shell",
"command": "llvm-objcopy -O binary target/aarch64-unknown-none/debug/nova target/aarch64-unknown-none/debug/kernel8.img && echo Starting QEMU&qemu-system-aarch64 -M raspi3b -cpu cortex-a53 -serial stdio -sd sd.img -kernel ${workspaceFolder}/target/aarch64-unknown-none/debug/kernel8.img -S -s -m 1024",
"command": "llvm-objcopy -O binary target/aarch64-unknown-none/debug/nova target/aarch64-unknown-none/debug/kernel8.img && qemu-system-aarch64 -M raspi3b -cpu cortex-a53 -serial stdio -sd sd.img -kernel ${workspaceFolder}/target/aarch64-unknown-none/debug/kernel8.img -S -s -m 1024",
"isBackground": true,
"dependsOn": ["Build"],
"problemMatcher": {
"pattern": {
"regexp": "^(Starting QEMU)",
"line": 1,
},
"background": {
"activeOnStart": true,
"beginsPattern": "^(Starting QEMU)",
"endsPattern": "^(Starting QEMU)"
}
}
},
{
"label": "Run QEMU wo window",
"type": "shell",
"command": "llvm-objcopy -O binary target/aarch64-unknown-none/debug/nova target/aarch64-unknown-none/debug/kernel8.img && echo Starting QEMU&qemu-system-aarch64 -M raspi3b -cpu cortex-a53 -display none -serial stdio -sd sd.img -kernel ${workspaceFolder}/target/aarch64-unknown-none/debug/kernel8.img -S -s -m 1024",
"isBackground": true,
"dependsOn": ["Build"],
"problemMatcher": {
"pattern": {
"regexp": "^(Starting QEMU)",
"line": 1,
},
"background": {
"activeOnStart": true,
"beginsPattern": "^(Starting QEMU)",
"endsPattern": "^(Starting QEMU)"
}
}
"dependsOn": ["Build"]
}
]
}
Generated
+4 -182
View File
@@ -3,205 +3,27 @@
version = 4
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
name = "NovaError"
version = "0.1.0"
[[package]]
name = "heap"
version = "0.1.0"
dependencies = [
"nova_error",
"rand",
"NovaError",
]
[[package]]
name = "libc"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "nova"
version = "0.1.0"
dependencies = [
"NovaError",
"heap",
"libm",
"log",
"nova_error",
"paste",
"spin",
]
[[package]]
name = "nova_error"
version = "0.1.0"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
dependencies = [
"lock_api",
]
[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+4 -8
View File
@@ -14,15 +14,11 @@ panic = "abort"
[dependencies]
libm = "0.2.15"
heap = {path = "workspace/heap"}
nova_error = {path = "workspace/nova_error"}
paste = "1.0.15"
log = "0.4.29"
spin = "0.10.0"
heap = {path = "heap"}
NovaError = {path = "NovaError"}
[workspace]
members = [
"workspace/nova_error",
"workspace/heap",
members = [ "NovaError",
"heap"
]
-26
View File
@@ -1,26 +0,0 @@
MIT NON-AI License
Copyright (c) 2026, Alexander Neuhäuser
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
In addition, the following restrictions apply:
1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms,
including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives,
modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License.
2. The Software may not be included in any dataset used for training or improving machine learning algorithms,
including but not limited to artificial intelligence, natural language processing, or data mining.
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable
for any damages resulting from such use.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,4 +1,4 @@
[package]
name = "nova_error"
name = "NovaError"
version = "0.1.0"
edition = "2024"
@@ -5,12 +5,7 @@ use core::prelude::rust_2024::derive;
#[derive(Debug)]
pub enum NovaError {
General(&'static str),
Mailbox,
HeapFull,
EmptyHeapSegmentNotAllowed,
Misalignment,
InvalidGranularity,
Paging(&'static str),
OutOfMeomory,
}
+16 -115
View File
@@ -1,120 +1,21 @@
# NovaOS
![NovaOS banner](docs/banner.png)
NovaOS is a expository project where I build a kernel from scratch for a Raspberry PI 3 B+.
NovaOS is a hobby operating system kernel written in Rust for the Raspberry Pi 3 B+.
It is built as a learning project for low-level systems programming, bare-metal boot flow, and kernel development.
[Technical write-up](https://blog.leafnova.net/projects/pi3_kernel/)
## At A Glance
## Features
NovaOS currently includes:
- UART initialization and logging
- Delay and sleep primitives
- Exception level transitions across EL2, EL1, and EL0
- GPIO control and interrupt handling
- Peripheral mailbox communication
- Framebuffer drawing primitives
- Heap memory allocation
- MMU initialization and translation table setup
Work in progress:
- SVC instruction handling
- Basic UART console improvements
- Multi-application management
Planned next:
- Multi-core support
- Dynamic clock speed management
- Kernel-independent applications
- Multiprocessing improvements
## Project Structure
- `src/` - kernel source, architecture code, peripherals, interrupts, and runtime
- `workspace/` - supporting crates such as `heap` and `nova_error`
- `tools/` - build, simulation, SD image generation, and deployment scripts
- `firmware_files/` - Raspberry Pi firmware files copied to SD or TFTP
- `link.ld` - linker script for the kernel image
## Requirements
You will need:
- Rust nightly toolchain (`rust-toolchain.toml` pins `nightly`)
- Rust target `aarch64-unknown-none`
- `llvm-objcopy` for generating `kernel8.img`
- `qemu-system-aarch64` for emulation
- `mtools` (`mformat`, `mcopy`) for SD image generation
Install the Rust target if needed:
```bash
rustup target add aarch64-unknown-none
```
## Build
Debug image:
```bash
cd tools
./build_debug.sh
```
Release image:
```bash
cd tools
./build_release.sh
```
Both scripts produce a `kernel8.img` under `target/aarch64-unknown-none/<profile>/`.
## Run In QEMU
1. Generate an SD image with firmware files:
```bash
cd tools
./generate_sd_card.sh
```
2. Start the emulator:
```bash
cd tools
./start_simulator.sh
```
For debug mode with the GDB stub enabled (`-S -s`):
```bash
cd tools
./start_simulator_debug.sh
```
## Deploy To Hardware
Use the TFTP workflow to deploy to a Raspberry Pi:
1. Copy `.env.example` to `.env` and fill in your values:
- `REMOTE_USER`
- `REMOTE_HOST`
- `TFTP_PATH`
- `BUILD_PATH`
- `BINARY_NAME`
- `KERNEL_NAME`
2. Run:
```bash
cd tools
./deply_to_hw.sh
```
## Notes
- This is an educational kernel project and is actively evolving.
- Interfaces and boot flow may change as features are added.
- Delay and sleep ✓
- UART ✓
- Switching ELs ✓
- GPIOs ✓
- GPIO Interrupts ✓
- Communicate with peripherals via mailboxes ✓
- Frame Buffer ✓
- Heap Memory allocation ✓
- Multi Core
- Dynamic clock speed
- MMU
- Multiprocessing
- Basic Terminal over UART
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

@@ -4,7 +4,4 @@ version = "0.1.0"
edition = "2024"
[dependencies]
nova_error = {path = "../nova_error"}
[dev-dependencies]
rand = "0.9.2"
NovaError = {path = "../NovaError"}
+48 -58
View File
@@ -3,58 +3,61 @@
use core::{
alloc::GlobalAlloc,
default::Default,
mem::size_of,
prelude::v1::*,
ptr::{self, null_mut},
ptr::{self, null_mut, read_volatile},
result::Result,
};
use nova_error::NovaError;
use NovaError::NovaError;
#[cfg(not(target_os = "none"))]
extern crate std;
extern crate alloc;
#[repr(C, align(16))]
#[derive(Clone, Copy)]
struct HeapHeader {
next: Option<*mut HeapHeader>,
before: Option<*mut HeapHeader>,
size: usize,
pub struct HeapHeader {
pub next: *mut HeapHeader,
before: *mut HeapHeader,
pub size: usize,
free: bool,
}
const HEAP_HEADER_SIZE: usize = size_of::<HeapHeader>();
const MIN_BLOCK_SIZE: usize = 16;
// TODO: This implementation has to be reevaluated when implementing multiprocessing
// Spinlock could be a solution but has its issues:
// https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html
pub struct Heap {
start_address: *mut HeapHeader,
end_address: *mut HeapHeader,
raw_size: usize,
pub start_address: *mut HeapHeader,
pub end_address: *mut HeapHeader,
pub raw_size: usize,
}
impl Heap {
pub const fn empty() -> Self {
Self {
start_address: null_mut(),
end_address: null_mut(),
start_address: null_mut() as *mut HeapHeader,
end_address: null_mut() as *mut HeapHeader,
raw_size: 0,
}
}
pub fn size(self) -> usize {
self.raw_size
}
pub fn init(&mut self, heap_start: usize, heap_end: usize) {
self.start_address = heap_start as *mut HeapHeader;
self.end_address = heap_end as *mut HeapHeader;
self.raw_size = heap_end - heap_start + 1;
self.raw_size = heap_end - heap_start;
unsafe {
ptr::write(
self.start_address,
HeapHeader {
next: None,
before: None,
next: null_mut(),
before: null_mut(),
size: self.raw_size - HEAP_HEADER_SIZE,
free: true,
},
@@ -64,19 +67,16 @@ impl Heap {
unsafe fn find_first_fit(&self, size: usize) -> Result<*mut HeapHeader, NovaError> {
let mut current = self.start_address;
unsafe {
while !fits(size, current) {
if let Some(next) = (*current).next {
current = next;
} else {
if (*self.start_address).next.is_null() {
return Err(NovaError::HeapFull);
}
}
current = (*current).next;
}
Ok(current)
}
fn malloc(&self, mut size: usize) -> Result<*mut u8, NovaError> {
pub fn malloc(&self, mut size: usize) -> Result<*mut u8, NovaError> {
if size == 0 {
return Err(NovaError::EmptyHeapSegmentNotAllowed);
}
@@ -112,47 +112,43 @@ impl Heap {
let new_address = unsafe { current.byte_add(byte_offset) };
// Handle case where fragmenting center free space
unsafe {
let next = (*current).next;
if let Some(next) = next {
(*next).before = Some(new_address);
if !(*current).next.is_null() {
(*next).before = new_address;
}
unsafe {
ptr::write(
new_address,
new_address as *mut HeapHeader,
HeapHeader {
next,
before: Some(current),
size: (*current).size - byte_offset,
before: current,
size: (*current).size - size - HEAP_HEADER_SIZE,
free: true,
},
);
(*current).next = Some(new_address);
)
};
(*current).next = new_address;
(*current).free = false;
(*current).size = size;
}
}
fn free(&self, pointer: *mut u8) -> Result<(), NovaError> {
let mut segment = Self::get_header_ref_from_data_pointer(pointer);
pub fn free(&self, pointer: *mut u8) -> Result<(), NovaError> {
let mut segment = unsafe { pointer.sub(HEAP_HEADER_SIZE) as *mut HeapHeader };
unsafe {
// IF prev is free:
// Delete header, add size to previous and fix pointers.
// Move Head left
if let Some(before_head) = (*segment).before
&& (*before_head).free
{
if !(*segment).before.is_null() && (*(*segment).before).free {
let before_head = (*segment).before;
(*before_head).size += (*segment).size + HEAP_HEADER_SIZE;
delete_header(segment);
segment = before_head;
}
// IF next is free:
// Delete next header and merge size, fix pointers
if let Some(next_head) = (*segment).next
&& (*next_head).free
{
if !(*segment).next.is_null() && (*(*segment).next).free {
let next_head = (*segment).next;
(*segment).size += (*next_head).size + HEAP_HEADER_SIZE;
delete_header(next_head);
}
@@ -163,10 +159,6 @@ impl Heap {
Ok(())
}
const fn get_header_ref_from_data_pointer(pointer: *mut u8) -> *mut HeapHeader {
unsafe { pointer.sub(HEAP_HEADER_SIZE) as *mut HeapHeader }
}
}
unsafe impl GlobalAlloc for Heap {
@@ -182,23 +174,21 @@ unsafe impl GlobalAlloc for Heap {
unsafe impl Sync for Heap {}
unsafe fn fits(size: usize, header: *mut HeapHeader) -> bool {
unsafe { (*header).free && size <= (*header).size }
(*header).free && size <= (*header).size
}
unsafe fn delete_header(header: *mut HeapHeader) {
unsafe {
let before_opt = (*header).before;
let next_opt = (*header).next;
let before = (*header).before;
let next = (*header).next;
if let Some(before) = before_opt {
(*before).next = next_opt;
if !before.is_null() {
(*before).next = next;
}
if let Some(next) = next_opt {
(*next).before = before_opt;
}
if !next.is_null() {
(*next).before = before;
}
}
#[cfg(test)]
mod tests;
mod tests {}
+17 -15
View File
@@ -5,42 +5,44 @@ SECTIONS {
KEEP(*(.text._start))
*(.text .text.*)
}
.vector_table ALIGN(2K) : {
KEEP(*(.vector_t))
}
. = ALIGN(4K);
__text_end = .;
.rodata : {
*(.rodata .rodata.*)
}
.data : {
_data = .;
*(.data .data.*)
}
.bss ALIGN(16) (NOLOAD) : {
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
. = ALIGN(2M);
.vector_table ALIGN(2048) : {
KEEP(*(.vector_table))
}
__share_end = .;
.heap 0x8000000 : ALIGN(16)
{
__heap_start = .;
. += 0x10000; #10kB
__heap_end = .;
}
# EL2 Stack
.stack ALIGN(16): {
.stack : ALIGN(16)
{
__stack_start = .;
. += 100K; #100kB stack
. = ALIGN(16);
. += 0x10000; #10kB stack
__stack_end = .;
}
. = ALIGN(2M);
__kernel_end = .;
_end = .;
}
__bss_size = (__bss_end - __bss_start) >> 3;
-439
View File
@@ -1,439 +0,0 @@
use core::mem::size_of;
use nova_error::NovaError;
use crate::{
aarch64::mmu::physical_mapping::{
reserve_block, reserve_block_explicit, reserve_page, reserve_page_explicit,
},
get_current_el,
};
const BLOCK: u64 = 0b01;
const TABLE: u64 = 0b11;
const PAGE: u64 = 0b11;
/// Allow EL0 to access this section
pub const EL0_ACCESSIBLE: u64 = 1 << 6;
/// Allow a page or block to be written.
pub const WRITABLE: u64 = 0 << 7;
/// Disallow a page or block to be written.
pub const READ_ONLY: u64 = 1 << 7;
const ACCESS_FLAG: u64 = 1 << 10;
const INNER_SHAREABILITY: u64 = 0b11 << 8;
pub const NORMAL_MEM: u64 = 0 << 2;
pub const DEVICE_MEM: u64 = 1 << 2;
/// Disallow EL1 Execution.
pub const PXN: u64 = 1 << 53;
/// Disallow EL0 Execution.
pub const UXN: u64 = 1 << 54;
pub const GRANULARITY: usize = 4 * 1024;
const TABLE_ENTRY_COUNT: usize = GRANULARITY / size_of::<u64>(); // 2MiB
pub const LEVEL1_BLOCK_SIZE: usize = TABLE_ENTRY_COUNT * TABLE_ENTRY_COUNT * GRANULARITY;
pub const LEVEL2_BLOCK_SIZE: usize = TABLE_ENTRY_COUNT * GRANULARITY;
const L2_BLOCK_BITMAP_WORDS: usize = LEVEL2_BLOCK_SIZE / (64 * GRANULARITY);
const MAX_PAGE_COUNT: usize = 1024 * 1024 * 1024 / GRANULARITY;
const TRANSLATION_TABLE_BASE_ADDR: usize = 0xFFFF_FF82_0000_0000;
#[no_mangle]
pub static KERNEL_VIRTUAL_MEM_SPACE: usize = 0xFFFF_FF80_0000_0000;
pub const STACK_START_ADDR: usize = !KERNEL_VIRTUAL_MEM_SPACE & (!0xF);
pub mod physical_mapping;
pub type VirtAddr = usize;
pub type PhysAddr = usize;
#[derive(Clone, Copy)]
pub struct TableEntry {
value: u64,
}
impl TableEntry {
pub fn invalid() -> Self {
Self { value: 0 }
}
fn table_descriptor(addr: PhysAddr) -> Self {
Self {
value: (addr as u64 & 0x0000_FFFF_FFFF_F000) | TABLE,
}
}
fn block_descriptor(physical_address: usize, additional_flags: u64) -> Self {
Self {
value: (physical_address as u64 & 0x0000_FFFF_FFFF_F000)
| BLOCK
| ACCESS_FLAG
| INNER_SHAREABILITY
| additional_flags,
}
}
fn page_descriptor(physical_address: usize, additional_flags: u64) -> Self {
Self {
value: (physical_address as u64 & 0x0000_FFFF_FFFF_F000)
| PAGE
| ACCESS_FLAG
| INNER_SHAREABILITY
| additional_flags,
}
}
fn is_invalid(self) -> bool {
self.value & 0b11 == 0
}
#[inline]
fn address(self) -> PhysAddr {
self.value as usize & 0x0000_FFFF_FFFF_F000
}
}
pub enum PhysSource {
Any,
Explicit(PhysAddr),
}
#[repr(align(4096))]
pub struct PageTable(pub [TableEntry; TABLE_ENTRY_COUNT]);
impl Iterator for PageTable {
type Item = VirtAddr;
fn next(&mut self) -> Option<Self::Item> {
for (offset, entity) in self.0.iter().enumerate() {
if entity.is_invalid() {
return Some(offset);
}
}
None
}
}
#[no_mangle]
pub static mut TRANSLATIONTABLE_TTBR0: PageTable = PageTable([TableEntry { value: 0 }; 512]);
#[no_mangle]
pub static mut TRANSLATIONTABLE_TTBR1: PageTable = PageTable([TableEntry { value: 0 }; 512]);
/// Allocate a memory block of `size` starting at `virtual_address`.
pub fn allocate_memory(
virtual_address: usize,
size_bytes: usize,
phys: PhysSource,
flags: u64,
) -> Result<(), NovaError> {
if !virtual_address.is_multiple_of(GRANULARITY) {
return Err(NovaError::Misalignment);
}
if !size_bytes.is_multiple_of(GRANULARITY) {
return Err(NovaError::InvalidGranularity);
}
let base_table = if virtual_address & KERNEL_VIRTUAL_MEM_SPACE > 0 {
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1)
} else {
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0)
};
match phys {
PhysSource::Any => map_range_dynamic(virtual_address, size_bytes, base_table, flags),
PhysSource::Explicit(phys_addr) => {
map_range_explicit(virtual_address, phys_addr, size_bytes, base_table, flags)
}
}
}
fn map_range_explicit(
mut virt: VirtAddr,
mut phys: PhysAddr,
size_bytes: usize,
base: *mut PageTable,
flags: u64,
) -> Result<(), NovaError> {
let mut remaining = size_bytes;
while !virt.is_multiple_of(LEVEL2_BLOCK_SIZE) && remaining > 0 {
map_page(virt, phys, base, flags)?;
(virt, _) = virt.overflowing_add(GRANULARITY);
phys += GRANULARITY;
remaining -= GRANULARITY;
}
while remaining >= LEVEL2_BLOCK_SIZE {
map_l2_block(virt, phys, base, flags)?;
(virt, _) = virt.overflowing_add(LEVEL2_BLOCK_SIZE);
phys += LEVEL2_BLOCK_SIZE;
remaining -= LEVEL2_BLOCK_SIZE;
}
while remaining > 0 {
map_page(virt, phys, base, flags)?;
(virt, _) = virt.overflowing_add(GRANULARITY);
phys += GRANULARITY;
remaining -= GRANULARITY;
}
Ok(())
}
fn map_range_dynamic(
mut virt: PhysAddr,
size_bytes: usize,
base: *mut PageTable,
flags: u64,
) -> Result<(), NovaError> {
let mut remaining = size_bytes;
while remaining >= LEVEL2_BLOCK_SIZE {
map_l2_block(virt, reserve_block(), base, flags)?;
(virt, _) = virt.overflowing_add(LEVEL2_BLOCK_SIZE);
remaining -= LEVEL2_BLOCK_SIZE;
}
while remaining > 0 {
map_page(virt, reserve_page(), base, flags)?;
(virt, _) = virt.overflowing_add(GRANULARITY);
remaining -= GRANULARITY;
}
Ok(())
}
/// Allocate a singe page.
pub fn alloc_page(
virtual_address: VirtAddr,
base_table: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
map_page(
virtual_address,
reserve_page(),
base_table,
additional_flags,
)
}
/// Allocate a singe page in one block.
pub fn find_free_kerne_page_in_block(start: VirtAddr) -> Result<VirtAddr, NovaError> {
if !start.is_multiple_of(LEVEL2_BLOCK_SIZE) {
return Err(NovaError::Misalignment);
}
let (off1, off2, _) = virtual_address_to_table_offset(start);
let offsets = [off1, off2];
let table = unsafe {
&mut *navigate_table(
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1),
&offsets,
true,
)?
};
if let Some(offset) = table.next() {
return Ok(start + (offset * GRANULARITY));
}
Err(NovaError::OutOfMeomory)
}
/// Allocate a single page at an explicit `physical_address`.
pub fn alloc_page_explicit(
virtual_address: usize,
physical_address: usize,
base_table: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
reserve_page_explicit(physical_address)?;
map_page(
virtual_address,
physical_address,
base_table,
additional_flags,
)
}
pub fn map_page(
virtual_address: usize,
physical_address: usize,
base_table_ptr: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
let (l1_off, l2_off, l3_off) = virtual_address_to_table_offset(virtual_address);
let offsets = [l1_off, l2_off];
let table_ptr = navigate_table(base_table_ptr, &offsets, true)?;
let table = unsafe { &mut *table_ptr };
if !table.0[l3_off].is_invalid() {
return Err(NovaError::Paging("Page already occupied."));
}
table.0[l3_off] = TableEntry::page_descriptor(physical_address, additional_flags);
Ok(())
}
// Allocate a level 2 block, at a explicit `physical_address`.
pub fn alloc_block_l2_explicit(
virtual_addr: usize,
physical_address: usize,
base_table_ptr: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
if !physical_address.is_multiple_of(LEVEL2_BLOCK_SIZE) {
return Err(NovaError::Misalignment);
}
reserve_block_explicit(physical_address)?;
map_l2_block(
virtual_addr,
physical_address,
base_table_ptr,
additional_flags,
)
}
pub fn map_l2_block(
virtual_addr: usize,
physical_address: usize,
base_table_ptr: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
let (l1_off, l2_off, _) = virtual_address_to_table_offset(virtual_addr);
let offsets = [l1_off];
let table_ptr = navigate_table(base_table_ptr, &offsets, true)?;
let table = unsafe { &mut *table_ptr };
// Verify virtual address is available.
if !table.0[l2_off].is_invalid() {
return Err(NovaError::Paging("Block already occupied."));
}
let new_entry = TableEntry::block_descriptor(physical_address, additional_flags);
table.0[l2_off] = new_entry;
Ok(())
}
pub fn reserve_range(
start_physical_address: PhysAddr,
end_physical_address: PhysAddr,
) -> Result<PhysAddr, NovaError> {
let mut size = end_physical_address - start_physical_address;
let l1_blocks = size / LEVEL1_BLOCK_SIZE;
size %= LEVEL1_BLOCK_SIZE;
let l2_blocks = size / LEVEL2_BLOCK_SIZE;
size %= LEVEL2_BLOCK_SIZE;
let l3_pages = size / GRANULARITY;
if !size.is_multiple_of(GRANULARITY) {
return Err(NovaError::Misalignment);
}
if l1_blocks > 0 {
todo!();
}
let mut addr = start_physical_address;
for _ in 0..l2_blocks {
reserve_block_explicit(addr)?;
addr += LEVEL2_BLOCK_SIZE;
}
for _ in 0..l3_pages {
reserve_page_explicit(addr)?;
addr += GRANULARITY;
}
Ok(start_physical_address)
}
fn virtual_address_to_table_offset(virtual_addr: usize) -> (usize, usize, usize) {
let absolute_page_off = (virtual_addr & !KERNEL_VIRTUAL_MEM_SPACE) / GRANULARITY;
let l3_off = absolute_page_off % TABLE_ENTRY_COUNT;
let l2_off = (absolute_page_off / TABLE_ENTRY_COUNT) % TABLE_ENTRY_COUNT;
let l1_off = (absolute_page_off / TABLE_ENTRY_COUNT / TABLE_ENTRY_COUNT) % TABLE_ENTRY_COUNT;
(l1_off, l2_off, l3_off)
}
/// Navigate the table tree, by following given offsets. This function
/// allocates new tables if required.
fn navigate_table(
initial_table_ptr: *mut PageTable,
offsets: &[usize],
create_missing: bool,
) -> Result<*mut PageTable, NovaError> {
let mut table = initial_table_ptr;
for offset in offsets {
table = next_table(table, *offset, create_missing)?;
}
Ok(table)
}
/// Get the next table one level down.
///
/// If table doesn't exit a page will be allocated for it.
fn next_table(
table_ptr: *mut PageTable,
offset: usize,
create_missing: bool,
) -> Result<*mut PageTable, NovaError> {
let table = unsafe { &mut *table_ptr };
match table.0[offset].value & 0b11 {
0 => {
if !create_missing {
return Err(NovaError::Paging("No table defined."));
}
let new_phys_page_table_address = reserve_page();
table.0[offset] = TableEntry::table_descriptor(new_phys_page_table_address);
map_page(
phys_table_to_kernel_space(new_phys_page_table_address),
new_phys_page_table_address,
&raw mut TRANSLATIONTABLE_TTBR1,
NORMAL_MEM | WRITABLE | PXN | UXN,
)?;
Ok(resolve_table_addr(table.0[offset].address()) as *mut PageTable)
}
1 => Err(NovaError::Paging(
"Can't navigate table due to block mapping.",
)),
3 => Ok(resolve_table_addr(table.0[offset].address()) as *mut PageTable),
_ => unreachable!(),
}
}
/// Converts a physical table address and returns the corresponding virtual address depending on EL.
///
/// - `== EL0` -> panic
/// - `== EL1` -> 0xFFFFFF82XXXXXXXX
/// - `>= EL2` -> physical address
#[inline]
fn resolve_table_addr(physical_address: PhysAddr) -> VirtAddr {
let current_el = get_current_el();
if current_el >= 2 {
physical_address
} else if get_current_el() == 1 {
phys_table_to_kernel_space(physical_address)
} else {
panic!("Access to table entries is forbidden in EL0.")
}
}
/// Extracts the physical address out of an table entry.
#[inline]
fn phys_table_to_kernel_space(entry: usize) -> VirtAddr {
entry | TRANSLATION_TABLE_BASE_ADDR
}
-95
View File
@@ -1,95 +0,0 @@
use crate::aarch64::mmu::{PhysAddr, GRANULARITY, L2_BLOCK_BITMAP_WORDS, MAX_PAGE_COUNT};
use nova_error::NovaError;
struct PagingMap {
bitmap: [u64; MAX_PAGE_COUNT / 64],
}
static mut PAGING_BITMAP: PagingMap = PagingMap {
bitmap: [0; MAX_PAGE_COUNT / 64],
};
pub fn reserve_page() -> PhysAddr {
if let Some(address) = find_unallocated_page() {
let page = address / GRANULARITY;
let word_index = page / 64;
unsafe { PAGING_BITMAP.bitmap[word_index] |= 1 << (page % 64) };
return address;
}
panic!("Out of Memory!");
}
pub fn reserve_page_explicit(physical_address: usize) -> Result<PhysAddr, NovaError> {
let page = physical_address / GRANULARITY;
let word_index = page / 64;
if unsafe { PAGING_BITMAP.bitmap[word_index] } & (1 << (page % 64)) > 0 {
return Err(NovaError::Paging("Page PA already taken."));
}
unsafe { PAGING_BITMAP.bitmap[word_index] |= 1 << (page % 64) };
Ok(physical_address)
}
pub fn reserve_block() -> usize {
if let Some(start) = find_contiguous_free_bitmap_words(L2_BLOCK_BITMAP_WORDS) {
for j in 0..L2_BLOCK_BITMAP_WORDS {
unsafe { PAGING_BITMAP.bitmap[start + j] = u64::MAX };
}
return start * 64 * GRANULARITY;
}
panic!("Out of Memory!");
}
pub fn reserve_block_explicit(physical_address: usize) -> Result<(), NovaError> {
let page = physical_address / GRANULARITY;
for i in 0..L2_BLOCK_BITMAP_WORDS {
unsafe {
if PAGING_BITMAP.bitmap[(page / 64) + i] != 0 {
return Err(NovaError::Paging("Block PA already taken."));
}
};
}
for i in 0..L2_BLOCK_BITMAP_WORDS {
unsafe {
PAGING_BITMAP.bitmap[(page / 64) + i] = u64::MAX;
};
}
Ok(())
}
fn find_unallocated_page() -> Option<usize> {
for (i, entry) in unsafe { PAGING_BITMAP.bitmap }.iter().enumerate() {
if *entry != u64::MAX {
for offset in 0..64 {
if entry >> offset & 0b1 == 0 {
return Some((i * 64 + offset) * GRANULARITY);
}
}
}
}
None
}
fn find_contiguous_free_bitmap_words(required_words: usize) -> Option<usize> {
let mut run_start = 0;
let mut run_len = 0;
for (i, entry) in unsafe { PAGING_BITMAP.bitmap }.iter().enumerate() {
if *entry == 0 {
if run_len == 0 {
run_start = i;
}
run_len += 1;
if run_len == required_words {
return Some(run_start);
}
} else {
run_len = 0;
}
}
None
}
-2
View File
@@ -1,2 +0,0 @@
pub mod mmu;
pub mod registers;
-59
View File
@@ -1,59 +0,0 @@
use core::arch::asm;
pub mod daif {
use core::arch::asm;
#[inline(always)]
pub fn mask_all() {
unsafe { asm!("msr DAIFSet, #0xf", options(nomem, nostack)) }
}
#[inline(always)]
pub fn unmask_all() {
unsafe { asm!("msr DAIFClr, #0xf", options(nomem, nostack)) }
}
#[inline(always)]
pub fn mask_irq() {
unsafe { asm!("msr DAIFSet, #0x2", options(nomem, nostack)) }
}
#[inline(always)]
pub fn unmask_irq() {
unsafe { asm!("msr DAIFClr, #0x2", options(nomem, nostack)) }
}
}
#[macro_export]
macro_rules! psr {
($name:ident, $t:tt) => {
paste::item! {
pub fn [<read_ $name:lower>]() -> $t {
let buf: $t;
unsafe {
asm!(
concat!("mrs {0:x}, ", stringify!($name)),
out(reg) buf
);
}
buf
}
}
};
}
psr!(TCR_EL1, u64);
psr!(ID_AA64MMFR0_EL1, u64);
psr!(ESR_EL1, u32);
psr!(SPSR_EL1, u32);
psr!(ELR_EL1, u64);
psr!(SCTLR_EL1, u64);
pub fn read_exception_source_el() -> u32 {
read_spsr_el1() & 0b1111
}
-145
View File
@@ -1,145 +0,0 @@
use crate::{
aarch64::mmu::{
find_free_kerne_page_in_block, map_page, physical_mapping::reserve_page, PageTable,
TableEntry, VirtAddr, NORMAL_MEM, TRANSLATIONTABLE_TTBR0, TRANSLATIONTABLE_TTBR1, WRITABLE,
},
configuration::memory_mapping::{APPLICATION_TRANSLATION_TABLE_VA, EL0_STACK_TOP},
};
use alloc::vec::Vec;
use core::{arch::asm, mem, ptr::write_volatile};
use log::error;
use nova_error::NovaError;
use spin::Mutex;
struct AppManager {
apps: Option<Vec<Application>>,
}
impl AppManager {
const fn new() -> Self {
Self { apps: None }
}
}
unsafe impl Send for AppManager {}
pub struct Application {
pub table_ptr: *mut TableEntry,
pub start_addr: usize,
pub stack_pointer: usize,
}
impl Application {
pub fn new(start_addr: VirtAddr) -> Self {
let physical_addr = reserve_page();
let virtual_address =
find_free_kerne_page_in_block(APPLICATION_TRANSLATION_TABLE_VA).unwrap();
map_page(
virtual_address,
physical_addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1),
NORMAL_MEM | WRITABLE,
)
.unwrap();
// TODO: Temporary solution, while kernel and app share some memory regions
#[allow(static_mut_refs)]
unsafe {
let table = &mut *(virtual_address as *mut PageTable);
table.0 = TRANSLATIONTABLE_TTBR0.0;
}
Self {
table_ptr: physical_addr as *mut TableEntry,
start_addr,
stack_pointer: EL0_STACK_TOP,
}
}
pub unsafe fn configure_registers(&self) {
asm!("msr ELR_EL1, {}", in(reg) self.start_addr);
asm!("msr SPSR_EL1, {0:x}", in(reg) 0);
asm!("msr SP_EL0, {0:x}", in(reg) self.stack_pointer);
asm!("msr TTBR0_EL1, {}", in(reg) self.table_ptr as usize);
}
/// Starts an application.
///
/// `ELR_EL1` -> Exception Link Register (starting virtual address)
/// `SPSR_EL1` -> Saved Program State Register (settings for `eret` behaviour)
/// `SP_EL0` -> Stack Pointer Register (virtual_address of stack Pointer)
/// `TTBR0_EL1` -> Translation Table base Register Register
pub fn start(&mut self, args: Vec<&str>) {
let size = args.len();
let argv = self.construct_inital_stack(args);
unsafe {
self.configure_registers();
asm!("", in("x0") size, in("x1") argv);
asm!("eret");
}
}
/// Initializes the stack based on the System V ABI
fn construct_inital_stack(&mut self, args: Vec<&str>) -> usize {
let size = args.len();
let mut arg_addresses = Vec::with_capacity(size);
// Write strings into stack
for value in args {
self.stack_pointer -= value.len() * mem::size_of::<u8>();
let pointer = self.stack_pointer as *mut u8;
unsafe { core::ptr::copy(value.as_ptr(), pointer, value.len()) };
arg_addresses.push(pointer);
}
self.stack_pointer = align_down(self.stack_pointer, 16);
// TODO: Auxiliry vector entry
// TODO: Environment pointers
let argv = self.stack_pointer;
// Write argument pointers into stack
for addr in arg_addresses {
unsafe { write_volatile(self.stack_pointer as *mut *const u8, addr) };
self.stack_pointer -= mem::size_of::<*const u8>();
}
argv
}
}
fn align_down(sp: usize, align: usize) -> usize {
sp & !(align - 1)
}
static APP_MANAGER: Mutex<AppManager> = Mutex::new(AppManager::new());
pub fn initialize_app_manager() {
let mut guard = APP_MANAGER.lock();
guard.apps = Some(Vec::new());
}
pub fn add_app(app: Application) -> Result<(), NovaError> {
if let Some(app_list) = APP_MANAGER.lock().apps.as_mut() {
app_list.push(app);
Ok(())
} else {
Err(NovaError::General("AppManager not initalized."))
}
}
pub fn start_app(index: usize, args: Vec<&str>) -> Result<(), NovaError> {
if let Some(app) = APP_MANAGER
.lock()
.apps
.as_mut()
.and_then(|am| am.get_mut(index))
{
app.start(args);
unreachable!()
} else {
error!("Unable to start app due to invalid App ID.");
Err(NovaError::General("Invalid app id."))
}
}
-80
View File
@@ -1,80 +0,0 @@
.section .text.config
.align 4
.global el2_to_el1
el2_to_el1:
mov x0, #(1 << 31)
msr HCR_EL2, x0
// Set SPSR_EL2: return to EL1h
mov x0, #(0b0101)
msr SPSR_EL2, x0
// Set return address to kernel_main
adrp x0, KERNEL_VIRTUAL_MEM_SPACE
ldr x1, [x0, :lo12:KERNEL_VIRTUAL_MEM_SPACE]
adrp x0, kernel_main
add x0, x0, :lo12:kernel_main
orr x0, x0, x1
msr ELR_EL2, x0
// Set SP_EL1 to stack base
adrp x0, EL1_STACK_TOP
ldr x1, [x0, :lo12:EL1_STACK_TOP]
msr SP_EL1, x1
// Set VBAR_EL1 to vector table
adrp x0, vector_table
add x0, x0, :lo12:vector_table
msr VBAR_EL1, x0
isb
adrp x0, SCTLR_EL1_CONF
ldr x1, [x0, :lo12:SCTLR_EL1_CONF]
msr SCTLR_EL1, x1
isb
// SIMD should not be trapped
mrs x0, CPACR_EL1
mov x1, #(0b11<<20)
orr x0,x0, x1
msr CPACR_EL1,x0
isb
// Return to EL1
eret
.section .text.config
.align 4
.global configure_mmu_el1
configure_mmu_el1:
// Configure MMU
adrp x0, TCR_EL1_CONF
ldr x1, [x0, :lo12:TCR_EL1_CONF]
msr TCR_EL1, x1
isb
// MAIR0: Normal Mem.
// MAIR1: Device Mem.
mov x0, #0x04FF
msr MAIR_EL1, x0
isb
// Configure translation table
adrp x0, TRANSLATIONTABLE_TTBR0
add x1, x0, :lo12:TRANSLATIONTABLE_TTBR0
msr TTBR0_EL1, x1
adrp x0, TRANSLATIONTABLE_TTBR1
add x1, x0, :lo12:TRANSLATIONTABLE_TTBR1
msr TTBR1_EL1, x1
tlbi vmalle1
dsb ish
isb
ret
+8 -27
View File
@@ -1,34 +1,15 @@
const SCTLR_EL1_MMU_ENABLED: u64 = 1; //M
const SCTLR_EL1_DATA_CACHE_DISABLED: u64 = 0 << 2; //C
const SCTLR_EL1_INSTRUCTION_CACHE_DISABLED: u64 = 0 << 12; //I
const SCTLR_EL1_LITTLE_ENDIAN_EL0: u64 = 0 << 24; //E0E
const SCTLR_EL1_LITTLE_ENDIAN_EL1: u64 = 0 << 25; //EE
const SCTLR_EL1_SPAN: u64 = 1 << 23; //SPAN
static SCTLR_EL1_MMU_DISABLED: u64 = 0 << 0; //M
static SCTLR_EL1_DATA_CACHE_DISABLED: u64 = 0 << 2; //C
static SCTLR_EL1_INSTRUCTION_CACHE_DISABLED: u64 = 0 << 12; //I
static SCTLR_EL1_LITTLE_ENDIAN_EL0: u64 = 0 << 24; //E0E
static SCTLR_EL1_LITTLE_ENDIAN_EL1: u64 = 0 << 25; //EE
#[allow(clippy::identity_op)]
const SCTLR_EL1_RES: u64 = (0 << 6) | (1 << 11) | (0 << 17) | (1 << 20) | (1 << 22); //Res0 & Res1
static SCTLR_EL1_RES: u64 = (0 << 6) | (1 << 11) | (0 << 17) | (1 << 20) | (1 << 22); //Res0 & Res1
#[no_mangle]
pub static SCTLR_EL1_CONF: u64 = SCTLR_EL1_MMU_ENABLED
pub static SCTLR_EL1_CONF: u64 = SCTLR_EL1_MMU_DISABLED
| SCTLR_EL1_DATA_CACHE_DISABLED
| SCTLR_EL1_INSTRUCTION_CACHE_DISABLED
| SCTLR_EL1_LITTLE_ENDIAN_EL0
| SCTLR_EL1_LITTLE_ENDIAN_EL1
| SCTLR_EL1_RES
| SCTLR_EL1_SPAN;
const TG0: u64 = 0b00 << 14; // 4KB granularity EL0
const T0SZ: u64 = 25; // 25 Bits of TTBR select -> 39 Bits of VA
const SH0: u64 = 0b11 << 12; // Inner shareable
const TG1: u64 = 0b10 << 30; // 4KB granularity EL1
const T1SZ: u64 = 25 << 16; // 25 Bits of TTBR select -> 39 Bits of VA
const SH1: u64 = 0b11 << 28; // Inner sharable
const IPS: u64 = 0b000 << 32; // 32 bits of PA space -> up to 4GiB
const AS: u64 = 0b1 << 36; // configure an ASID size of 16 bits
#[no_mangle]
pub static TCR_EL1_CONF: u64 = IPS | TG0 | TG1 | T0SZ | T1SZ | SH0 | SH1 | AS;
pub mod memory_mapping;
| SCTLR_EL1_RES;
-127
View File
@@ -1,127 +0,0 @@
use crate::{
aarch64::mmu::{
alloc_block_l2_explicit, allocate_memory, map_page, physical_mapping::reserve_page,
reserve_range, PhysAddr, PhysSource, VirtAddr, DEVICE_MEM, EL0_ACCESSIBLE, GRANULARITY,
KERNEL_VIRTUAL_MEM_SPACE, LEVEL1_BLOCK_SIZE, LEVEL2_BLOCK_SIZE, NORMAL_MEM, PXN, READ_ONLY,
STACK_START_ADDR, TRANSLATIONTABLE_TTBR0, TRANSLATIONTABLE_TTBR1, UXN, WRITABLE,
},
PERIPHERAL_BASE,
};
#[no_mangle]
static EL1_STACK_TOP: usize = STACK_START_ADDR | KERNEL_VIRTUAL_MEM_SPACE;
const EL1_STACK_SIZE: usize = LEVEL2_BLOCK_SIZE * 2;
#[no_mangle]
pub static EL0_STACK_TOP: usize = STACK_START_ADDR;
pub const EL0_STACK_SIZE: usize = LEVEL2_BLOCK_SIZE * 2;
pub const MAILBOX_VIRTUAL_ADDRESS: VirtAddr = 0xFFFF_FF81_FFFF_E000;
pub static mut MAILBOX_PHYSICAL_ADDRESS: Option<PhysAddr> = None;
// TODO: Currently limited to 512 applications, more than enough, but has to be kept
// in mind
pub const APPLICATION_TRANSLATION_TABLE_VA: VirtAddr = 0xFFFF_FF81_FE00_0000;
extern "C" {
static __text_end: u64;
static __share_end: u64;
static __kernel_end: u64;
}
pub fn initialize_mmu_translation_tables() {
let text_end = unsafe { &__text_end } as *const _ as usize;
let shared_segment_end = unsafe { &__share_end } as *const _ as usize;
let kernel_end = unsafe { &__kernel_end } as *const _ as usize;
reserve_range(0x0, kernel_end).unwrap();
for addr in (0..text_end).step_by(GRANULARITY) {
map_page(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | READ_ONLY | NORMAL_MEM,
)
.unwrap();
}
for addr in (0..text_end).step_by(GRANULARITY) {
map_page(
addr | KERNEL_VIRTUAL_MEM_SPACE,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1),
READ_ONLY | NORMAL_MEM,
)
.unwrap();
}
for addr in (text_end..shared_segment_end).step_by(GRANULARITY) {
map_page(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | WRITABLE | NORMAL_MEM,
)
.unwrap();
}
for addr in (text_end..shared_segment_end).step_by(GRANULARITY) {
map_page(
addr | KERNEL_VIRTUAL_MEM_SPACE,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1),
EL0_ACCESSIBLE | WRITABLE | NORMAL_MEM,
)
.unwrap();
}
for addr in (PERIPHERAL_BASE..LEVEL1_BLOCK_SIZE).step_by(LEVEL2_BLOCK_SIZE) {
alloc_block_l2_explicit(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | WRITABLE | UXN | PXN | DEVICE_MEM,
)
.unwrap();
}
// Frame Buffer memory range
allocate_memory(
0x3c100000,
1080 * 1920 * 4,
PhysSource::Explicit(0x3c100000),
NORMAL_MEM | PXN | UXN | WRITABLE | EL0_ACCESSIBLE,
)
.unwrap();
// Allocate EL1 stack
allocate_memory(
EL1_STACK_TOP - EL1_STACK_SIZE + 0x10,
EL1_STACK_SIZE,
PhysSource::Any,
WRITABLE | NORMAL_MEM,
)
.unwrap();
// Allocate EL0 stack
allocate_memory(
EL0_STACK_TOP - EL0_STACK_SIZE + 0x10,
EL0_STACK_SIZE,
PhysSource::Any,
WRITABLE | EL0_ACCESSIBLE | NORMAL_MEM,
)
.unwrap();
// Allocate Mailbox buffer
{
let addr = reserve_page();
unsafe { MAILBOX_PHYSICAL_ADDRESS = Some(addr) };
allocate_memory(
MAILBOX_VIRTUAL_ADDRESS,
GRANULARITY,
PhysSource::Explicit(addr),
WRITABLE | NORMAL_MEM,
)
.unwrap();
}
}
-92
View File
@@ -1,92 +0,0 @@
use alloc::string::String;
use crate::{
application_manager::start_app,
interrupt_handlers::irq::{register_interrupt_handler, IRQSource},
peripherals::uart::read_uart_data,
pi3::mailbox::read_soc_temp,
print, println,
};
pub static mut TERMINAL: Option<Terminal> = None;
pub struct Terminal {
input: String,
}
impl Default for Terminal {
fn default() -> Self {
Self::new()
}
}
impl Terminal {
pub fn new() -> Self {
Self {
input: String::new(),
}
}
fn flush(&mut self) {
print!("\n> {}", self.input);
}
fn exec(&mut self) {
print!("\n");
let val = self.input.clone();
self.input.clear();
let mut parts = val.split(" ");
match parts.next().unwrap() {
"temp" => {
println!("{}", read_soc_temp([0]).unwrap()[1]);
}
"app" => {
if let Some(app_id) = parts.next().and_then(|a| a.parse::<usize>().ok()) {
let args = parts.collect();
let _ = start_app(app_id, args);
} else {
println!("App ID not set.");
}
}
_ => {
println!("Unknown command: \"{}\"", self.input);
}
}
self.input.clear();
}
}
pub fn init_terminal() {
unsafe { TERMINAL = Some(Terminal::new()) };
register_terminal_interrupt_handler();
}
fn terminal_uart_rx_interrupt_handler() {
let input = read_uart_data();
#[allow(static_mut_refs)]
if let Some(term) = unsafe { TERMINAL.as_mut() } {
match input {
'\r' => {
term.exec();
term.flush();
}
_ => {
term.input.push(input);
print!("{}", input);
}
}
}
}
pub fn flush_terminal() {
#[allow(static_mut_refs)]
if let Some(term) = unsafe { TERMINAL.as_mut() } {
term.flush();
}
}
fn register_terminal_interrupt_handler() {
register_interrupt_handler(IRQSource::UartInt, terminal_uart_rx_interrupt_handler);
}
+72 -80
View File
@@ -4,8 +4,7 @@ mod bitmaps;
use bitmaps::BASIC_LEGACY;
use crate::pi3::mailbox::{read_mailbox, write_mailbox};
use log::error;
use crate::mailbox::{read_mailbox, write_mailbox};
#[repr(align(16))]
struct Mailbox([u32; 36]);
@@ -17,13 +16,12 @@ const SET_PIXEL_ORDER: u32 = 0x0004_8006;
const GET_PITCH: u32 = 0x000_40008;
const SET_FB_OFFSET: u32 = 0x0004_8009;
#[allow(dead_code)]
pub struct FrameBuffer {
pixel_depth: u32, // Bits per pixel
pitch: u32, // Pixel per row
rows: u32, // Rows
pub start_addr: *mut u32,
pub size: u32, //Bytes
start_addr: *mut u32,
size: u32, //Bytes
}
pub const RED: u32 = 0x00FF0000;
@@ -33,11 +31,76 @@ pub const ORANGE: u32 = 0x00FFA500;
pub const YELLOW: u32 = 0x00FFFF00;
impl FrameBuffer {
pub fn new() -> Self {
let mut mailbox = Mailbox([0; 36]);
mailbox.0[0] = 35 * 4;
mailbox.0[1] = 0;
mailbox.0[2] = SET_PHYSICAL_DISPLAY_WH;
mailbox.0[3] = 8;
mailbox.0[4] = 8;
mailbox.0[5] = 1920;
mailbox.0[6] = 1080;
mailbox.0[7] = SET_VIRTUAL_DISPLAY_WH;
mailbox.0[8] = 8;
mailbox.0[9] = 8;
mailbox.0[10] = 1920;
mailbox.0[11] = 1080;
mailbox.0[12] = SET_PIXEL_DEPTH;
mailbox.0[13] = 4;
mailbox.0[14] = 4;
mailbox.0[15] = 32; // 32 bit per pixel
mailbox.0[16] = SET_PIXEL_ORDER;
mailbox.0[17] = 4;
mailbox.0[18] = 4;
mailbox.0[19] = 0x0; // RGB
mailbox.0[20] = SET_FB_OFFSET;
mailbox.0[21] = 8;
mailbox.0[22] = 8;
mailbox.0[23] = 0; // X in pixels
mailbox.0[24] = 0; // Y in pixels
mailbox.0[25] = ALLOCATE_BUFFER;
mailbox.0[26] = 8;
mailbox.0[27] = 4;
mailbox.0[28] = 4096; // Alignment
mailbox.0[29] = 0;
mailbox.0[30] = GET_PITCH;
mailbox.0[31] = 4;
mailbox.0[32] = 0;
mailbox.0[33] = 0;
mailbox.0[34] = 0; // End tag
// TODO: validate responses
let addr = core::ptr::addr_of!(mailbox.0[0]) as u32;
write_mailbox(8, addr);
let _ = read_mailbox(8);
if mailbox.0[1] == 0 {
println!("Failed");
}
mailbox.0[28] &= 0x3FFFFFFF;
Self {
pixel_depth: mailbox.0[15],
pitch: mailbox.0[33] / (mailbox.0[15] / 8),
rows: mailbox.0[29] / mailbox.0[33],
start_addr: mailbox.0[28] as *mut u32,
size: mailbox.0[29],
}
}
pub fn draw_pixel(&self, x: u32, y: u32, color: u32) {
let offset = x + y * self.pitch;
if x >= self.pitch || y >= self.rows {
return;
}
unsafe {
write_volatile(self.start_addr.add(offset as usize), color);
}
@@ -46,7 +109,6 @@ impl FrameBuffer {
/*Bresenham's line algorithm
TODO: check if its possible to optimize y1==y2 case (ARM neon?)
*/
#[allow(clippy::collapsible_else_if)]
pub fn draw_line(&self, x1: u32, y1: u32, x2: u32, y2: u32, color: u32) {
if x1 == x2 {
for y in y1..=y2 {
@@ -156,7 +218,7 @@ impl FrameBuffer {
}
fn draw_ascii(&self, x: u32, y: u32, char: usize, scale: u32, color: u32) {
for (y_offset, row) in BASIC_LEGACY[char].iter().enumerate() {
for (y_offset, row) in (&BASIC_LEGACY[char]).iter().enumerate() {
for bit in 0..8 {
match row & (1 << bit) {
0 => {}
@@ -179,73 +241,3 @@ impl FrameBuffer {
}
}
}
impl Default for FrameBuffer {
fn default() -> Self {
let mut mailbox = Mailbox([0; 36]);
mailbox.0[0] = 35 * 4;
mailbox.0[1] = 0;
mailbox.0[2] = SET_PHYSICAL_DISPLAY_WH;
mailbox.0[3] = 8;
mailbox.0[4] = 8;
mailbox.0[5] = 1920;
mailbox.0[6] = 1080;
mailbox.0[7] = SET_VIRTUAL_DISPLAY_WH;
mailbox.0[8] = 8;
mailbox.0[9] = 8;
mailbox.0[10] = 1920;
mailbox.0[11] = 1080;
mailbox.0[12] = SET_PIXEL_DEPTH;
mailbox.0[13] = 4;
mailbox.0[14] = 4;
mailbox.0[15] = 32; // 32 bit per pixel
mailbox.0[16] = SET_PIXEL_ORDER;
mailbox.0[17] = 4;
mailbox.0[18] = 4;
mailbox.0[19] = 0x0; // RGB
mailbox.0[20] = SET_FB_OFFSET;
mailbox.0[21] = 8;
mailbox.0[22] = 8;
mailbox.0[23] = 0; // X in pixels
mailbox.0[24] = 0; // Y in pixels
mailbox.0[25] = ALLOCATE_BUFFER;
mailbox.0[26] = 8;
mailbox.0[27] = 4;
mailbox.0[28] = 4096; // Alignment
mailbox.0[29] = 0;
mailbox.0[30] = GET_PITCH;
mailbox.0[31] = 4;
mailbox.0[32] = 0;
mailbox.0[33] = 0;
mailbox.0[34] = 0; // End tag
// TODO: validate responses
let addr = core::ptr::addr_of!(mailbox.0[0]) as u32;
write_mailbox(8, addr);
let _ = read_mailbox(8);
if mailbox.0[1] == 0 {
error!("Mailbox request was not processed!");
}
mailbox.0[28] &= 0x3FFFFFFF;
Self {
pixel_depth: mailbox.0[15],
pitch: mailbox.0[33] / (mailbox.0[15] / 8),
rows: mailbox.0[29] / mailbox.0[33],
start_addr: mailbox.0[28] as *mut u32,
size: mailbox.0[29],
}
}
}
-83
View File
@@ -1,83 +0,0 @@
use core::arch::asm;
use crate::{
aarch64::registers::{daif::mask_all, read_esr_el1, read_exception_source_el},
get_current_el,
};
use log::debug;
const INTERRUPT_BASE: u32 = 0x3F00_B000;
const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204;
const ENABLE_IRQ_BASE: u32 = INTERRUPT_BASE + 0x210;
const DISABLE_IRQ_BASE: u32 = INTERRUPT_BASE + 0x21C;
const GPIO_PENDING_BIT_OFFSET: u64 = 0b1111 << 49;
#[repr(C)]
pub struct TrapFrame {
pub x0: u64,
pub x1: u64,
pub x2: u64,
pub x3: u64,
pub x4: u64,
pub x5: u64,
pub x6: u64,
pub x7: u64,
pub x8: u64,
pub x9: u64,
pub x10: u64,
pub x11: u64,
pub x12: u64,
pub x13: u64,
pub x14: u64,
pub x15: u64,
pub x16: u64,
pub x17: u64,
pub x18: u64,
pub x29: u64,
pub x30: u64,
}
/// Representation of the ESR_ELx registers
///
/// Reference: D1.10.4
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
struct EsrElX {
ec: u32,
il: u32,
iss: u32,
}
impl From<u32> for EsrElX {
fn from(value: u32) -> Self {
Self {
ec: value >> 26,
il: (value >> 25) & 0b1,
iss: value & 0x1FFFFFF,
}
}
}
pub mod irq;
pub mod synchronous;
#[no_mangle]
unsafe extern "C" fn rust_synchronous_interrupt_no_el_change() {
mask_all();
let source_el = read_exception_source_el() >> 2;
debug!("--------Sync Exception in EL{}--------", source_el);
debug!("No EL change");
debug!("Current EL: {}", get_current_el());
debug!("{:?}", EsrElX::from(read_esr_el1()));
debug!("Return register address: {:#x}", read_esr_el1());
debug!("-------------------------------------");
}
fn set_return_to_kernel_loop() {
unsafe {
asm!("ldr x0, =kernel_loop", "msr ELR_EL1, x0");
asm!("mov x0, #(0b0101)", "msr SPSR_EL1, x0");
}
}
-152
View File
@@ -1,152 +0,0 @@
use crate::aarch64::registers::read_esr_el1;
use crate::{
aarch64::registers::{
daif::{mask_all, unmask_irq},
read_exception_source_el,
},
get_current_el,
interrupt_handlers::{
DISABLE_IRQ_BASE, ENABLE_IRQ_BASE, GPIO_PENDING_BIT_OFFSET, IRQ_PENDING_BASE,
},
peripherals::{
gpio::{read_gpio_event_detect_status, reset_gpio_event_detect_status},
uart::clear_uart_interrupt_state,
},
read_address, write_address,
};
use alloc::vec::Vec;
use log::{debug, info};
struct InterruptHandlers {
source: IRQSource,
function: fn(),
}
// TODO: replace with hashmap and check for better alternatives for option
static mut INTERRUPT_HANDLERS: Option<Vec<InterruptHandlers>> = None;
#[derive(Clone)]
#[repr(u32)]
pub enum IRQSource {
AuxInt = 29,
I2cSpiSlvInt = 44,
Pwa0 = 45,
Pwa1 = 46,
Smi = 48,
GpioInt0 = 49,
GpioInt1 = 50,
GpioInt2 = 51,
GpioInt3 = 52,
I2cInt = 53,
SpiInt = 54,
PcmInt = 55,
UartInt = 57,
}
#[inline(always)]
pub fn initialize_interrupt_handler() {
unsafe { INTERRUPT_HANDLERS = Some(Vec::new()) };
}
pub fn register_interrupt_handler(source: IRQSource, function: fn()) {
if let Some(handler_vec) = unsafe { &mut *core::ptr::addr_of_mut!(INTERRUPT_HANDLERS) } {
handler_vec.push(InterruptHandlers { source, function });
}
}
#[no_mangle]
unsafe extern "C" fn rust_irq_handler() {
mask_all();
let pending_irqs = get_irq_pending_sources();
if pending_irqs & GPIO_PENDING_BIT_OFFSET != 0 {
handle_gpio_interrupt();
let source_el = read_exception_source_el() >> 2;
debug!("Source EL: {}", source_el);
debug!("Current EL: {}", get_current_el());
debug!("Return register address: {:#x}", read_esr_el1());
}
if let Some(handler_vec) = unsafe { &*core::ptr::addr_of_mut!(INTERRUPT_HANDLERS) } {
for handler in handler_vec {
if (pending_irqs & (1 << (handler.source.clone() as u32))) != 0 {
(handler.function)();
clear_interrupt_for_source(handler.source.clone());
}
}
}
}
fn handle_gpio_interrupt() {
debug!("GPIO interrupt triggered");
for i in 0..=53u32 {
let val = read_gpio_event_detect_status(i);
if val {
#[allow(clippy::single_match)]
match i {
26 => {
info!("Button Pressed");
}
_ => {}
}
// Reset GPIO Interrupt handler by writing a 1
reset_gpio_event_detect_status(i);
}
}
unmask_irq();
}
/// Enables IRQ Source
pub fn enable_irq_source(state: IRQSource) {
let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
let current = unsafe { read_address(register) };
let mask = 0b1 << register_offset;
let new_val = current | mask;
unsafe { write_address(register, new_val) };
}
/// Disable IRQ Source
pub fn disable_irq_source(state: IRQSource) {
let nr = state as u32;
let register = DISABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
let current = unsafe { read_address(register) };
let mask = 0b1 << register_offset;
let new_val = current | mask;
unsafe { write_address(register, new_val) };
}
/// Read current IRQ Source status
pub fn read_irq_source_status(state: IRQSource) -> u32 {
let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
(unsafe { read_address(register) } >> register_offset) & 0b1
}
/// Status if a IRQ Source is pending
pub fn is_irq_source_pending(state: IRQSource) -> bool {
let nr = state as u32;
let register = IRQ_PENDING_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
((unsafe { read_address(register) } >> register_offset) & 0b1) != 0
}
/// Status if a IRQ Source is pending
pub fn get_irq_pending_sources() -> u64 {
let mut pending = unsafe { read_address(IRQ_PENDING_BASE + 4) as u64 } << 32;
pending |= unsafe { read_address(IRQ_PENDING_BASE) as u64 };
pending
}
fn clear_interrupt_for_source(source: IRQSource) {
match source {
IRQSource::UartInt => clear_uart_interrupt_state(),
_ => {
todo!()
}
}
}
-116
View File
@@ -1,116 +0,0 @@
use crate::{
aarch64::registers::{daif::mask_all, read_elr_el1, read_esr_el1, read_exception_source_el},
get_current_el,
interrupt_handlers::{set_return_to_kernel_loop, EsrElX, TrapFrame},
pi3::mailbox,
};
use log::{debug, error, warn};
/// Synchronous Exception Handler
///
/// Source is a lower Exception level, where the implemented level
/// immediately lower than the target level is using
/// AArch64.
#[no_mangle]
unsafe extern "C" fn rust_synchronous_interrupt_imm_lower_aarch64(frame: &mut TrapFrame) -> usize {
mask_all();
let esr: EsrElX = EsrElX::from(read_esr_el1());
debug!("Synchronous interrupt from lower EL triggered");
log_sync_exception();
match esr.ec {
0b100100 => {
error!("Data Abort from a lower Exception level");
error!("Cause: {}", decode_data_abort(esr.iss as usize));
}
0b010101 => {
debug!("SVC instruction execution in AArch64");
return handle_svc(frame);
}
0b100010 => {
error!("PC alignment fault.");
}
_ => {
error!("Synchronous interrupt: Unknown Error Code: {:b}", esr.ec);
}
}
warn!("UnhandledException -> Returning to kernel...");
set_return_to_kernel_loop();
0
}
fn decode_data_abort(iss: usize) -> &'static str {
match iss & 0b111111 {
0b000000 => "Address size fault, level 0",
0b000001 => "Address size fault, level 1",
0b000010 => "Address size fault, level 2",
0b000011 => "Address size fault, level 3",
0b000100 => "Translation fault, level 0",
0b000101 => "Translation fault, level 1",
0b000110 => "Translation fault, level 2",
0b000111 => "Translation fault, level 3",
0b001001 => "Access flag fault, level 1",
0b001010 => "Access flag fault, level 2",
0b001011 => "Access flag fault, level 3",
0b001101 => "Permission fault, level 1",
0b001110 => "Permission fault, level 2",
0b001111 => "Permission fault, level 3",
0b010000 => "Synchronous External abort, not on translation table walk",
0b011000 => {
"Synchronous parity or ECC error on memory access, not on translation table walk"
}
0b010100 => "Synchronous External abort, on translation table walk, level 0",
0b010101 => "Synchronous External abort, on translation table walk, level 1",
0b010110 => "Synchronous External abort, on translation table walk, level 2",
0b010111 => "Synchronous External abort, on translation table walk, level 3",
0b011100 => "Synchronous parity or ECC error on translation table walk, level 0",
0b011101 => "Synchronous parity or ECC error on translation table walk, level 1",
0b011110 => "Synchronous parity or ECC error on translation table walk, level 2",
0b011111 => "Synchronous parity or ECC error on translation table walk, level 3",
0b100001 => "Alignment fault",
0b110000 => "TLB conflict abort",
0b110001 => "Unsupported atomic hardware update fault",
0b110100 => "IMPLEMENTATION DEFINED fault (Lockdown)",
0b110101 => "IMPLEMENTATION DEFINED fault (Unsupported Exclusive or Atomic access)",
0b111101 => "Section Domain Fault",
0b111110 => "Page Domain Fault",
_ => "Reserved / Unknown",
}
}
fn handle_svc(frame: &mut TrapFrame) -> usize {
match frame.x8 {
0 => {
debug!("Program exited!");
set_return_to_kernel_loop();
0
}
67 => {
let response = mailbox::read_soc_temp([0]).unwrap();
response[1] as usize
}
_ => 0,
}
}
fn log_sync_exception() {
let source_el = read_exception_source_el() >> 2;
debug!("--------Sync Exception in EL{}--------", source_el);
debug!("Exception escalated to EL {}", get_current_el());
debug!("Current EL: {}", get_current_el());
let esr: EsrElX = EsrElX::from(read_esr_el1());
debug!("{:?}", esr);
debug!("Return address: {:#x}", read_elr_el1());
debug!("-------------------------------------");
}
+162
View File
@@ -0,0 +1,162 @@
use core::{
arch::asm,
sync::atomic::{compiler_fence, Ordering},
};
use crate::{
mmio_read, mmio_write,
peripherals::gpio::{blink_gpio, SpecificGpio},
print,
timer::sleep_s,
};
const INTERRUPT_BASE: u32 = 0x3F00_B000;
const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204;
const ENABLE_IRQ_BASE: u32 = INTERRUPT_BASE + 0x210;
const DISABLE_IRQ_BASE: u32 = INTERRUPT_BASE + 0x21C;
// GPIO
const GPEDS_BASE: u32 = 0x3F20_0040;
#[repr(u32)]
pub enum IRQState {
AuxInt = 29,
I2cSpiSlvInt = 44,
Pwa0 = 45,
Pwa1 = 46,
Smi = 48,
GpioInt0 = 49,
GpioInt1 = 50,
GpioInt2 = 51,
GpioInt3 = 52,
I2cInt = 53,
SpiInt = 54,
PcmInt = 55,
UartInt = 57,
}
#[no_mangle]
unsafe extern "C" fn irq_handler() {
handle_gpio_interrupt();
}
#[no_mangle]
unsafe extern "C" fn synchronous_interrupt() {
loop {
println!("Sync Exception");
blink_gpio(SpecificGpio::OnboardLed as u8, 100);
esr_uart_dump();
sleep_s(200);
}
}
fn esr_uart_dump() {
let esr: u32;
unsafe {
asm!(
"mrs {esr}, ESR_EL1",
esr = out(reg) esr
);
}
for i in (0..32).rev() {
if ((esr >> i) & 1) == 0 {
print!("0");
} else {
print!("1");
}
if i % 4 == 0 && i > 0 {
print!("_");
}
if i == 26 || i == 25 || i == 0 {
print!("\n\r");
}
}
}
fn handle_gpio_interrupt() {
println!("Interrupt");
for i in 0..=53u32 {
let val = read_gpio_event_detect_status(i);
if val {
match i {
26 => print!("Button Pressed"),
_ => {}
}
// Reset GPIO Interrupt handler by writing a 1
reset_gpio_event_detect_status(i);
}
}
enable_irq();
}
/// Get current interrupt status of a GPIO pin
pub fn read_gpio_event_detect_status(id: u32) -> bool {
let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32;
let val = mmio_read(register) >> register_offset;
(val & 0b1) != 0
}
/// Resets current interrupt status of a GPIO pin
pub fn reset_gpio_event_detect_status(id: u32) {
let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32;
mmio_write(register, 0b1 << register_offset);
compiler_fence(Ordering::SeqCst);
}
/// Enables IRQ Source
pub fn enable_irq_source(state: IRQState) {
let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
let current = mmio_read(register);
let mask = 0b1 << register_offset;
let new_val = current | mask;
mmio_write(register, new_val);
}
/// Disable IRQ Source
pub fn disable_irq_source(state: IRQState) {
let nr = state as u32;
let register = DISABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
let current = mmio_read(register);
let mask = 0b1 << register_offset;
let new_val = current | mask;
mmio_write(register, new_val);
}
/// Read current IRQ Source status
pub fn read_irq_source_status(state: IRQState) -> u32 {
let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
(mmio_read(register) >> register_offset) & 0b1
}
/// Status if a IRQ Source is enabled
pub fn read_irq_pending(state: IRQState) -> bool {
let nr = state as u32;
let register = IRQ_PENDING_BASE + 4 * (nr / 32);
let register_offset = nr % 32;
((mmio_read(register) >> register_offset) & 0b1) != 0
}
/// Clears the IRQ DAIF Mask
///
/// Enables IRQ interrupts
pub fn enable_irq() {
unsafe { asm!("msr DAIFClr, #0x2") }
}
/// Clears the IRQ DAIF Mask
///
/// Disable IRQ interrupts
pub fn disable_irq() {
unsafe { asm!("msr DAIFSet, #0x2") }
}
+29 -83
View File
@@ -1,117 +1,63 @@
#![no_std]
#![allow(clippy::missing_safety_doc)]
extern crate alloc;
use core::{
arch::asm,
panic::PanicInfo,
ptr::{read_volatile, write_volatile},
};
use log::LevelFilter;
use log::{Level, Metadata, Record};
use heap::Heap;
use crate::{
aarch64::mmu::{
allocate_memory, PhysSource, KERNEL_VIRTUAL_MEM_SPACE, LEVEL2_BLOCK_SIZE, NORMAL_MEM, UXN,
WRITABLE,
},
application_manager::initialize_app_manager,
console::{flush_terminal, init_terminal},
interrupt_handlers::irq::initialize_interrupt_handler,
pi3::timer::sleep_s,
};
static LOGGER: UartLogger = UartLogger;
static PERIPHERAL_BASE: usize = 0x3F00_0000;
unsafe extern "C" {
unsafe static mut __kernel_end: u8;
unsafe static mut __heap_start: u8;
unsafe static mut __heap_end: u8;
}
#[global_allocator]
pub static mut GLOBAL_ALLOCATOR: Heap = Heap::empty();
pub unsafe fn initialize_kernel_heap() {
let start = core::ptr::addr_of_mut!(__kernel_end) as usize | KERNEL_VIRTUAL_MEM_SPACE;
let size = LEVEL2_BLOCK_SIZE * 2;
pub unsafe fn init_heap() {
let start = core::ptr::addr_of_mut!(__heap_start) as usize;
let end = core::ptr::addr_of_mut!(__heap_end) as usize;
allocate_memory(start, size, PhysSource::Any, NORMAL_MEM | UXN | WRITABLE).unwrap();
let heap = core::ptr::addr_of_mut!(GLOBAL_ALLOCATOR);
(*heap).init(start, start + size);
GLOBAL_ALLOCATOR.init(start, end);
}
#[panic_handler]
fn panic(_panic: &PanicInfo) -> ! {
loop {
println!("Panic: {}", _panic.message());
sleep_s(1);
println!("Panic");
}
}
#[macro_export]
macro_rules! print {
() => {};
($($arg:tt)*) => {
$crate::peripherals::uart::_print(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! println {
() => {};
($($arg:tt)*) => {
print!($($arg)*);
print!("\r\n");
};
}
pub mod peripherals;
pub mod aarch64;
pub mod configuration;
pub mod framebuffer;
pub mod interrupt_handlers;
pub mod irq_interrupt;
pub mod mailbox;
pub mod timer;
pub mod application_manager;
pub mod console;
pub mod pi3;
#[inline(always)]
pub unsafe fn read_address(address: u32) -> u32 {
pub fn mmio_read(address: u32) -> u32 {
unsafe { read_volatile(address as *const u32) }
}
#[inline(always)]
pub unsafe fn write_address(address: u32, data: u32) {
pub fn mmio_write(address: u32, data: u32) {
unsafe { write_volatile(address as *mut u32, data) }
}
pub fn get_current_el() -> u64 {
let el: u64;
unsafe {
asm!(
"mrs {el}, CurrentEL",
el = out(reg) el,
options(nomem, nostack, preserves_flags)
);
}
el >> 2
}
pub fn initialize_kernel() {
unsafe { initialize_kernel_heap() };
initialize_interrupt_handler();
initialize_app_manager();
init_terminal();
}
struct UartLogger;
impl log::Log for UartLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Debug
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
println!("{} - {}", record.level(), record.args());
if record.level() <= Level::Info {
flush_terminal();
}
}
}
fn flush(&self) {}
}
pub fn init_logger() {
log::set_logger(&LOGGER)
.map(|()| log::set_max_level(LevelFilter::Debug))
.unwrap();
}
+15 -22
View File
@@ -1,15 +1,9 @@
use core::slice;
use crate::{
aarch64::mmu::GRANULARITY, configuration::memory_mapping::MAILBOX_PHYSICAL_ADDRESS,
configuration::memory_mapping::MAILBOX_VIRTUAL_ADDRESS, read_address, write_address,
};
use nova_error::NovaError;
use crate::{mmio_read, mmio_write};
const MBOX_BASE: u32 = 0x3F00_0000 + 0xB880;
// MB0
const MBOX_READ: u32 = MBOX_BASE;
const MBOX_READ: u32 = MBOX_BASE + 0x00;
const MBOX_STATUS: u32 = MBOX_BASE + 0x18;
// MB1
@@ -32,13 +26,12 @@ macro_rules! max {
#[macro_export]
macro_rules! mailbox_command {
($name:ident, $tag:expr, $request_len:expr,$response_len:expr) => {
/// More information at: <https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface>
/// More information at: https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
pub fn $name(
request_data: [u32; $request_len / 4],
) -> Result<[u32; $response_len / 4], NovaError> {
let mailbox = unsafe {
slice::from_raw_parts_mut(MAILBOX_VIRTUAL_ADDRESS as *mut u32, GRANULARITY / 4)
};
) -> Result<[u32; $response_len / 4], NovaError::NovaError> {
let mut mailbox =
[0u32; (HEADER_LENGTH + max!($request_len, $response_len) + FOOTER_LENGTH) / 4];
mailbox[0] = (HEADER_LENGTH + max!($request_len, $response_len) + FOOTER_LENGTH) as u32; // Total length in Bytes
mailbox[1] = 0; // Request
mailbox[2] = $tag; // Command Tag
@@ -48,14 +41,14 @@ macro_rules! mailbox_command {
mailbox[5..(5 + ($request_len / 4))].copy_from_slice(&request_data);
mailbox[(5 + ($request_len / 4))..].fill(0);
//let addr = core::ptr::addr_of!(mailbox[0]) as u32;
let addr = core::ptr::addr_of!(mailbox[0]) as u32;
write_mailbox(8, unsafe { MAILBOX_PHYSICAL_ADDRESS.unwrap() } as u32);
write_mailbox(8, addr);
let _ = read_mailbox(8);
if mailbox[1] == 0 {
return Err(NovaError::Mailbox);
return Err(NovaError::NovaError::Mailbox);
}
let mut out = [0u32; $response_len / 4];
@@ -65,16 +58,16 @@ macro_rules! mailbox_command {
};
}
mailbox_command!(read_soc_temp, 0x0003_0006, 4, 8);
mailbox_command!(mb_read_soc_temp, 0x0003_0006, 4, 8);
// Framebuffer
mailbox_command!(get_display_resolution, 0x0004_0003, 0, 8);
mailbox_command!(mb_get_display_resolution, 0x0004_0003, 0, 8);
pub fn read_mailbox(channel: u32) -> u32 {
// Wait until mailbox is not empty
loop {
while unsafe { read_address(MBOX_STATUS) } & MAIL_EMPTY != 0 {}
let mut data = unsafe { read_address(MBOX_READ) };
while mmio_read(MBOX_STATUS) & MAIL_EMPTY != 0 {}
let mut data = mmio_read(MBOX_READ);
let read_channel = data & 0xF;
data >>= 4;
@@ -86,6 +79,6 @@ pub fn read_mailbox(channel: u32) -> u32 {
}
pub fn write_mailbox(channel: u32, data: u32) {
while unsafe { read_address(MBOX_STATUS) } & MAIL_FULL != 0 {}
unsafe { write_address(MBOX_WRITE, (data & !0xF) | (channel & 0xF)) };
while mmio_read(MBOX_STATUS) & MAIL_FULL != 0 {}
mmio_write(MBOX_WRITE, (data & !0xF) | (channel & 0xF));
}
+49 -113
View File
@@ -1,23 +1,19 @@
#![no_main]
#![no_std]
#![feature(asm_experimental_arch)]
#![allow(static_mut_refs)]
#![allow(clippy::missing_safety_doc)]
use core::{
arch::{asm, global_asm},
ptr::write_volatile,
};
use log::{debug, info};
extern crate alloc;
use alloc::{slice, vec::Vec};
use nova::{
aarch64::registers::{daif, read_id_aa64mmfr0_el1},
application_manager::{add_app, Application},
configuration::memory_mapping::initialize_mmu_translation_tables,
framebuffer::{FrameBuffer, BLUE, GREEN, RED},
get_current_el, init_logger,
interrupt_handlers::irq::{enable_irq_source, IRQSource},
init_heap,
irq_interrupt::enable_irq_source,
mailbox::mb_read_soc_temp,
peripherals::{
gpio::{
blink_gpio, gpio_pull_up, set_falling_edge_detect, set_gpio_function, GPIOFunction,
@@ -25,18 +21,15 @@ use nova::{
},
uart::uart_init,
},
pi3::timer::sleep_s,
print, println,
timer::{delay_nops, sleep_us},
GLOBAL_ALLOCATOR,
};
global_asm!(include_str!("vector.S"));
global_asm!(include_str!("config.S"));
static mut FRAMEBUFFER: Option<FrameBuffer> = None;
extern "C" {
fn el2_to_el1();
fn configure_mmu_el1();
static mut __bss_start: u32;
static mut __bss_end: u32;
}
@@ -62,23 +55,16 @@ pub extern "C" fn main() -> ! {
// Set ACT Led to Outout
let _ = set_gpio_function(21, GPIOFunction::Output);
init_logger();
info!("Hello World!");
info!("Current exception level: {}", get_current_el());
info!("initializing MMU...");
initialize_mmu_translation_tables();
unsafe { configure_mmu_el1() };
info!("MMU configured!");
debug!("Register: AA64MMFR0_EL1: {:064b}", read_id_aa64mmfr0_el1());
info!("Moving El2->EL1");
unsafe { FRAMEBUFFER = Some(FrameBuffer::default()) };
// Delay so clock speed can stabilize
delay_nops(50000);
println!("Hello World!");
unsafe {
asm!("mrs x0, SCTLR_EL1");
el2_to_el1();
}
#[allow(clippy::empty_loop)]
loop {}
}
@@ -92,119 +78,69 @@ unsafe fn zero_bss() {
}
#[no_mangle]
pub extern "C" fn kernel_main() {
nova::initialize_kernel();
info!("Kernel Initialized...");
info!("Current exception Level: {}", get_current_el());
pub extern "C" fn kernel_main() -> ! {
println!("EL: {}", get_current_el());
let mut test_vector = Vec::new();
for i in 0..20 {
test_vector.push(i);
}
debug!("heap allocation test: {:?}", test_vector);
enable_irq_source(IRQSource::UartInt);
unsafe {
init_heap();
heap_test();
};
let app = Application::new(el0 as *const () as usize);
add_app(app).unwrap();
kernel_loop();
}
#[no_mangle]
pub extern "C" fn kernel_loop() {
daif::unmask_all();
#[allow(clippy::empty_loop)]
loop {}
}
#[no_mangle]
pub unsafe extern "C" fn el0(argc: usize, argv: *const *const u8) {
println!("Jumped into EL0");
println!("num: {}", argc);
println!("argv: {:?}", argv);
let raw_args = unsafe { slice::from_raw_parts(argv, argc) };
let first_arg = raw_args
.iter()
.map(|&arg_ptr| {
if arg_ptr.is_null() {
return "";
}
let c_str = unsafe { core::ffi::CStr::from_ptr(arg_ptr) };
let str_slice = c_str.to_str().unwrap();
str_slice
})
.next();
sleep_s(1);
sleep_us(500_000);
// Set GPIO 26 to Input
enable_irq_source(IRQSource::GpioInt0); //26 is on the first GPIO bank
enable_irq_source(nova::irq_interrupt::IRQState::GpioInt0); //26 is on the first GPIO bank
let _ = set_gpio_function(26, GPIOFunction::Input);
gpio_pull_up(26);
set_falling_edge_detect(26, true);
if let Some(fb) = unsafe { FRAMEBUFFER.as_mut() } {
for i in 0..1080 {
fb.draw_pixel(50, i, BLUE);
}
let fb = FrameBuffer::new();
fb.draw_square(500, 500, 600, 700, RED);
fb.draw_square_fill(800, 800, 900, 900, GREEN);
fb.draw_square_fill(1000, 800, 1200, 700, BLUE);
fb.draw_square_fill(900, 100, 800, 150, RED | BLUE);
fb.draw_string("Hello World! :D\nTest next Line", 500, 5, 3, BLUE);
fb.draw_function(cos, 0, 101, RED);
}
fb.draw_function(cos, 100, 101, RED);
let _temp = syscall(67);
if let Some(num) = first_arg.and_then(|val| val.parse::<usize>().ok()) {
println!("Calculting prime to: {}", num);
for i in 3..num {
let mut is_prime = true;
for j in 3..i {
if i == j {
continue;
}
if i % j == 0 {
is_prime = false;
}
}
if is_prime {
print!("{} ", i);
}
}
println!("");
} else {
println!("Input NaN");
}
loop {
let temp = mb_read_soc_temp([0]).unwrap();
println!("{} °C", temp[1] / 1000);
blink_gpio(SpecificGpio::OnboardLed as u8, 500);
syscall(0);
}
}
unsafe fn heap_test() {
let a = GLOBAL_ALLOCATOR.malloc(32).unwrap();
let b = GLOBAL_ALLOCATOR.malloc(64).unwrap();
let c = GLOBAL_ALLOCATOR.malloc(128).unwrap();
let _ = GLOBAL_ALLOCATOR.malloc(256).unwrap();
GLOBAL_ALLOCATOR.free(b).unwrap();
GLOBAL_ALLOCATOR.free(a).unwrap();
GLOBAL_ALLOCATOR.free(c).unwrap();
}
fn cos(x: u32) -> f64 {
libm::cos(x as f64 * 0.1) * 20.0
}
fn get_current_el() -> u64 {
let el: u64;
unsafe {
asm!(
"mrs {el}, CurrentEL",
el = out(reg) el,
options(nomem, nostack, preserves_flags)
);
}
el >> 2
}
fn enable_uart() {
uart_init();
// Set GPIO Pins to UART
let _ = set_gpio_function(14, GPIOFunction::Alternative0);
let _ = set_gpio_function(15, GPIOFunction::Alternative0);
uart_init();
}
pub fn syscall(nr: u64) -> u64 {
let ret: u64;
unsafe {
asm!(
"svc #0",
in("x8") nr,
lateout("x0") ret,
);
}
ret
}
+21 -47
View File
@@ -1,19 +1,17 @@
use core::result::Result;
use core::result::Result::Ok;
use core::sync::atomic::{compiler_fence, Ordering};
use crate::pi3::timer::{delay_nops, sleep_ms};
use crate::{read_address, write_address};
use crate::timer::{delay_nops, sleep_ms};
use crate::{mmio_read, mmio_write};
const GPFSEL_BASE: u32 = 0x3F20_0000;
const GPSET_BASE: u32 = 0x3F20_001C;
const GPCLR_BASE: u32 = 0x3F20_0028;
const GPLEV_BASE: u32 = 0x3F20_0034;
const GPEDS_BASE: u32 = 0x3F20_0040;
const GPFEN_BASE: u32 = 0x3F20_0058;
const GPPUD: u32 = 0x3F20_0094;
const GPPUDCLK_BASE: u32 = 0x3F20_0098;
const GPREN_BASE: u32 = 0x3F20_004C;
const GPFEN_BASE: u32 = 0x3F20_0058;
#[repr(u8)]
pub enum SpecificGpio {
@@ -37,27 +35,26 @@ pub fn set_gpio_function(gpio: u8, state: GPIOFunction) -> Result<(), &'static s
let register_index = gpio / 10;
let register_offset = (gpio % 10) * 3;
let register_addr = GPFSEL_BASE + (register_index as u32 * 4);
let current = unsafe { read_address(register_addr) };
let current = mmio_read(register_addr);
let mask = !(0b111 << register_offset);
let cleared = current & mask;
let new_val = cleared | ((state as u32) << register_offset);
unsafe { write_address(register_addr, new_val) };
mmio_write(register_addr, new_val);
Ok(())
}
/// Set the GPIO to high
///
/// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function`,
/// otherwise setting is ignored
/// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function`
pub fn gpio_high(gpio: u8) -> Result<(), &'static str> {
let register_index = gpio / 32;
let register_offset = gpio % 32;
let register_addr = GPSET_BASE + (register_index as u32 * 4);
unsafe { write_address(register_addr, 1 << register_offset) };
mmio_write(register_addr, 1 << register_offset);
Ok(())
}
@@ -69,7 +66,7 @@ pub fn gpio_low(gpio: u8) -> Result<(), &'static str> {
let register_offset = gpio % 32;
let register_addr = GPCLR_BASE + (register_index as u32 * 4);
unsafe { write_address(register_addr, 1 << register_offset) };
mmio_write(register_addr, 1 << register_offset);
Ok(())
}
@@ -79,7 +76,7 @@ pub fn gpio_get_state(gpio: u8) -> u8 {
let register_offset = gpio % 32;
let register_addr = GPLEV_BASE + (register_index as u32 * 4);
let state = unsafe { read_address(register_addr) };
let state = mmio_read(register_addr);
((state >> register_offset) & 0b1) as u8
}
@@ -103,40 +100,40 @@ fn gpio_pull_up_down(gpio: u8, val: u32) {
let register_offset = gpio % 32;
// 1. Write Pull up
unsafe { write_address(GPPUD, val) };
mmio_write(GPPUD, val);
// 2. Delay 150 cycles
delay_nops(150);
// 3. Write to clock
let new_val = 0b1 << register_offset;
unsafe { write_address(register_addr, new_val) };
mmio_write(register_addr, new_val);
// 4. Delay 150 cycles
delay_nops(150);
// 5. reset GPPUD
unsafe { write_address(GPPUD, 0) };
mmio_write(GPPUD, 0);
// 6. reset clock
unsafe { write_address(register_addr, 0) };
mmio_write(register_addr, 0);
}
/// Get the current status of the falling edge detection
/// Get the current status if falling edge detection is set
pub fn read_falling_edge_detect(gpio: u8) -> bool {
let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = unsafe { read_address(register_addr) };
let current = mmio_read(register_addr);
((current >> register_offset) & 0b1) != 0
}
/// Get the current status of the rising edge detection
/// Get the current status if falling edge detection is set
pub fn read_rising_edge_detect(gpio: u8) -> bool {
let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = unsafe { read_address(register_addr) };
let current = mmio_read(register_addr);
((current >> register_offset) & 0b1) != 0
}
@@ -145,7 +142,7 @@ pub fn set_falling_edge_detect(gpio: u8, enable: bool) {
let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = unsafe { read_address(register_addr) };
let current = mmio_read(register_addr);
let mask = 0b1 << register_offset;
let new_val = if enable {
current | mask
@@ -153,7 +150,7 @@ pub fn set_falling_edge_detect(gpio: u8, enable: bool) {
current & !mask
};
unsafe { write_address(register_addr, new_val) };
mmio_write(register_addr, new_val);
}
/// Enables rising edge detection
@@ -161,7 +158,7 @@ pub fn set_rising_edge_detect(gpio: u8, enable: bool) {
let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = unsafe { read_address(register_addr) };
let current = mmio_read(register_addr);
let mask = 0b1 << register_offset;
let new_val = if enable {
@@ -170,32 +167,9 @@ pub fn set_rising_edge_detect(gpio: u8, enable: bool) {
current & !mask
};
unsafe { write_address(register_addr, new_val) };
mmio_write(register_addr, new_val);
}
/// Returns with the interrupt status of an GPIO.
///
/// GPEDS register is used to record level and edge events on the GPIO pins.
/// When an event is triggered by the GPIO, the corresponding bit will be set to 1.
pub fn read_gpio_event_detect_status(id: u32) -> bool {
let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32;
let val = unsafe { read_address(register) } >> register_offset;
(val & 0b1) != 0
}
/// Resets current interrupt status of a GPIO pin.
pub fn reset_gpio_event_detect_status(id: u32) {
let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32;
unsafe { write_address(register, 0b1 << register_offset) };
compiler_fence(Ordering::SeqCst);
}
// TODO: GPHEN,GPLEN,GPAREN,GPAFEN
pub fn blink_gpio(gpio: u8, duration_ms: u64) {
let _ = gpio_high(gpio);
+20 -61
View File
@@ -3,7 +3,7 @@ use core::{
fmt::{self, Write},
};
use crate::{read_address, write_address};
use crate::{mmio_read, mmio_write};
const BAUD: u32 = 115200;
const UART_CLK: u32 = 48_000_000;
@@ -18,55 +18,35 @@ const UART0_FBRD: u32 = 0x3F20_1028;
const UART0_CR: u32 = 0x3F20_1030;
const UART0_CR_UARTEN: u32 = 1 << 0;
const UART0_CR_TXE: u32 = 1 << 8;
const UART0_CR_RXE: u32 = 1 << 9;
const UART0_LCRH: u32 = 0x3F20_102C;
const UART0_LCRH_FEN: u32 = 1 << 4;
const UART0_IMSC: u32 = 0x3F20_1038;
const UART0_IMSC_RXIM: u32 = 1 << 4;
const UART0_ICR: u32 = 0x3F20_1044;
pub struct Uart;
impl Write for Uart {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for byte in s.bytes() {
while (unsafe { read_address(UART0_FR) } & UART0_FR_TXFF) != 0 {
while (mmio_read(UART0_FR) & UART0_FR_TXFF) != 0 {
unsafe { asm!("nop") }
}
unsafe { write_address(UART0_DR, byte as u32) };
mmio_write(UART0_DR, byte as u32);
}
// wait till uart is not busy anymore
while ((unsafe { read_address(UART0_FR) } >> 3) & 0b1) != 0 {}
while ((mmio_read(UART0_FR) >> 3) & 0b1) != 0 {}
Ok(())
}
}
#[macro_export]
macro_rules! print {
() => {};
($($arg:tt)*) => {
$crate::peripherals::uart::_print(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! println {
() => {};
($($arg:tt)*) => {
$crate::print!($($arg)*);
$crate::print!("\r\n");
};
}
pub fn _print(args: fmt::Arguments) {
let _ = Uart.write_fmt(args);
}
pub fn _print_str(st: &str) {
let _ = Uart.write_str(st);
}
/// Initialize UART peripheral
pub fn uart_init() {
let baud_div_times_64 = (UART_CLK * 4) / BAUD;
@@ -75,26 +55,23 @@ pub fn uart_init() {
let fbrd = baud_div_times_64 % 64;
uart_enable(false);
uart_fifo_enable(true);
uart_fifo_enable(false);
unsafe {
write_address(UART0_IBRD, ibrd);
write_address(UART0_FBRD, fbrd);
}
mmio_write(UART0_IBRD, ibrd);
mmio_write(UART0_FBRD, fbrd);
uart_enable_rx_interrupt();
uart_set_lcrh(0b11, true);
// Enable transmit, receive and uart
let mut cr = unsafe { read_address(UART0_CR) };
cr |= UART0_CR_UARTEN | UART0_CR_TXE | UART0_CR_RXE;
// Enable transmit and uart
let mut cr = mmio_read(UART0_CR);
cr |= UART0_CR_UARTEN | UART0_CR_TXE;
unsafe { write_address(UART0_CR, cr) };
mmio_write(UART0_CR, cr);
}
/// Enable UARTEN
fn uart_enable(enable: bool) {
let mut cr = unsafe { read_address(UART0_CR) };
let mut cr = mmio_read(UART0_CR);
if enable {
cr |= UART0_CR_UARTEN;
@@ -102,12 +79,12 @@ fn uart_enable(enable: bool) {
cr &= !UART0_CR_UARTEN;
}
unsafe { write_address(UART0_CR, cr) };
mmio_write(UART0_CR, cr);
}
/// Enable UART FIFO
fn uart_fifo_enable(enable: bool) {
let mut lcrh = unsafe { read_address(UART0_LCRH) };
let mut lcrh = mmio_read(UART0_LCRH);
if enable {
lcrh |= UART0_LCRH_FEN;
@@ -115,32 +92,14 @@ fn uart_fifo_enable(enable: bool) {
lcrh &= !UART0_LCRH_FEN;
}
unsafe { write_address(UART0_LCRH, lcrh) };
}
#[inline(always)]
fn uart_enable_rx_interrupt() {
unsafe { write_address(UART0_IMSC, UART0_IMSC_RXIM) };
mmio_write(UART0_LCRH, lcrh);
}
/// Set UART word length and set FIFO status
#[inline(always)]
fn uart_set_lcrh(wlen: u32, enable_fifo: bool) {
let mut value = (wlen & 0b11) << 5;
if enable_fifo {
value |= UART0_LCRH_FEN;
}
unsafe { write_address(UART0_LCRH, value) };
}
#[inline(always)]
pub fn read_uart_data() -> char {
(unsafe { read_address(UART0_DR) } & 0xFF) as u8 as char
}
#[inline(always)]
pub fn clear_uart_interrupt_state() {
unsafe {
write_address(UART0_ICR, 1 << 4);
}
mmio_write(UART0_LCRH, value);
}
-3
View File
@@ -1,3 +0,0 @@
pub mod mailbox;
pub mod power_management;
pub mod timer;
-28
View File
@@ -1,28 +0,0 @@
use core::ptr::{read_volatile, write_volatile};
use crate::PERIPHERAL_BASE;
/// Power Management Base
static PM_BASE: u32 = PERIPHERAL_BASE as u32 + 0x10_0000;
static PM_RSTC: u32 = PM_BASE + 0x1c;
static PM_WDOG: u32 = PM_BASE + 0x24;
static PM_PASSWORD: u32 = 0x5a000000;
static PM_WDOG_TIMER_MASK: u32 = 0x000fffff;
static PM_RSTC_WRCFG_CLR: u32 = 0xffffffcf;
static PM_RSTC_WRCFG_FULL_RESET: u32 = 0x00000020;
pub fn reboot_system() {
unsafe {
let pm_rstc_val = read_volatile(PM_RSTC as *mut u32);
// (31:16) bits -> password
// (11:0) bits -> value
write_volatile(PM_WDOG as *mut u32, PM_PASSWORD | (1 & PM_WDOG_TIMER_MASK));
write_volatile(
PM_RSTC as *mut u32,
PM_PASSWORD | (pm_rstc_val & PM_RSTC_WRCFG_CLR) | PM_RSTC_WRCFG_FULL_RESET,
);
}
#[allow(clippy::empty_loop)]
loop {}
}
View File
+38 -112
View File
@@ -1,4 +1,5 @@
.section .vector_t , "ax"
.global vector_table
.extern irq_handler
.macro ventry label
@@ -6,128 +7,53 @@
b \label
.endm
.global vector_table
.section .vector_table, "ax"
vector_table:
// Exceptions from current EL using SP_EL0
ventry .
ventry .
ventry .
ventry .
// Exceptions from the current EL using SP_ELx
ventry synchronous_interrupt_no_el_change // Synchronous Exception 0x200
ventry synchronous_interrupt // Synchronous Exception 0x200
ventry irq_handler // IRQ(Interrupt Request) 0x280
ventry . // FIQ(Fast Interrupt Request) 0x300
ventry . // SError 0x580
ventry .
ventry .
// Exceptions from lower EL AArch64
ventry synchronous_interrupt_imm_lower_aarch64 // Synchronous Exception 0x400
ventry irq_handler // IRQ(Interrupt Request) 0x480
ventry . // FIQ(Fast Interrupt Request) 0x500
ventry . // SError 0x580
// Exceptions from lower EL AArch32
ventry .
ventry .
ventry .
ventry .
.align 4
irq_handler:
sub sp, sp, #176
stp x0, x1, [sp, #0]
stp x2, x3, [sp, #16]
stp x4, x5, [sp, #32]
stp x6, x7, [sp, #48]
stp x8, x9, [sp, #64]
stp x10, x11, [sp, #80]
stp x12, x13, [sp, #96]
stp x14, x15, [sp, #112]
stp x16, x17, [sp, #128]
stp x18, x29, [sp, #144]
stp x30, xzr, [sp, #160]
.global el2_to_el1
el2_to_el1:
bl rust_irq_handler
mov x0, #(1 << 31)
msr HCR_EL2, x0
ldp x0, x1, [sp, #0]
ldp x2, x3, [sp, #16]
ldp x4, x5, [sp, #32]
ldp x6, x7, [sp, #48]
ldp x8, x9, [sp, #64]
ldp x10, x11, [sp, #80]
ldp x12, x13, [sp, #96]
ldp x14, x15, [sp, #112]
ldp x16, x17, [sp, #128]
ldp x18, x29, [sp, #144]
ldp x30, xzr, [sp, #160]
add sp, sp, #176
eret
.align 4
synchronous_interrupt_imm_lower_aarch64:
sub sp, sp, #176
stp x0, x1, [sp, #0]
stp x2, x3, [sp, #16]
stp x4, x5, [sp, #32]
stp x6, x7, [sp, #48]
stp x8, x9, [sp, #64]
stp x10, x11, [sp, #80]
stp x12, x13, [sp, #96]
stp x14, x15, [sp, #112]
stp x16, x17, [sp, #128]
stp x18, x29, [sp, #144]
stp x30, xzr, [sp, #160]
mov x0, sp
bl rust_synchronous_interrupt_imm_lower_aarch64
str x0, [sp, #0]
ldp x0, x1, [sp, #0]
ldp x2, x3, [sp, #16]
ldp x4, x5, [sp, #32]
ldp x6, x7, [sp, #48]
ldp x8, x9, [sp, #64]
ldp x10, x11, [sp, #80]
ldp x12, x13, [sp, #96]
ldp x14, x15, [sp, #112]
ldp x16, x17, [sp, #128]
ldp x18, x29, [sp, #144]
ldp x30, xzr, [sp, #160]
add sp, sp, #176
eret
.align 4
synchronous_interrupt_no_el_change:
sub sp, sp, #176
stp x0, x1, [sp, #0]
stp x2, x3, [sp, #16]
stp x4, x5, [sp, #32]
stp x6, x7, [sp, #48]
stp x8, x9, [sp, #64]
stp x10, x11, [sp, #80]
stp x12, x13, [sp, #96]
stp x14, x15, [sp, #112]
stp x16, x17, [sp, #128]
stp x18, x29, [sp, #144]
stp x30, xzr, [sp, #160]
mov x0, sp
bl rust_synchronous_interrupt_no_el_change
str x0, [sp, #0]
ldp x0, x1, [sp, #0]
ldp x2, x3, [sp, #16]
ldp x4, x5, [sp, #32]
ldp x6, x7, [sp, #48]
ldp x8, x9, [sp, #64]
ldp x10, x11, [sp, #80]
ldp x12, x13, [sp, #96]
ldp x14, x15, [sp, #112]
ldp x16, x17, [sp, #128]
ldp x18, x29, [sp, #144]
ldp x30, xzr, [sp, #160]
add sp, sp, #176
// Set SPSR_EL2: return to EL1h
mov x0, #(0b0101)
msr SPSR_EL2, x0
// Set return address to ELR_EL2
ldr x0, =kernel_main
msr ELR_EL2, x0
// Set SP_EL1 to stack base
ldr x0, =__stack_end
msr SP_EL1, x0
// Set VBAR_EL1 to vector table
adr x0, vector_table
msr VBAR_EL1, x0
// Disable MMU
ldr x0, =SCTLR_EL1_CONF
msr sctlr_el1, x0
// SIMD should not be trapped
mrs x0, CPACR_EL1
mov x1, #(0b11<<20)
orr x0,x0, x1
msr CPACR_EL1,x0
isb
// Return to EL1
eret
+1 -1
View File
@@ -15,7 +15,7 @@ REMOTE_DIR="$TFTP_PATH"
# BUILD
echo "[*] Building kernel..."
cargo build --release --target aarch64-unknown-none
cargo build --release
# CONVERT TO IMG
echo "[*] Convert kernel elf to img..."
-2
View File
@@ -1,5 +1,3 @@
set -e
cargo build --target aarch64-unknown-none --release
cd "$(dirname "$0")"
+3 -3
View File
@@ -1,5 +1,3 @@
set -e
cargo build --target aarch64-unknown-none
cd "$(dirname "$0")"
@@ -11,4 +9,6 @@ qemu-system-aarch64 \
-cpu cortex-a53 \
-serial stdio \
-sd ../sd.img \
-kernel ../target/aarch64-unknown-none/debug/kernel8.img -S -s
-display none \
-kernel ../target/aarch64-unknown-none/debug/kernel8.img \
-s -S
View File
-163
View File
@@ -1,163 +0,0 @@
use super::*;
use rand::{self, random_range};
extern crate std;
static HEAP_SIZE: usize = 1024;
#[test]
fn test_heap_allocation() {
let heap_vector = Box::new([0u8; HEAP_SIZE]);
let mut heap = Heap::empty();
heap.init(
&heap_vector[0] as *const u8 as usize,
&heap_vector[HEAP_SIZE - 1] as *const u8 as usize,
);
let root_header = heap.start_address;
let malloc_size = random_range(0..(HEAP_SIZE - HEAP_HEADER_SIZE));
let malloc = heap.malloc(malloc_size).unwrap();
let malloc_header = Heap::get_header_ref_from_data_pointer(malloc);
assert_eq!(root_header, malloc_header);
unsafe {
let actual_alloc_size = (*malloc_header).size;
let actual_raw_size = actual_alloc_size + HEAP_HEADER_SIZE;
// Verify sizing
assert!(actual_alloc_size >= malloc_size);
assert_eq!(actual_alloc_size % MIN_BLOCK_SIZE, 0);
// Verify section is occupied
assert!(!(*malloc_header).free);
// Verify next header has been created
let next = (*malloc_header).next.unwrap();
assert_eq!(malloc_header.byte_add(actual_raw_size), next);
assert!((*next).free);
assert_eq!((*malloc_header).next.unwrap(), next);
assert_eq!((*next).before.unwrap(), malloc_header);
assert_eq!((*next).size, HEAP_SIZE - actual_raw_size - HEAP_HEADER_SIZE)
}
}
#[test]
fn test_full_heap() {
let heap_vector = Box::new([0u8; HEAP_SIZE]);
let mut heap = Heap::empty();
heap.init(
&heap_vector[0] as *const u8 as usize,
&heap_vector[HEAP_SIZE - 1] as *const u8 as usize,
);
let malloc_size = HEAP_SIZE - HEAP_HEADER_SIZE;
let malloc = heap.malloc(malloc_size).unwrap();
let malloc_header = Heap::get_header_ref_from_data_pointer(malloc);
unsafe {
assert!(!(*malloc_header).free);
assert!((*malloc_header).next.is_none());
}
let malloc2 = heap.malloc(MIN_BLOCK_SIZE);
assert!(malloc2.is_err());
}
#[test]
fn test_freeing_root() {
let heap_vector = Box::new([0u8; HEAP_SIZE]);
let mut heap = Heap::empty();
heap.init(
&heap_vector[0] as *const u8 as usize,
&heap_vector[HEAP_SIZE - 1] as *const u8 as usize,
);
let root_header = heap.start_address;
let root_header_start_size = unsafe { (*root_header).size };
let malloc_size = random_range(0..((HEAP_SIZE - HEAP_HEADER_SIZE) / 2));
let malloc = heap.malloc(malloc_size).unwrap();
let malloc_header = Heap::get_header_ref_from_data_pointer(malloc);
unsafe {
assert!(!(*malloc_header).free);
assert!((*malloc_header).size >= malloc_size);
assert!((*root_header).next.is_some());
assert!(heap.free(malloc).is_ok());
assert_eq!((*root_header).size, root_header_start_size);
assert!((*root_header).next.is_none());
}
}
#[test]
fn test_merging_free_sections() {
let heap_vector = Box::new([0u8; HEAP_SIZE]);
let mut heap = Heap::empty();
heap.init(
&heap_vector[0] as *const u8 as usize,
&heap_vector[HEAP_SIZE - 1] as *const u8 as usize,
);
let root_header = heap.start_address;
let _root_header_start_size = unsafe { (*root_header).size };
let malloc1 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
let malloc_header_before = unsafe { *Heap::get_header_ref_from_data_pointer(malloc1) };
let malloc2 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
let _ = heap.malloc(MIN_BLOCK_SIZE).unwrap();
unsafe {
assert!(heap.free(malloc1).is_ok());
let malloc_header_free = *Heap::get_header_ref_from_data_pointer(malloc1);
assert_ne!(malloc_header_before.free, malloc_header_free.free);
assert_eq!(malloc_header_before.size, malloc_header_free.size);
assert!(heap.free(malloc2).is_ok());
let malloc_header_merge = *Heap::get_header_ref_from_data_pointer(malloc1);
assert!(malloc_header_merge.free);
assert_eq!(
malloc_header_merge.size,
malloc_header_free.size + MIN_BLOCK_SIZE + HEAP_HEADER_SIZE
);
}
}
#[test]
fn test_first_fit() {
let heap_vector = Box::new([0u8; HEAP_SIZE]);
let mut heap = Heap::empty();
heap.init(
&heap_vector[0] as *const u8 as usize,
&heap_vector[HEAP_SIZE - 1] as *const u8 as usize,
);
let root_header = heap.start_address;
let _root_header_start_size = unsafe { (*root_header).size };
let malloc1 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
let _malloc2 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
let malloc3 = heap.malloc(MIN_BLOCK_SIZE * 3).unwrap();
let malloc4 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
assert!(heap.free(malloc1).is_ok());
assert!(heap.free(malloc3).is_ok());
let malloc5 = heap.malloc(MIN_BLOCK_SIZE * 2).unwrap();
let malloc1_header = unsafe { *Heap::get_header_ref_from_data_pointer(malloc1) };
// First free block stays empty
assert!(malloc1_header.free);
// New allocation takes the first fit aka. malloc3
assert_eq!(malloc5, malloc3);
// If no free slot could be found, append to the end
let malloc6 = heap.malloc(MIN_BLOCK_SIZE * 2).unwrap();
assert!(malloc6 > malloc4);
// Malloc7 takes slot of Malloc1
let malloc7 = heap.malloc(MIN_BLOCK_SIZE).unwrap();
assert_eq!(malloc1, malloc7);
}