feat: UART terminal (#7)

* feat: basic terminal

* feat: execute application via terminal
This commit is contained in:
Alexander Neuhäuser
2026-03-24 17:50:28 +01:00
committed by GitHub
parent 34a66ff87a
commit f33100d36b
16 changed files with 232 additions and 150 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ sd.img
settings.json
.DS_Store
.venv
.nvimlog

9
.vscode/launch.json vendored
View File

@@ -58,15 +58,12 @@
],
"preLaunchTask": "Run QEMU wo window"
},
{
"name": "Attach LLDB",
"name": "LLDB",
"type": "lldb",
"request": "attach",
"debugServer": 1234,
"request": "launch",
"program": "${workspaceFolder}/target/aarch64-unknown-none/debug/nova",
"stopOnEntry": true,
"processCreateCommands": ["gdb-remote localhost:1234"]
"preLaunchTask": "Run QEMU wo window"
}
]
}

7
Cargo.lock generated
View File

@@ -40,12 +40,19 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[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 = [
"heap",
"libm",
"log",
"nova_error",
"paste",
]

View File

@@ -17,6 +17,7 @@ libm = "0.2.15"
heap = {path = "workspace/heap"}
nova_error = {path = "workspace/nova_error"}
paste = "1.0.15"
log = "0.4.29"
[workspace]

View File

@@ -106,6 +106,18 @@ pub enum PhysSource {
#[repr(align(4096))]
pub struct PageTable([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]
@@ -197,7 +209,7 @@ fn map_range_dynamic(
/// Allocate a singe page.
pub fn alloc_page(
virtual_address: usize,
virtual_address: VirtAddr,
base_table: *mut PageTable,
additional_flags: u64,
) -> Result<(), NovaError> {
@@ -209,6 +221,28 @@ 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(GRANULARITY) {
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(virt_addr) = table.next() {
return Ok(virt_addr);
}
Err(NovaError::OutOfMeomory)
}
/// Allocate a single page at an explicit `physical_address`.
pub fn alloc_page_explicit(
virtual_address: usize,

View File

@@ -77,7 +77,7 @@ configure_mmu_el1:
.align 4
.global el1_to_el0
el1_to_el0:
mov x8, x0
// Set SPSR_EL1: return to EL0t
mov x0, #(0b0000)
msr SPSR_EL1, x0
@@ -93,5 +93,6 @@ el1_to_el0:
isb
mov x0, x8
// Return to EL0
eret

View File

@@ -19,6 +19,8 @@ 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;
pub const APPLICATION_TRANSLATION_TABLE_VA: VirtAddr = 0xFFFF_FF81_FE00_0000;
extern "C" {
static __text_end: u64;
static __share_end: u64;
@@ -99,6 +101,7 @@ pub fn initialize_mmu_translation_tables() {
)
.unwrap();
// Allocate Mailbox buffer
{
let addr = reserve_page();
unsafe { MAILBOX_PHYSICAL_ADDRESS = Some(addr) };

View File

@@ -4,10 +4,8 @@ mod bitmaps;
use bitmaps::BASIC_LEGACY;
use crate::{
pi3::mailbox::{read_mailbox, write_mailbox},
println,
};
use crate::pi3::mailbox::{read_mailbox, write_mailbox};
use log::error;
#[repr(align(16))]
struct Mailbox([u32; 36]);
@@ -237,7 +235,7 @@ impl Default for FrameBuffer {
let _ = read_mailbox(8);
if mailbox.0[1] == 0 {
println!("Failed");
error!("Mailbox request was not processed!");
}
mailbox.0[28] &= 0x3FFFFFFF;

View File

@@ -2,8 +2,9 @@ use core::arch::asm;
use crate::{
aarch64::registers::{daif::mask_all, read_esr_el1, read_exception_source_el},
get_current_el, println,
get_current_el,
};
use log::debug;
const INTERRUPT_BASE: u32 = 0x3F00_B000;
const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204;
@@ -66,17 +67,17 @@ unsafe extern "C" fn rust_synchronous_interrupt_no_el_change() {
mask_all();
let source_el = read_exception_source_el() >> 2;
println!("--------Sync Exception in EL{}--------", source_el);
println!("No EL change");
println!("Current EL: {}", get_current_el());
println!("{:?}", EsrElX::from(read_esr_el1()));
println!("Return register address: {:#x}", read_esr_el1());
println!("-------------------------------------");
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_main() {
fn set_return_to_kernel_loop() {
unsafe {
asm!("ldr x0, =kernel_main", "msr ELR_EL1, x0");
asm!("ldr x0, =kernel_loop", "msr ELR_EL1, x0");
asm!("mov x0, #(0b0101)", "msr SPSR_EL1, x0");
}
}

View File

@@ -12,9 +12,11 @@ use crate::{
gpio::{read_gpio_event_detect_status, reset_gpio_event_detect_status},
uart::clear_uart_interrupt_state,
},
println, read_address, write_address,
read_address, write_address,
};
use alloc::vec::Vec;
use log::{debug, info};
struct InterruptHandlers {
source: IRQSource,
function: fn(),
@@ -60,9 +62,9 @@ unsafe extern "C" fn rust_irq_handler() {
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());
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) } {
@@ -76,7 +78,7 @@ unsafe extern "C" fn rust_irq_handler() {
}
fn handle_gpio_interrupt() {
println!("Interrupt");
debug!("GPIO interrupt triggered");
for i in 0..=53u32 {
let val = read_gpio_event_detect_status(i);
@@ -84,7 +86,7 @@ fn handle_gpio_interrupt() {
#[allow(clippy::single_match)]
match i {
26 => {
println!("Button Pressed");
info!("Button Pressed");
}
_ => {}
}

View File

@@ -1,11 +1,12 @@
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_main, EsrElX, TrapFrame},
interrupt_handlers::{set_return_to_kernel_loop, EsrElX, TrapFrame},
pi3::mailbox,
println,
};
use log::{debug, error, warn};
/// Synchronous Exception Handler
///
/// Source is a lower Exception level, where the implemented level
@@ -15,22 +16,23 @@ use crate::{
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 => {
log_sync_exception();
println!("Cause: Data Abort from a lower Exception level");
warn!("Cause: Data Abort from a lower Exception level");
}
0b010101 => {
println!("Cause: SVC instruction execution in AArch64");
debug!("Cause: SVC instruction execution in AArch64");
return handle_svc(frame);
}
_ => {
println!("Unknown Error Code: {:b}", esr.ec);
error!("Synchronous interrupt: Unknown Error Code: {:b}", esr.ec);
}
}
println!("Returning to kernel main...");
set_return_to_kernel_main();
warn!("UnhandledException -> Returning to kernel...");
set_return_to_kernel_loop();
0
}
@@ -46,11 +48,11 @@ fn handle_svc(frame: &mut TrapFrame) -> usize {
fn log_sync_exception() {
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());
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());
println!("{:?}", esr);
println!("Return address: {:#x}", read_elr_el1());
println!("-------------------------------------");
debug!("{:?}", esr);
debug!("Return address: {:#x}", read_elr_el1());
debug!("-------------------------------------");
}

View File

@@ -3,12 +3,13 @@
extern crate alloc;
use alloc::boxed::Box;
use core::{
arch::asm,
panic::PanicInfo,
ptr::{read_volatile, write_volatile},
};
use log::LevelFilter;
use log::{Level, Metadata, Record};
use heap::Heap;
@@ -18,10 +19,11 @@ use crate::{
WRITABLE,
},
interrupt_handlers::irq::initialize_interrupt_handler,
logger::DefaultLogger,
pi3::timer::sleep_s,
terminal::{flush_terminal, init_terminal},
};
static LOGGER: UartLogger = UartLogger;
static PERIPHERAL_BASE: usize = 0x3F00_0000;
unsafe extern "C" {
@@ -31,7 +33,7 @@ unsafe extern "C" {
#[global_allocator]
pub static mut GLOBAL_ALLOCATOR: Heap = Heap::empty();
pub unsafe fn init_kernel_heap() {
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;
@@ -54,9 +56,9 @@ pub mod aarch64;
pub mod configuration;
pub mod framebuffer;
pub mod interrupt_handlers;
pub mod logger;
pub mod pi3;
pub mod terminal;
#[inline(always)]
pub unsafe fn read_address(address: u32) -> u32 {
@@ -80,14 +82,33 @@ pub fn get_current_el() -> u64 {
el >> 2
}
static mut KERNEL_INITIALIZED: bool = false;
pub fn initialize_kernel() {
if unsafe { KERNEL_INITIALIZED } {
return;
}
unsafe { init_kernel_heap() };
logger::set_logger(Box::new(DefaultLogger));
unsafe { initialize_kernel_heap() };
initialize_interrupt_handler();
unsafe { KERNEL_INITIALIZED = true };
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();
}

View File

@@ -1,45 +0,0 @@
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);
}
}

View File

@@ -6,6 +6,7 @@ use core::{
arch::{asm, global_asm},
ptr::write_volatile,
};
use log::{debug, info};
extern crate alloc;
@@ -14,7 +15,7 @@ use nova::{
aarch64::registers::{daif, read_id_aa64mmfr0_el1},
configuration::memory_mapping::initialize_mmu_translation_tables,
framebuffer::{FrameBuffer, BLUE, GREEN, RED},
get_current_el,
get_current_el, init_logger,
interrupt_handlers::irq::{enable_irq_source, IRQSource},
peripherals::{
gpio::{
@@ -23,7 +24,7 @@ use nova::{
},
uart::uart_init,
},
println,
print, println,
};
global_asm!(include_str!("vector.S"));
@@ -33,7 +34,6 @@ static mut FRAMEBUFFER: Option<FrameBuffer> = None;
extern "C" {
fn el2_to_el1();
fn el1_to_el0();
fn configure_mmu_el1();
static mut __bss_start: u32;
static mut __bss_end: u32;
@@ -60,16 +60,18 @@ pub extern "C" fn main() -> ! {
// Set ACT Led to Outout
let _ = set_gpio_function(21, GPIOFunction::Output);
init_logger();
println!("Hello World!");
println!("Exception level: {}", get_current_el());
info!("Hello World!");
info!("Current exception level: {}", get_current_el());
info!("initializing MMU...");
initialize_mmu_translation_tables();
unsafe { configure_mmu_el1() };
println!("MMU initialized...");
info!("MMU configured!");
println!("Register: AA64MMFR0_EL1: {:064b}", read_id_aa64mmfr0_el1());
println!("Moving El2->EL1");
debug!("Register: AA64MMFR0_EL1: {:064b}", read_id_aa64mmfr0_el1());
info!("Moving El2->EL1");
unsafe { FRAMEBUFFER = Some(FrameBuffer::default()) };
unsafe {
@@ -89,28 +91,32 @@ unsafe fn zero_bss() {
}
#[no_mangle]
pub extern "C" fn kernel_main() -> ! {
println!("Kernel Start...");
pub extern "C" fn kernel_main() {
nova::initialize_kernel();
info!("Kernel Initialized...");
info!("Current exception Level: {}", get_current_el());
let mut test_vector = Vec::new();
for i in 0..20 {
test_vector.push(i);
}
println!("heap allocation test: {:?}", test_vector);
debug!("heap allocation test: {:?}", test_vector);
println!("Exception Level: {}", get_current_el());
enable_irq_source(IRQSource::UartInt);
kernel_loop();
}
#[no_mangle]
pub extern "C" fn kernel_loop() {
daif::unmask_all();
unsafe {
el1_to_el0();
};
#[allow(clippy::empty_loop)]
loop {}
}
#[no_mangle]
pub extern "C" fn el0() -> ! {
pub extern "C" fn el0(input: usize) {
println!("Jumped into EL0");
// Set GPIO 26 to Input
@@ -119,8 +125,6 @@ pub extern "C" fn el0() -> ! {
gpio_pull_up(26);
set_falling_edge_detect(26, true);
enable_irq_source(IRQSource::UartInt);
if let Some(fb) = unsafe { FRAMEBUFFER.as_mut() } {
for i in 0..1080 {
fb.draw_pixel(50, i, BLUE);
@@ -134,13 +138,28 @@ pub extern "C" fn el0() -> ! {
fb.draw_function(cos, 0, 101, RED);
}
loop {
let temp = syscall(67);
println!("{} °C", temp / 1000);
let _temp = syscall(67);
println!("Calculting prime to: {}", input);
for i in 3..input {
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!("");
blink_gpio(SpecificGpio::OnboardLed as u8, 500);
}
}
fn cos(x: u32) -> f64 {
libm::cos(x as f64 * 0.1) * 20.0

View File

@@ -1,53 +1,92 @@
use core::fmt::Write;
use core::arch::asm;
use alloc::string::String;
use nova::{
interrupt_handlers::register_interrupt_handler, logger::Logger,
peripherals::uart::read_uart_data, print, println,
use crate::{
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 {
buffer: String,
input: String,
}
extern "C" {
fn el1_to_el0();
}
impl Default for Terminal {
fn default() -> Self {
Self::new()
}
}
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();
print!("\n> {}", self.input);
}
fn exec(&mut self) {
print!("\n");
let val = self.input.clone();
self.input.clear();
match val.as_str() {
"temp" => {
println!("{}", read_soc_temp([0]).unwrap()[1]);
}
"el0" => unsafe {
let i = 69;
asm!("", in("x0") i);
el1_to_el0();
},
_ => {
println!("Unknown command: \"{}\"", self.input);
}
}
self.input.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();
}
pub fn init_terminal() {
unsafe { TERMINAL = Some(Terminal::new()) };
register_terminal_interrupt_handler();
}
fn terminal_uart_rx_interrupt_handler() {
print!("{}", read_uart_data());
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 register_terminal_interrupt_handler() {
register_interrupt_handler(
nova::interrupt_handlers::IRQSource::UartInt,
terminal_uart_rx_interrupt_handler,
);
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);
}

View File

@@ -11,4 +11,5 @@ pub enum NovaError {
Misalignment,
InvalidGranularity,
Paging,
OutOfMeomory,
}