6 Commits

34 changed files with 844 additions and 1474 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
KERNEL_NAME=kernel8.img KERNEL_NAME=kernel8.img
BUILD_PATH=target/aarch64-unknown-none/release BUILD_PATH=target/aarch64-unknown-none/release
BINARY_NAME=nova BINARY_NAME=BINARY_NAME
TFTP_PATH=/srv/tftp TFTP_PATH=/srv/tftp
REMOTE_USER=TFTP_HOST_USER REMOTE_USER=TFTP_HOST_USER
REMOTE_HOST=TFTP_HOST_IP REMOTE_HOST=TFTP_HOST_IP
-6
View File
@@ -5,9 +5,3 @@ sd.img
settings.json settings.json
.DS_Store .DS_Store
.venv .venv
.nvimlog
__pycache__
.pytest_cache
build/
+4 -13
View File
@@ -3,7 +3,7 @@
"compounds": [ "compounds": [
{ {
"name": "Run QEMU + Attach LLDB", "name": "Run QEMU + Attach LLDB",
"configurations": ["LLDB"], "configurations": ["Attach LLDB"],
"preLaunchTask": "Run QEMU" "preLaunchTask": "Run QEMU"
} }
], ],
@@ -58,22 +58,13 @@
], ],
"preLaunchTask": "Run QEMU wo window" "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", "name": "Attach LLDB",
"type": "codelldb", "type": "lldb",
"request": "attach", "request": "attach",
"debugServer": 1234,
"program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova", "program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova",
"preLaunchTask": "Run QEMU",
"stopOnEntry": true, "stopOnEntry": true,
"processCreateCommands": ["gdb-remote localhost:1234"] "processCreateCommands": ["gdb-remote localhost:1234"]
} }
Generated
-32
View File
@@ -40,31 +40,14 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 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]] [[package]]
name = "nova" name = "nova"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"heap", "heap",
"libm", "libm",
"log",
"nova_error", "nova_error",
"paste", "paste",
"spin",
] ]
[[package]] [[package]]
@@ -139,21 +122,6 @@ dependencies = [
"getrandom", "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]] [[package]]
name = "syn" name = "syn"
version = "2.0.111" version = "2.0.111"
-2
View File
@@ -17,8 +17,6 @@ libm = "0.2.15"
heap = {path = "workspace/heap"} heap = {path = "workspace/heap"}
nova_error = {path = "workspace/nova_error"} nova_error = {path = "workspace/nova_error"}
paste = "1.0.15" paste = "1.0.15"
log = "0.4.29"
spin = "0.10.0"
[workspace] [workspace]
-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.
+17 -115
View File
@@ -1,120 +1,22 @@
# NovaOS # 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+. [Technical write-up](https://leafnova.net/projects/pi3_kernel/)
It is built as a learning project for low-level systems programming, bare-metal boot flow, and kernel development.
## At A Glance ## Features
NovaOS currently includes: - Delay and sleep ✓
- UART ✓
- UART initialization and logging - Switching ELs ✓
- Delay and sleep primitives - GPIOs ✓
- Exception level transitions across EL2, EL1, and EL0 - GPIO Interrupts ✓
- GPIO control and interrupt handling - Communicate with peripherals via mailboxes ✓
- Peripheral mailbox communication - Frame Buffer ✓
- Framebuffer drawing primitives - Heap Memory allocation ✓
- Heap memory allocation - MMU
- MMU initialization and translation table setup - SVC instructions
- Multi Core
Work in progress: - Dynamic clock speed
- Multiprocessing
- SVC instruction handling - Basic Terminal over UART
- 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.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

+23 -13
View File
@@ -5,42 +5,52 @@ SECTIONS {
KEEP(*(.text._start)) KEEP(*(.text._start))
*(.text .text.*) *(.text .text.*)
} }
.vector_table ALIGN(2K) : {
KEEP(*(.vector_t))
}
. = ALIGN(4K);
__text_end = .;
.rodata : { .rodata : {
*(.rodata .rodata.*) *(.rodata .rodata.*)
} }
.data : { .data ALIGN(2M) : {
_data = .;
*(.data .data.*) *(.data .data.*)
} }
.bss ALIGN(16) (NOLOAD) : { .bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .; __bss_start = .;
*(.bss .bss.*) *(.bss .bss.*)
*(COMMON)
__bss_end = .; __bss_end = .;
} }
. = ALIGN(2M); .vector_table ALIGN(2048) : {
KEEP(*(.vector_table))
}
__share_end = .; .heap ALIGN(16): {
__heap_start = .;
. += 100K; #100kB
__heap_end = .;
}
# EL2 Stack
.stack ALIGN(16): { .stack ALIGN(16): {
__stack_start = .; __stack_start = .;
. += 100K; #100kB stack . += 10K; #10kB stack
. = ALIGN(16);
__stack_end = .; __stack_end = .;
} }
. = ALIGN(2M); . = ALIGN(2M);
__kernel_end = .; __kernel_end = .;
.stack_el0 : {
__stack_start_el0 = .;
. += 10K; #10kB stack
__stack_end_el0 = .;
}
. = ALIGN(2M);
_end = .;
} }
__bss_size = (__bss_end - __bss_start) >> 3; __bss_size = (__bss_end - __bss_start) >> 3;
+264 -217
View File
@@ -1,12 +1,13 @@
use core::mem::size_of; use core::panic;
use nova_error::NovaError; use nova_error::NovaError;
use crate::{ unsafe extern "C" {
aarch64::mmu::physical_mapping::{ static mut __translation_table_l2_start: u64;
reserve_block, reserve_block_explicit, reserve_page, reserve_page_explicit, static __stack_start_el0: u64;
}, static __kernel_end: u64;
get_current_el, static _data: u64;
}; }
const BLOCK: u64 = 0b01; const BLOCK: u64 = 0b01;
const TABLE: u64 = 0b11; const TABLE: u64 = 0b11;
@@ -41,175 +42,134 @@ pub const LEVEL2_BLOCK_SIZE: usize = TABLE_ENTRY_COUNT * GRANULARITY;
const L2_BLOCK_BITMAP_WORDS: usize = LEVEL2_BLOCK_SIZE / (64 * GRANULARITY); const L2_BLOCK_BITMAP_WORDS: usize = LEVEL2_BLOCK_SIZE / (64 * GRANULARITY);
const MAX_PAGE_COUNT: usize = 1024 * 1024 * 1024 / 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))] #[repr(align(4096))]
pub struct PageTable(pub [TableEntry; TABLE_ENTRY_COUNT]); pub struct PageTable([u64; TABLE_ENTRY_COUNT]);
impl Iterator for PageTable { #[no_mangle]
type Item = VirtAddr; pub static mut TRANSLATIONTABLE_TTBR0: PageTable = PageTable([0; 512]);
fn next(&mut self) -> Option<Self::Item> { static mut PAGING_BITMAP: [u64; MAX_PAGE_COUNT / 64] = [0; MAX_PAGE_COUNT / 64];
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`. /// Allocate a memory block of `size` starting at `virtual_address`.
pub fn allocate_memory( pub fn allocate_memory(
virtual_address: usize, mut virtual_address: usize,
size_bytes: usize, mut size: usize,
phys: PhysSource, additional_flags: u64,
flags: u64,
) -> Result<(), NovaError> { ) -> Result<(), NovaError> {
if !virtual_address.is_multiple_of(GRANULARITY) { if !virtual_address.is_multiple_of(GRANULARITY) {
return Err(NovaError::Misalignment); return Err(NovaError::Misalignment);
} }
if !size_bytes.is_multiple_of(GRANULARITY) {
let level1_blocks = size / LEVEL1_BLOCK_SIZE;
size %= LEVEL1_BLOCK_SIZE;
let level2_blocks = size / LEVEL2_BLOCK_SIZE;
size %= LEVEL2_BLOCK_SIZE;
let level3_pages = size / GRANULARITY;
if !size.is_multiple_of(GRANULARITY) {
return Err(NovaError::InvalidGranularity); return Err(NovaError::InvalidGranularity);
} }
let base_table = if virtual_address & KERNEL_VIRTUAL_MEM_SPACE > 0 { if level1_blocks > 0 {
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR1) todo!("Currently not supported");
} 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 { for _ in 0..level2_blocks {
map_l2_block(virt, phys, base, flags)?; alloc_block_l2(
(virt, _) = virt.overflowing_add(LEVEL2_BLOCK_SIZE); virtual_address,
phys += LEVEL2_BLOCK_SIZE; core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
remaining -= LEVEL2_BLOCK_SIZE; additional_flags,
)?;
virtual_address += LEVEL2_BLOCK_SIZE;
} }
for _ in 0..level3_pages {
while remaining > 0 { alloc_page(
map_page(virt, phys, base, flags)?; virtual_address,
(virt, _) = virt.overflowing_add(GRANULARITY); core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
phys += GRANULARITY; additional_flags,
remaining -= GRANULARITY; )?;
virtual_address += GRANULARITY;
} }
Ok(()) Ok(())
} }
fn map_range_dynamic( /// Allocate a memory block of `size` starting at `virtual_address`,
mut virt: PhysAddr, /// with explicit physical_address.
size_bytes: usize, ///
base: *mut PageTable, /// Note: This can be used when mapping predefined regions.
flags: u64, pub fn allocate_memory_explicit(
mut virtual_address: usize,
mut size: usize,
mut physical_address: usize,
additional_flags: u64,
) -> Result<(), NovaError> { ) -> Result<(), NovaError> {
let mut remaining = size_bytes; if !virtual_address.is_multiple_of(GRANULARITY) {
return Err(NovaError::Misalignment);
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 { let level1_blocks = size / LEVEL1_BLOCK_SIZE;
map_page(virt, reserve_page(), base, flags)?; size %= LEVEL1_BLOCK_SIZE;
(virt, _) = virt.overflowing_add(GRANULARITY); let mut level2_blocks = size / LEVEL2_BLOCK_SIZE;
remaining -= GRANULARITY; size %= LEVEL2_BLOCK_SIZE;
let mut level3_pages = size / GRANULARITY;
if !size.is_multiple_of(GRANULARITY) {
return Err(NovaError::InvalidGranularity);
} }
if level1_blocks > 0 {
todo!("Currently not supported");
}
let l2_alignment = (physical_address % LEVEL2_BLOCK_SIZE) / GRANULARITY;
if l2_alignment != 0 {
let l3_diff = LEVEL2_BLOCK_SIZE / GRANULARITY - l2_alignment;
if l3_diff > level3_pages {
level2_blocks -= 1;
level3_pages += TABLE_ENTRY_COUNT;
}
level3_pages -= l3_diff;
for _ in 0..l3_diff {
alloc_page_explicit(
virtual_address,
physical_address,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
additional_flags,
)?;
virtual_address += GRANULARITY;
physical_address += GRANULARITY;
}
}
for _ in 0..level2_blocks {
alloc_block_l2_explicit(
virtual_address,
physical_address,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
additional_flags,
)?;
virtual_address += LEVEL2_BLOCK_SIZE;
physical_address += LEVEL2_BLOCK_SIZE;
}
for _ in 0..level3_pages {
alloc_page_explicit(
virtual_address,
physical_address,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
additional_flags,
)?;
virtual_address += GRANULARITY;
physical_address += GRANULARITY;
}
Ok(()) Ok(())
} }
/// Allocate a singe page. /// Allocate a singe page.
pub fn alloc_page( pub fn alloc_page(
virtual_address: VirtAddr, virtual_address: usize,
base_table: *mut PageTable, base_table: *mut PageTable,
additional_flags: u64, additional_flags: u64,
) -> Result<(), NovaError> { ) -> Result<(), NovaError> {
@@ -221,28 +181,6 @@ pub fn alloc_page(
) )
} }
/// 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`. /// Allocate a single page at an explicit `physical_address`.
pub fn alloc_page_explicit( pub fn alloc_page_explicit(
virtual_address: usize, virtual_address: usize,
@@ -259,7 +197,7 @@ pub fn alloc_page_explicit(
) )
} }
pub fn map_page( fn map_page(
virtual_address: usize, virtual_address: usize,
physical_address: usize, physical_address: usize,
base_table_ptr: *mut PageTable, base_table_ptr: *mut PageTable,
@@ -269,18 +207,32 @@ pub fn map_page(
let offsets = [l1_off, l2_off]; let offsets = [l1_off, l2_off];
let table_ptr = navigate_table(base_table_ptr, &offsets, true)?; let table_ptr = navigate_table(base_table_ptr, &offsets)?;
let table = unsafe { &mut *table_ptr }; let table = unsafe { &mut *table_ptr };
if !table.0[l3_off].is_invalid() { if table.0[l3_off] & 0b11 > 0 {
return Err(NovaError::Paging("Page already occupied.")); return Err(NovaError::Paging);
} }
table.0[l3_off] = TableEntry::page_descriptor(physical_address, additional_flags); table.0[l3_off] = create_page_descriptor_entry(physical_address, additional_flags);
Ok(()) Ok(())
} }
// Allocate a level 2 block.
pub fn alloc_block_l2(
virtual_addr: usize,
base_table_ptr: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
map_l2_block(
virtual_addr,
reserve_block(),
base_table_ptr,
additional_flags,
)
}
// Allocate a level 2 block, at a explicit `physical_address`. // Allocate a level 2 block, at a explicit `physical_address`.
pub fn alloc_block_l2_explicit( pub fn alloc_block_l2_explicit(
virtual_addr: usize, virtual_addr: usize,
@@ -309,26 +261,26 @@ pub fn map_l2_block(
) -> Result<(), NovaError> { ) -> Result<(), NovaError> {
let (l1_off, l2_off, _) = virtual_address_to_table_offset(virtual_addr); let (l1_off, l2_off, _) = virtual_address_to_table_offset(virtual_addr);
let offsets = [l1_off]; let offsets = [l1_off];
let table_ptr = navigate_table(base_table_ptr, &offsets, true)?; let table_ptr = navigate_table(base_table_ptr, &offsets)?;
let table = unsafe { &mut *table_ptr }; let table = unsafe { &mut *table_ptr };
// Verify virtual address is available. // Verify virtual address is available.
if !table.0[l2_off].is_invalid() { if table.0[l2_off] & 0b11 != 0 {
return Err(NovaError::Paging("Block already occupied.")); return Err(NovaError::Paging);
} }
let new_entry = TableEntry::block_descriptor(physical_address, additional_flags); let new_entry = create_block_descriptor_entry(physical_address, additional_flags);
table.0[l2_off] = new_entry; table.0[l2_off] = new_entry;
Ok(()) Ok(())
} }
pub fn reserve_range( pub fn reserve_range_explicit(
start_physical_address: PhysAddr, start_physical_address: usize,
end_physical_address: PhysAddr, end_physical_address: usize,
) -> Result<PhysAddr, NovaError> { ) -> Result<(), NovaError> {
let mut size = end_physical_address - start_physical_address; let mut size = end_physical_address - start_physical_address;
let l1_blocks = size / LEVEL1_BLOCK_SIZE; let l1_blocks = size / LEVEL1_BLOCK_SIZE;
size %= LEVEL1_BLOCK_SIZE; size %= LEVEL1_BLOCK_SIZE;
@@ -355,27 +307,109 @@ pub fn reserve_range(
addr += GRANULARITY; addr += GRANULARITY;
} }
Ok(start_physical_address) Ok(())
}
fn reserve_page() -> usize {
if let Some(address) = find_unallocated_page() {
let page = address / GRANULARITY;
let word_index = page / 64;
unsafe { PAGING_BITMAP[word_index] |= 1 << (page % 64) };
return address;
}
panic!("Out of Memory!");
}
fn reserve_page_explicit(physical_address: usize) -> Result<(), NovaError> {
let page = physical_address / GRANULARITY;
let word_index = page / 64;
if unsafe { PAGING_BITMAP[word_index] } & (1 << (page % 64)) > 0 {
return Err(NovaError::Paging);
}
unsafe { PAGING_BITMAP[word_index] |= 1 << (page % 64) };
Ok(())
}
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[start + j] = u64::MAX };
}
return start * 64 * GRANULARITY;
}
panic!("Out of Memory!");
}
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[(page / 64) + i] != 0 {
return Err(NovaError::Paging);
}
};
}
for i in 0..L2_BLOCK_BITMAP_WORDS {
unsafe {
PAGING_BITMAP[(page / 64) + i] = u64::MAX;
};
}
Ok(())
}
fn create_block_descriptor_entry(physical_address: usize, additional_flags: u64) -> u64 {
(physical_address as u64 & 0x0000_FFFF_FFFF_F000)
| BLOCK
| ACCESS_FLAG
| INNER_SHAREABILITY
| additional_flags
}
fn create_page_descriptor_entry(physical_address: usize, additional_flags: u64) -> u64 {
(physical_address as u64 & 0x0000_FFFF_FFFF_F000)
| PAGE
| ACCESS_FLAG
| INNER_SHAREABILITY
| additional_flags
}
fn create_table_descriptor_entry(addr: usize) -> u64 {
(addr as u64 & 0x0000_FFFF_FFFF_F000) | TABLE
} }
fn virtual_address_to_table_offset(virtual_addr: usize) -> (usize, usize, usize) { fn virtual_address_to_table_offset(virtual_addr: usize) -> (usize, usize, usize) {
let absolute_page_off = (virtual_addr & !KERNEL_VIRTUAL_MEM_SPACE) / GRANULARITY; let absolute_page_off = virtual_addr / GRANULARITY;
let l3_off = absolute_page_off % TABLE_ENTRY_COUNT; let l3_off = absolute_page_off % TABLE_ENTRY_COUNT;
let l2_off = (absolute_page_off / TABLE_ENTRY_COUNT) % 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; let l1_off = (absolute_page_off / TABLE_ENTRY_COUNT / TABLE_ENTRY_COUNT) % TABLE_ENTRY_COUNT;
(l1_off, l2_off, l3_off) (l1_off, l2_off, l3_off)
} }
/// Debugging function to navigate the translation tables.
#[allow(unused_variables)]
pub fn sim_l3_access(addr: usize) {
unsafe {
let entry1 = TRANSLATIONTABLE_TTBR0.0[addr / LEVEL1_BLOCK_SIZE];
let table2 = &mut *(entry_phys(entry1) as *mut PageTable);
let entry2 = table2.0[(addr % LEVEL1_BLOCK_SIZE) / LEVEL2_BLOCK_SIZE];
let table3 = &mut *(entry_phys(entry2) as *mut PageTable);
let _entry3 = table3.0[(addr % LEVEL2_BLOCK_SIZE) / GRANULARITY];
}
}
/// Navigate the table tree, by following given offsets. This function /// Navigate the table tree, by following given offsets. This function
/// allocates new tables if required. /// allocates new tables if required.
fn navigate_table( fn navigate_table(
initial_table_ptr: *mut PageTable, initial_table_ptr: *mut PageTable,
offsets: &[usize], offsets: &[usize],
create_missing: bool,
) -> Result<*mut PageTable, NovaError> { ) -> Result<*mut PageTable, NovaError> {
let root_table_ptr = initial_table_ptr;
let mut table = initial_table_ptr; let mut table = initial_table_ptr;
for offset in offsets { for offset in offsets {
table = next_table(table, *offset, create_missing)?; table = next_table(table, *offset, root_table_ptr)?;
} }
Ok(table) Ok(table)
} }
@@ -386,54 +420,67 @@ fn navigate_table(
fn next_table( fn next_table(
table_ptr: *mut PageTable, table_ptr: *mut PageTable,
offset: usize, offset: usize,
create_missing: bool, root_table_ptr: *mut PageTable,
) -> Result<*mut PageTable, NovaError> { ) -> Result<*mut PageTable, NovaError> {
let table = unsafe { &mut *table_ptr }; let table = unsafe { &mut *table_ptr };
match table.0[offset].value & 0b11 { match table.0[offset] & 0b11 {
0 => { 0 => {
if !create_missing { let new_table_addr = reserve_page();
return Err(NovaError::Paging("No table defined."));
} table.0[offset] = create_table_descriptor_entry(new_table_addr);
let new_phys_page_table_address = reserve_page();
table.0[offset] = TableEntry::table_descriptor(new_phys_page_table_address);
map_page( map_page(
phys_table_to_kernel_space(new_phys_page_table_address), new_table_addr,
new_phys_page_table_address, new_table_addr,
&raw mut TRANSLATIONTABLE_TTBR1, unsafe { &mut *root_table_ptr },
NORMAL_MEM | WRITABLE | PXN | UXN, NORMAL_MEM | WRITABLE | PXN | UXN,
)?; )?;
Ok(resolve_table_addr(table.0[offset].address()) as *mut PageTable) Ok(entry_phys(table.0[offset]) as *mut PageTable)
} }
1 => Err(NovaError::Paging( 1 => Err(NovaError::Paging),
"Can't navigate table due to block mapping.", 3 => Ok(entry_phys(table.0[offset]) as *mut PageTable),
)),
3 => Ok(resolve_table_addr(table.0[offset].address()) as *mut PageTable),
_ => unreachable!(), _ => unreachable!(),
} }
} }
/// Converts a physical table address and returns the corresponding virtual address depending on EL. fn find_unallocated_page() -> Option<usize> {
/// for (i, entry) in unsafe { PAGING_BITMAP }.iter().enumerate() {
/// - `== EL0` -> panic if *entry != u64::MAX {
/// - `== EL1` -> 0xFFFFFF82XXXXXXXX for offset in 0..64 {
/// - `>= EL2` -> physical address if entry >> offset & 0b1 == 0 {
#[inline] return Some((i * 64 + offset) * GRANULARITY);
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.")
} }
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 }.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
} }
/// Extracts the physical address out of an table entry. /// Extracts the physical address out of an table entry.
#[inline] #[inline]
fn phys_table_to_kernel_space(entry: usize) -> VirtAddr { fn entry_phys(entry: u64) -> u64 {
entry | TRANSLATION_TABLE_BASE_ADDR entry & 0x0000_FFFF_FFFF_F000
} }
-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 -2
View File
@@ -50,9 +50,9 @@ psr!(ESR_EL1, u32);
psr!(SPSR_EL1, u32); psr!(SPSR_EL1, u32);
psr!(ELR_EL1, u64); psr!(ELR_EL1, u32);
psr!(SCTLR_EL1, u64); psr!(SCTLR_EL1, u32);
pub fn read_exception_source_el() -> u32 { pub fn read_exception_source_el() -> u32 {
read_spsr_el1() & 0b1111 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
+62 -2
View File
@@ -23,12 +23,72 @@ const SH0: u64 = 0b11 << 12; // Inner shareable
const TG1: u64 = 0b10 << 30; // 4KB granularity EL1 const TG1: u64 = 0b10 << 30; // 4KB granularity EL1
const T1SZ: u64 = 25 << 16; // 25 Bits of TTBR select -> 39 Bits of VA const T1SZ: u64 = 25 << 16; // 25 Bits of TTBR select -> 39 Bits of VA
const EPD1: u64 = 0b1 << 23; // Trigger translation fault when using TTBR1_EL1
const SH1: u64 = 0b11 << 28; // Inner sharable const SH1: u64 = 0b11 << 28; // Inner sharable
const IPS: u64 = 0b000 << 32; // 32 bits of PA space -> up to 4GiB 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 const AS: u64 = 0b1 << 36; // configure an ASID size of 16 bits
#[no_mangle] #[no_mangle]
pub static TCR_EL1_CONF: u64 = IPS | TG0 | TG1 | T0SZ | T1SZ | SH0 | SH1 | AS; pub static TCR_EL1_CONF: u64 = IPS | TG0 | TG1 | T0SZ | T1SZ | SH0 | SH1 | EPD1 | AS;
pub mod memory_mapping; pub mod mmu {
use crate::{
aarch64::mmu::{
alloc_block_l2_explicit, map_l2_block, reserve_range_explicit, DEVICE_MEM,
EL0_ACCESSIBLE, LEVEL1_BLOCK_SIZE, LEVEL2_BLOCK_SIZE, NORMAL_MEM, PXN, READ_ONLY,
TRANSLATIONTABLE_TTBR0, UXN, WRITABLE,
},
PERIPHERAL_BASE,
};
extern "C" {
static _data: u64;
static _end: u64;
static __kernel_end: u64;
}
pub fn initialize_mmu_translation_tables() {
let shared_segment_end = unsafe { &_data } as *const _ as usize;
let kernel_end = unsafe { &__kernel_end } as *const _ as usize;
let user_space_end = unsafe { &_end } as *const _ as usize;
reserve_range_explicit(0x0, user_space_end).unwrap();
for addr in (0..shared_segment_end).step_by(LEVEL2_BLOCK_SIZE) {
let _ = map_l2_block(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | READ_ONLY | NORMAL_MEM,
);
}
for addr in (shared_segment_end..kernel_end).step_by(LEVEL2_BLOCK_SIZE) {
let _ = map_l2_block(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
WRITABLE | UXN | NORMAL_MEM,
);
}
for addr in (kernel_end..user_space_end).step_by(LEVEL2_BLOCK_SIZE) {
let _ = map_l2_block(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | WRITABLE | PXN | NORMAL_MEM,
);
}
for addr in (PERIPHERAL_BASE..LEVEL1_BLOCK_SIZE).step_by(LEVEL2_BLOCK_SIZE) {
let _ = alloc_block_l2_explicit(
addr,
addr,
core::ptr::addr_of_mut!(TRANSLATIONTABLE_TTBR0),
EL0_ACCESSIBLE | WRITABLE | UXN | PXN | DEVICE_MEM,
);
}
}
}
-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);
}
+6 -4
View File
@@ -4,8 +4,10 @@ mod bitmaps;
use bitmaps::BASIC_LEGACY; use bitmaps::BASIC_LEGACY;
use crate::pi3::mailbox::{read_mailbox, write_mailbox}; use crate::{
use log::error; pi3::mailbox::{read_mailbox, write_mailbox},
println,
};
#[repr(align(16))] #[repr(align(16))]
struct Mailbox([u32; 36]); struct Mailbox([u32; 36]);
@@ -39,7 +41,7 @@ impl FrameBuffer {
return; return;
} }
unsafe { unsafe {
write_volatile(self.start_addr.add(offset as usize), color); write_volatile(self.start_addr.byte_add(4 * offset as usize), color);
} }
} }
@@ -235,7 +237,7 @@ impl Default for FrameBuffer {
let _ = read_mailbox(8); let _ = read_mailbox(8);
if mailbox.0[1] == 0 { if mailbox.0[1] == 0 {
error!("Mailbox request was not processed!"); println!("Failed");
} }
mailbox.0[28] &= 0x3FFFFFFF; mailbox.0[28] &= 0x3FFFFFFF;
+180 -35
View File
@@ -1,10 +1,19 @@
use core::arch::asm; use core::arch::asm;
use alloc::vec::Vec;
use crate::{ use crate::{
aarch64::registers::{daif::mask_all, read_esr_el1, read_exception_source_el}, aarch64::registers::{
daif::{mask_all, unmask_irq},
read_elr_el1, read_esr_el1, read_exception_source_el,
},
get_current_el, get_current_el,
peripherals::{
gpio::{read_gpio_event_detect_status, reset_gpio_event_detect_status},
uart::clear_uart_interrupt_state,
},
println, read_address, write_address,
}; };
use log::debug;
const INTERRUPT_BASE: u32 = 0x3F00_B000; const INTERRUPT_BASE: u32 = 0x3F00_B000;
const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204; const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204;
@@ -13,29 +22,30 @@ const DISABLE_IRQ_BASE: u32 = INTERRUPT_BASE + 0x21C;
const GPIO_PENDING_BIT_OFFSET: u64 = 0b1111 << 49; const GPIO_PENDING_BIT_OFFSET: u64 = 0b1111 << 49;
#[repr(C)] struct InterruptHandlers {
pub struct TrapFrame { source: IRQSource,
pub x0: u64, function: fn(),
pub x1: u64, }
pub x2: u64,
pub x3: u64, // TODO: replace with hashmap and check for better alternatives for option
pub x4: u64, static mut INTERRUPT_HANDLERS: Option<Vec<InterruptHandlers>> = None;
pub x5: u64,
pub x6: u64, #[derive(Clone)]
pub x7: u64, #[repr(u32)]
pub x8: u64, pub enum IRQSource {
pub x9: u64, AuxInt = 29,
pub x10: u64, I2cSpiSlvInt = 44,
pub x11: u64, Pwa0 = 45,
pub x12: u64, Pwa1 = 46,
pub x13: u64, Smi = 48,
pub x14: u64, GpioInt0 = 49,
pub x15: u64, GpioInt1 = 50,
pub x16: u64, GpioInt2 = 51,
pub x17: u64, GpioInt3 = 52,
pub x18: u64, I2cInt = 53,
pub x29: u64, SpiInt = 54,
pub x30: u64, PcmInt = 55,
UartInt = 57,
} }
/// Representation of the ESR_ELx registers /// Representation of the ESR_ELx registers
@@ -59,25 +69,160 @@ impl From<u32> for EsrElX {
} }
} }
pub mod irq; #[no_mangle]
pub mod synchronous; 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;
println!("Source EL: {}", source_el);
println!("Current EL: {}", get_current_el());
println!("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());
}
}
}
}
#[no_mangle] #[no_mangle]
unsafe extern "C" fn rust_synchronous_interrupt_no_el_change() { unsafe extern "C" fn rust_synchronous_interrupt_no_el_change() {
mask_all(); mask_all();
let source_el = read_exception_source_el() >> 2; let source_el = read_exception_source_el() >> 2;
debug!("--------Sync Exception in EL{}--------", source_el); println!("--------Sync Exception in EL{}--------", source_el);
debug!("No EL change"); println!("No EL change");
debug!("Current EL: {}", get_current_el()); println!("Current EL: {}", get_current_el());
debug!("{:?}", EsrElX::from(read_esr_el1())); println!("{:?}", EsrElX::from(read_esr_el1()));
debug!("Return register address: {:#x}", read_esr_el1()); println!("Return register address: {:#x}", read_esr_el1());
debug!("-------------------------------------"); println!("-------------------------------------");
} }
fn set_return_to_kernel_loop() { /// Synchronous Exception Handler
///
/// 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() {
mask_all();
let source_el = read_exception_source_el() >> 2;
println!("--------Sync Exception in EL{}--------", source_el);
println!("Exception escalated to EL {}", get_current_el());
println!("Current EL: {}", get_current_el());
let esr: EsrElX = EsrElX::from(read_esr_el1());
println!("{:?}", esr);
println!("Return address: {:#x}", read_elr_el1());
match esr.ec {
0b100100 => {
println!("Cause: Data Abort from a lower Exception level");
}
_ => {
println!("Unknown Error Code: {:b}", esr.ec);
}
}
println!("-------------------------------------");
set_return_to_kernel_main();
}
fn clear_interrupt_for_source(source: IRQSource) {
match source {
IRQSource::UartInt => clear_uart_interrupt_state(),
_ => {
todo!()
}
}
}
fn set_return_to_kernel_main() {
unsafe { unsafe {
asm!("ldr x0, =kernel_loop", "msr ELR_EL1, x0"); asm!("ldr x0, =kernel_main", "msr ELR_EL1, x0");
asm!("mov x0, #(0b0101)", "msr SPSR_EL1, x0"); asm!("mov x0, #(0b0101)", "msr SPSR_EL1, x0");
} }
} }
fn handle_gpio_interrupt() {
println!("Interrupt");
for i in 0..=53u32 {
let val = read_gpio_event_detect_status(i);
if val {
#[allow(clippy::single_match)]
match i {
26 => {
println!("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
}
#[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 });
}
}
-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!("-------------------------------------");
}
+11 -50
View File
@@ -3,51 +3,39 @@
extern crate alloc; extern crate alloc;
use alloc::boxed::Box;
use core::{ use core::{
arch::asm, arch::asm,
panic::PanicInfo, panic::PanicInfo,
ptr::{read_volatile, write_volatile}, ptr::{read_volatile, write_volatile},
}; };
use log::LevelFilter;
use log::{Level, Metadata, Record};
use heap::Heap; use heap::Heap;
use crate::{ use crate::{interrupt_handlers::initialize_interrupt_handler, logger::DefaultLogger};
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; static PERIPHERAL_BASE: usize = 0x3F00_0000;
unsafe extern "C" { unsafe extern "C" {
unsafe static mut __kernel_end: u8; unsafe static mut __heap_start: u8;
unsafe static mut __heap_end: u8;
} }
#[global_allocator] #[global_allocator]
pub static mut GLOBAL_ALLOCATOR: Heap = Heap::empty(); pub static mut GLOBAL_ALLOCATOR: Heap = Heap::empty();
pub unsafe fn initialize_kernel_heap() { pub unsafe fn init_heap() {
let start = core::ptr::addr_of_mut!(__kernel_end) as usize | KERNEL_VIRTUAL_MEM_SPACE; let start = core::ptr::addr_of_mut!(__heap_start) as usize;
let size = LEVEL2_BLOCK_SIZE * 2; 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); let heap = core::ptr::addr_of_mut!(GLOBAL_ALLOCATOR);
(*heap).init(start, start + size); (*heap).init(start, end);
} }
#[panic_handler] #[panic_handler]
fn panic(_panic: &PanicInfo) -> ! { fn panic(_panic: &PanicInfo) -> ! {
loop { loop {
println!("Panic: {}", _panic.message()); println!("Panic: {}", _panic.message());
sleep_s(1);
} }
} }
@@ -57,9 +45,9 @@ pub mod aarch64;
pub mod configuration; pub mod configuration;
pub mod framebuffer; pub mod framebuffer;
pub mod interrupt_handlers; pub mod interrupt_handlers;
pub mod logger;
pub mod timer;
pub mod application_manager;
pub mod console;
pub mod pi3; pub mod pi3;
#[inline(always)] #[inline(always)]
@@ -85,33 +73,6 @@ pub fn get_current_el() -> u64 {
} }
pub fn initialize_kernel() { pub fn initialize_kernel() {
unsafe { initialize_kernel_heap() }; logger::set_logger(Box::new(DefaultLogger));
initialize_interrupt_handler(); 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();
} }
+45
View File
@@ -0,0 +1,45 @@
use core::fmt::Write;
use alloc::{boxed::Box, fmt};
use crate::peripherals::uart;
static mut LOGGER: Option<Box<dyn Logger>> = None;
pub trait Logger: Write + Sync {
fn flush(&mut self);
}
pub struct DefaultLogger;
impl Logger for DefaultLogger {
fn flush(&mut self) {}
}
impl Write for DefaultLogger {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
uart::Uart.write_str(s)
}
}
#[macro_export]
macro_rules! log {
() => {};
($($arg:tt)*) => {
$crate::logger::log(format_args!($($arg)*))
};
}
pub fn log(args: fmt::Arguments) {
if let Some(logger) = unsafe { &mut *core::ptr::addr_of_mut!(LOGGER) } {
logger.write_str("\n").unwrap();
logger.write_fmt(args).unwrap();
logger.flush();
}
}
pub fn set_logger(logger: Box<dyn Logger>) {
unsafe {
LOGGER = Some(logger);
}
}
+60 -106
View File
@@ -6,18 +6,20 @@ use core::{
arch::{asm, global_asm}, arch::{asm, global_asm},
ptr::write_volatile, ptr::write_volatile,
}; };
use log::{debug, info};
extern crate alloc; extern crate alloc;
use alloc::{slice, vec::Vec};
use nova::{ use nova::{
aarch64::registers::{daif, read_id_aa64mmfr0_el1}, aarch64::{
application_manager::{add_app, Application}, mmu::{
configuration::memory_mapping::initialize_mmu_translation_tables, allocate_memory_explicit, sim_l3_access, EL0_ACCESSIBLE, NORMAL_MEM, PXN, UXN, WRITABLE,
},
registers::{daif, read_id_aa64mmfr0_el1},
},
configuration::mmu::initialize_mmu_translation_tables,
framebuffer::{FrameBuffer, BLUE, GREEN, RED}, framebuffer::{FrameBuffer, BLUE, GREEN, RED},
get_current_el, init_logger, get_current_el, init_heap,
interrupt_handlers::irq::{enable_irq_source, IRQSource}, interrupt_handlers::{enable_irq_source, IRQSource},
peripherals::{ peripherals::{
gpio::{ gpio::{
blink_gpio, gpio_pull_up, set_falling_edge_detect, set_gpio_function, GPIOFunction, blink_gpio, gpio_pull_up, set_falling_edge_detect, set_gpio_function, GPIOFunction,
@@ -25,17 +27,15 @@ use nova::{
}, },
uart::uart_init, uart::uart_init,
}, },
pi3::timer::sleep_s, pi3::mailbox,
print, println, println,
}; };
global_asm!(include_str!("vector.S")); global_asm!(include_str!("vector.S"));
global_asm!(include_str!("config.S"));
static mut FRAMEBUFFER: Option<FrameBuffer> = None;
extern "C" { extern "C" {
fn el2_to_el1(); fn el2_to_el1();
fn el1_to_el0();
fn configure_mmu_el1(); fn configure_mmu_el1();
static mut __bss_start: u32; static mut __bss_start: u32;
static mut __bss_end: u32; static mut __bss_end: u32;
@@ -62,23 +62,33 @@ pub extern "C" fn main() -> ! {
// Set ACT Led to Outout // Set ACT Led to Outout
let _ = set_gpio_function(21, GPIOFunction::Output); let _ = set_gpio_function(21, GPIOFunction::Output);
init_logger();
info!("Hello World!"); println!("Hello World!");
info!("Current exception level: {}", get_current_el()); println!("Exception level: {}", get_current_el());
info!("initializing MMU..."); unsafe {
initialize_mmu_translation_tables(); init_heap();
unsafe { configure_mmu_el1() };
info!("MMU configured!");
debug!("Register: AA64MMFR0_EL1: {:064b}", read_id_aa64mmfr0_el1()); initialize_mmu_translation_tables();
info!("Moving El2->EL1"); // Frame Buffer memory range
unsafe { FRAMEBUFFER = Some(FrameBuffer::default()) }; // TODO: this is just temporary
allocate_memory_explicit(
0x3c100000,
1080 * 1920 * 4,
0x3c100000,
NORMAL_MEM | PXN | UXN | WRITABLE | EL0_ACCESSIBLE,
)
.unwrap();
sim_l3_access(0x3c100000);
configure_mmu_el1();
};
println!("AA64 {:064b}", read_id_aa64mmfr0_el1());
unsafe { unsafe {
el2_to_el1(); el2_to_el1();
} }
#[allow(clippy::empty_loop)] #[allow(clippy::empty_loop)]
loop {} loop {}
} }
@@ -92,50 +102,26 @@ unsafe fn zero_bss() {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn kernel_main() { pub extern "C" fn kernel_main() -> ! {
nova::initialize_kernel(); nova::initialize_kernel();
info!("Kernel Initialized..."); println!("Exception Level: {}", get_current_el());
info!("Current exception Level: {}", 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);
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(); daif::unmask_all();
let fb = FrameBuffer::default();
for i in 0..1080 {
fb.draw_pixel(50, i, RED);
}
unsafe {
el1_to_el0();
};
#[allow(clippy::empty_loop)] #[allow(clippy::empty_loop)]
loop {} loop {}
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn el0(argc: usize, argv: *const *const u8) { pub extern "C" fn el0() -> ! {
println!("Jumped into EL0"); 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);
// Set GPIO 26 to Input // Set GPIO 26 to Input
enable_irq_source(IRQSource::GpioInt0); //26 is on the first GPIO bank enable_irq_source(IRQSource::GpioInt0); //26 is on the first GPIO bank
@@ -143,45 +129,27 @@ pub unsafe extern "C" fn el0(argc: usize, argv: *const *const u8) {
gpio_pull_up(26); gpio_pull_up(26);
set_falling_edge_detect(26, true); set_falling_edge_detect(26, true);
if let Some(fb) = unsafe { FRAMEBUFFER.as_mut() } { enable_irq_source(IRQSource::UartInt);
for i in 0..1080 {
fb.draw_pixel(50, i, BLUE);
}
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); let fb = FrameBuffer::default();
for i in 600..1080 {
fb.draw_pixel(50, i, RED);
} }
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);
let _temp = syscall(67); fb.draw_function(cos, 0, 101, RED);
if let Some(num) = first_arg.and_then(|val| val.parse::<usize>().ok()) { loop {
println!("Calculting prime to: {}", num); let temp = mailbox::read_soc_temp([0]).unwrap();
println!("{} °C", temp[1] / 1000);
for i in 3..num { blink_gpio(SpecificGpio::OnboardLed as u8, 500);
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");
} }
blink_gpio(SpecificGpio::OnboardLed as u8, 500);
syscall(0);
} }
fn cos(x: u32) -> f64 { fn cos(x: u32) -> f64 {
@@ -194,17 +162,3 @@ fn enable_uart() {
let _ = set_gpio_function(15, GPIOFunction::Alternative0); let _ = set_gpio_function(15, GPIOFunction::Alternative0);
uart_init(); uart_init();
} }
pub fn syscall(nr: u64) -> u64 {
let ret: u64;
unsafe {
asm!(
"svc #0",
in("x8") nr,
lateout("x0") ret,
);
}
ret
}
+1 -1
View File
@@ -2,7 +2,7 @@ use core::result::Result;
use core::result::Result::Ok; use core::result::Result::Ok;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use crate::pi3::timer::{delay_nops, sleep_ms}; use crate::timer::{delay_nops, sleep_ms};
use crate::{read_address, write_address}; use crate::{read_address, write_address};
const GPFSEL_BASE: u32 = 0x3F20_0000; const GPFSEL_BASE: u32 = 0x3F20_0000;
+5 -11
View File
@@ -1,9 +1,4 @@
use core::slice; use crate::{read_address, write_address};
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 nova_error::NovaError;
const MBOX_BASE: u32 = 0x3F00_0000 + 0xB880; const MBOX_BASE: u32 = 0x3F00_0000 + 0xB880;
@@ -36,9 +31,8 @@ macro_rules! mailbox_command {
pub fn $name( pub fn $name(
request_data: [u32; $request_len / 4], request_data: [u32; $request_len / 4],
) -> Result<[u32; $response_len / 4], NovaError> { ) -> Result<[u32; $response_len / 4], NovaError> {
let mailbox = unsafe { let mut mailbox =
slice::from_raw_parts_mut(MAILBOX_VIRTUAL_ADDRESS as *mut u32, GRANULARITY / 4) [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[0] = (HEADER_LENGTH + max!($request_len, $response_len) + FOOTER_LENGTH) as u32; // Total length in Bytes
mailbox[1] = 0; // Request mailbox[1] = 0; // Request
mailbox[2] = $tag; // Command Tag mailbox[2] = $tag; // Command Tag
@@ -48,9 +42,9 @@ macro_rules! mailbox_command {
mailbox[5..(5 + ($request_len / 4))].copy_from_slice(&request_data); mailbox[5..(5 + ($request_len / 4))].copy_from_slice(&request_data);
mailbox[(5 + ($request_len / 4))..].fill(0); 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); let _ = read_mailbox(8);
-1
View File
@@ -1,3 +1,2 @@
pub mod mailbox; pub mod mailbox;
pub mod power_management; pub mod power_management;
pub mod timer;
+53
View File
@@ -0,0 +1,53 @@
use core::fmt::Write;
use alloc::string::String;
use nova::{
interrupt_handlers::register_interrupt_handler, logger::Logger,
peripherals::uart::read_uart_data, print, println,
};
pub struct Terminal {
buffer: String,
input: String,
}
impl Terminal {
pub fn new() -> Self {
Self {
buffer: String::new(),
input: String::new(),
}
}
fn flush(&mut self) {
println!("{}", self.buffer);
print!("> {}", self.input);
self.buffer.clear();
}
}
impl Write for Terminal {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.buffer.push_str(s);
Ok(())
}
}
impl Logger for Terminal {
fn flush(&mut self) {
println!("{}", self.buffer);
print!("> {}", self.input);
self.buffer.clear();
}
}
fn terminal_uart_rx_interrupt_handler() {
print!("{}", read_uart_data());
}
pub fn register_terminal_interrupt_handler() {
register_interrupt_handler(
nova::interrupt_handlers::IRQSource::UartInt,
terminal_uart_rx_interrupt_handler,
);
}
View File
+103 -19
View File
@@ -1,37 +1,125 @@
.section .vector_t , "ax"
.global v_table
.extern irq_handler .extern irq_handler
.macro ventry label .macro ventry label
.align 7 .align 7
b \label b \label
.endm .endm
.global vector_table .section .vector_table , "ax"
vector_table: vector_table:
// Exceptions from current EL using SP_EL0
ventry . ventry .
ventry . ventry .
ventry . ventry .
ventry . ventry .
// Exceptions from the current EL using SP_ELx ventry synchronous_interrupt_no_el_change // Synchronous Exception 0x200
ventry synchronous_interrupt_no_el_change // Synchronous Exception 0x200 ventry irq_handler // IRQ(Interrupt Request) 0x280
ventry irq_handler // IRQ(Interrupt Request) 0x280 ventry .
ventry . // FIQ(Fast Interrupt Request) 0x300 ventry .
ventry . // SError 0x580
// Exceptions from lower EL AArch64 ventry synchronous_interrupt_imm_lower_aarch64
ventry synchronous_interrupt_imm_lower_aarch64 // Synchronous Exception 0x400 ventry irq_handler
ventry irq_handler // IRQ(Interrupt Request) 0x480 ventry .
ventry . // FIQ(Fast Interrupt Request) 0x500 ventry .
ventry . // SError 0x580
// Exceptions from lower EL AArch32
ventry . ventry .
ventry . ventry .
ventry . ventry .
ventry . ventry .
.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
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
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
.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
msr TTBR1_EL1, x1
tlbi vmalle1
dsb ish
isb
ret
.align 4
.global el1_to_el0
el1_to_el0:
// Set SPSR_EL1: return to EL0t
mov x0, #(0b0000)
msr SPSR_EL1, x0
// Set return address to el0
ldr x0, =el0
msr ELR_EL1, x0
// Set SP_EL1 to stack base
ldr x0, =__stack_end_el0
msr SP_EL0, x0
isb
// Return to EL0
eret
.align 4 .align 4
irq_handler: irq_handler:
sub sp, sp, #176 sub sp, sp, #176
@@ -79,9 +167,7 @@ synchronous_interrupt_imm_lower_aarch64:
stp x18, x29, [sp, #144] stp x18, x29, [sp, #144]
stp x30, xzr, [sp, #160] stp x30, xzr, [sp, #160]
mov x0, sp
bl rust_synchronous_interrupt_imm_lower_aarch64 bl rust_synchronous_interrupt_imm_lower_aarch64
str x0, [sp, #0]
ldp x0, x1, [sp, #0] ldp x0, x1, [sp, #0]
ldp x2, x3, [sp, #16] ldp x2, x3, [sp, #16]
@@ -113,9 +199,7 @@ synchronous_interrupt_no_el_change:
stp x18, x29, [sp, #144] stp x18, x29, [sp, #144]
stp x30, xzr, [sp, #160] stp x30, xzr, [sp, #160]
mov x0, sp
bl rust_synchronous_interrupt_no_el_change bl rust_synchronous_interrupt_no_el_change
str x0, [sp, #0]
ldp x0, x1, [sp, #0] ldp x0, x1, [sp, #0]
ldp x2, x3, [sp, #16] ldp x2, x3, [sp, #16]
+3 -1
View File
@@ -11,4 +11,6 @@ qemu-system-aarch64 \
-cpu cortex-a53 \ -cpu cortex-a53 \
-serial stdio \ -serial stdio \
-sd ../sd.img \ -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
-4
View File
@@ -39,10 +39,6 @@ impl Heap {
} }
} }
pub fn size(self) -> usize {
self.raw_size
}
pub fn init(&mut self, heap_start: usize, heap_end: usize) { pub fn init(&mut self, heap_start: usize, heap_end: usize) {
self.start_address = heap_start as *mut HeapHeader; self.start_address = heap_start as *mut HeapHeader;
self.end_address = heap_end as *mut HeapHeader; self.end_address = heap_end as *mut HeapHeader;
+3 -3
View File
@@ -29,7 +29,7 @@ fn test_heap_allocation() {
assert_eq!(actual_alloc_size % MIN_BLOCK_SIZE, 0); assert_eq!(actual_alloc_size % MIN_BLOCK_SIZE, 0);
// Verify section is occupied // Verify section is occupied
assert!(!(*malloc_header).free); assert!((*malloc_header).free == false);
// Verify next header has been created // Verify next header has been created
let next = (*malloc_header).next.unwrap(); let next = (*malloc_header).next.unwrap();
@@ -55,7 +55,7 @@ fn test_full_heap() {
let malloc = heap.malloc(malloc_size).unwrap(); let malloc = heap.malloc(malloc_size).unwrap();
let malloc_header = Heap::get_header_ref_from_data_pointer(malloc); let malloc_header = Heap::get_header_ref_from_data_pointer(malloc);
unsafe { unsafe {
assert!(!(*malloc_header).free); assert_eq!((*malloc_header).free, false);
assert!((*malloc_header).next.is_none()); assert!((*malloc_header).next.is_none());
} }
@@ -79,7 +79,7 @@ fn test_freeing_root() {
let malloc = heap.malloc(malloc_size).unwrap(); let malloc = heap.malloc(malloc_size).unwrap();
let malloc_header = Heap::get_header_ref_from_data_pointer(malloc); let malloc_header = Heap::get_header_ref_from_data_pointer(malloc);
unsafe { unsafe {
assert!(!(*malloc_header).free); assert_eq!((*malloc_header).free, false);
assert!((*malloc_header).size >= malloc_size); assert!((*malloc_header).size >= malloc_size);
assert!((*root_header).next.is_some()); assert!((*root_header).next.is_some());
+1 -3
View File
@@ -5,12 +5,10 @@ use core::prelude::rust_2024::derive;
#[derive(Debug)] #[derive(Debug)]
pub enum NovaError { pub enum NovaError {
General(&'static str),
Mailbox, Mailbox,
HeapFull, HeapFull,
EmptyHeapSegmentNotAllowed, EmptyHeapSegmentNotAllowed,
Misalignment, Misalignment,
InvalidGranularity, InvalidGranularity,
Paging(&'static str), Paging,
OutOfMeomory,
} }