From 74089ae2365d2bf51262e8f4ebdea0b35fa91511 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 28 May 2023 16:50:41 +0100 Subject: [PATCH 01/14] Add an object for managing the TPA. Also involves moving the filesystem code, and a new command called "loadf" which can load applications from Flash. --- neotron-flash-0002.ld | 6 +- neotron-flash-0802.ld | 6 +- neotron-flash-1002.ld | 14 +++- src/bin/flash0002.rs | 2 +- src/bin/flash0802.rs | 2 +- src/bin/flash1002.rs | 2 +- src/commands/config.rs | 2 +- src/commands/fs.rs | 118 +++-------------------------- src/commands/mod.rs | 3 +- src/commands/ram.rs | 97 +++++++++++++++--------- src/fs.rs | 83 +++++++++++++++++++++ src/lib.rs | 43 +++++++++-- src/program.rs | 165 +++++++++++++++++++++++++++++++++++++++++ 13 files changed, 380 insertions(+), 163 deletions(-) create mode 100644 src/fs.rs create mode 100644 src/program.rs diff --git a/neotron-flash-0002.ld b/neotron-flash-0002.ld index f7d3a28..a7f56ea 100644 --- a/neotron-flash-0002.ld +++ b/neotron-flash-0002.ld @@ -32,7 +32,11 @@ MEMORY /* # Entry point = what the BIOS calls to start the OS */ ENTRY(main); -EXTERN(__RESET_VECTOR); + +/* +Where the Transient Program Area starts. +*/ +_tpa_start = ORIGIN(RAM) + LENGTH(RAM); /* # Sections */ SECTIONS diff --git a/neotron-flash-0802.ld b/neotron-flash-0802.ld index 2859cbe..cd36166 100644 --- a/neotron-flash-0802.ld +++ b/neotron-flash-0802.ld @@ -32,7 +32,11 @@ MEMORY /* # Entry point = what the BIOS calls to start the OS */ ENTRY(main); -EXTERN(__RESET_VECTOR); + +/* +Where the Transient Program Area starts. +*/ +_tpa_start = ORIGIN(RAM) + LENGTH(RAM); /* # Sections */ SECTIONS diff --git a/neotron-flash-1002.ld b/neotron-flash-1002.ld index 9a09f2c..d2e0f2a 100644 --- a/neotron-flash-1002.ld +++ b/neotron-flash-1002.ld @@ -23,16 +23,24 @@ MEMORY { /* The first 128 KiB is for the BIOS. We get the rest. */ FLASH (rx) : ORIGIN = 0x10020000, LENGTH = 128K + /* - * We get the bottom 4KB of RAM. Anything above that is for applications - * (up to wherever the BIOS tells us we can use.) + * The RAM reserved for the OS. Above this is the Transient Program Area. + * + * This is defined by the Neotron specification for a given platform. On this + * Cortex-M based platform, it's the start of Cortex-M SRAM, plus 4 KiB, or + * 0x2000_1000. */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 4K } /* # Entry point = what the BIOS calls to start the OS */ ENTRY(main); -EXTERN(__RESET_VECTOR); + +/* +Where the Transient Program Area starts. +*/ +_tpa_start = ORIGIN(RAM) + LENGTH(RAM); /* # Sections */ SECTIONS diff --git a/src/bin/flash0002.rs b/src/bin/flash0002.rs index 289dca3..4bf5162 100644 --- a/src/bin/flash0002.rs +++ b/src/bin/flash0002.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(*const neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; diff --git a/src/bin/flash0802.rs b/src/bin/flash0802.rs index ab8e020..c1ddc87 100644 --- a/src/bin/flash0802.rs +++ b/src/bin/flash0802.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(*const neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; diff --git a/src/bin/flash1002.rs b/src/bin/flash1002.rs index 9e44849..7be5837 100644 --- a/src/bin/flash1002.rs +++ b/src/bin/flash1002.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(*const neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; diff --git a/src/commands/config.rs b/src/commands/config.rs index 18b5f8d..fedf676 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -22,7 +22,7 @@ pub static COMMAND_ITEM: menu::Item = menu::Item { /// Called when the "config" command is executed. fn command(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], ctx: &mut Ctx) { - let command = args.get(0).cloned().unwrap_or("print"); + let command = args.first().cloned().unwrap_or("print"); match command { "reset" => match config::Config::load() { Ok(new_config) => { diff --git a/src/commands/fs.rs b/src/commands/fs.rs index fae0138..e750d20 100644 --- a/src/commands/fs.rs +++ b/src/commands/fs.rs @@ -1,9 +1,8 @@ //! File Systems related commands for Neotron OS -use chrono::{Datelike, Timelike}; use embedded_sdmmc::VolumeIdx; -use crate::{bios, print, println, Ctx, API}; +use crate::{bios, print, println, Ctx}; pub static DIR_ITEM: menu::Item = menu::Item { item_type: menu::ItemType::Callback { @@ -14,7 +13,6 @@ pub static DIR_ITEM: menu::Item = menu::Item { help: Some("Dir the root directory on block device 0"), }; -#[cfg(target_os = "none")] pub static LOAD_ITEM: menu::Item = menu::Item { item_type: menu::ItemType::Callback { function: load, @@ -27,89 +25,12 @@ pub static LOAD_ITEM: menu::Item = menu::Item { help: Some("Load a file into the application area"), }; -struct BiosBlock(); - -impl embedded_sdmmc::BlockDevice for BiosBlock { - type Error = bios::Error; - - fn read( - &self, - blocks: &mut [embedded_sdmmc::Block], - start_block_idx: embedded_sdmmc::BlockIdx, - _reason: &str, - ) -> Result<(), Self::Error> { - let api = API.get(); - let byte_slice = unsafe { - core::slice::from_raw_parts_mut( - blocks.as_mut_ptr() as *mut u8, - blocks.len() * embedded_sdmmc::Block::LEN, - ) - }; - match (api.block_read)( - 0, - bios::block_dev::BlockIdx(u64::from(start_block_idx.0)), - blocks.len() as u8, - bios::ApiBuffer::new(byte_slice), - ) { - bios::Result::Ok(_) => Ok(()), - bios::Result::Err(e) => Err(e), - } - } - - fn write( - &self, - blocks: &[embedded_sdmmc::Block], - start_block_idx: embedded_sdmmc::BlockIdx, - ) -> Result<(), Self::Error> { - let api = API.get(); - let byte_slice = unsafe { - core::slice::from_raw_parts( - blocks.as_ptr() as *const u8, - blocks.len() * embedded_sdmmc::Block::LEN, - ) - }; - match (api.block_write)( - 0, - bios::block_dev::BlockIdx(u64::from(start_block_idx.0)), - blocks.len() as u8, - bios::ApiByteSlice::new(byte_slice), - ) { - bios::Result::Ok(_) => Ok(()), - bios::Result::Err(e) => Err(e), - } - } - - fn num_blocks(&self) -> Result { - let api = API.get(); - match (api.block_dev_get_info)(0) { - bios::Option::Some(info) => Ok(embedded_sdmmc::BlockCount(info.num_blocks as u32)), - bios::Option::None => Err(bios::Error::InvalidDevice), - } - } -} - -struct BiosTime(); - -impl embedded_sdmmc::TimeSource for BiosTime { - fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { - let time = API.get_time(); - embedded_sdmmc::Timestamp { - year_since_1970: (time.year() - 1970) as u8, - zero_indexed_month: time.month0() as u8, - zero_indexed_day: time.day0() as u8, - hours: time.hour() as u8, - minutes: time.minute() as u8, - seconds: time.second() as u8, - } - } -} - /// Called when the "dir" command is executed. fn dir(_menu: &menu::Menu, _item: &menu::Item, _args: &[&str], _ctx: &mut Ctx) { fn work() -> Result<(), embedded_sdmmc::Error> { println!("Listing files on Block Device 0, /"); - let bios_block = BiosBlock(); - let time = BiosTime(); + let bios_block = crate::fs::BiosBlock(); + let time = crate::fs::BiosTime(); let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time); // Open the first partition let volume = mgr.get_volume(VolumeIdx(0))?; @@ -165,33 +86,12 @@ fn dir(_menu: &menu::Menu, _item: &menu::Item, _args: &[&str], _ctx: & } /// Called when the "load" command is executed. -#[cfg(target_os = "none")] -fn load(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], _ctx: &mut Ctx) { - fn work(args: &[&str]) -> Result<(), embedded_sdmmc::Error> { - println!("Loading /{} from Block Device 0", args[0]); - let bios_block = BiosBlock(); - let time = BiosTime(); - let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time); - // Open the first partition - let mut volume = mgr.get_volume(VolumeIdx(0))?; - let root_dir = mgr.open_root_dir(&volume)?; - let mut file = mgr.open_file_in_dir( - &mut volume, - &root_dir, - args[0], - embedded_sdmmc::Mode::ReadOnly, - )?; - let file_length = file.length(); - // Application space starts 4K into Cortex-M SRAM - const APPLICATION_START_ADDR: usize = 0x2000_1000; - let application_ram: &'static mut [u8] = unsafe { - core::slice::from_raw_parts_mut(APPLICATION_START_ADDR as *mut u8, file_length as usize) - }; - mgr.read(&mut volume, &mut file, application_ram)?; - Ok(()) - } - - match work(args) { +fn load(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], ctx: &mut Ctx) { + let Some(filename) = args.first() else { + println!("Need a filename"); + return; + }; + match ctx.tpa.load_program(filename) { Ok(_) => {} Err(e) => { println!("Error: {:?}", e); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c316eb3..2b83f3a 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -23,9 +23,8 @@ pub static OS_MENU: menu::Menu = menu::Menu { &fs::DIR_ITEM, &hardware::LSHW_ITEM, &ram::HEXDUMP_ITEM, - #[cfg(target_os = "none")] &ram::RUN_ITEM, - #[cfg(target_os = "none")] + &ram::LOAD_ITEM, &fs::LOAD_ITEM, &screen::CLEAR_ITEM, &screen::BENCH_ITEM, diff --git a/src/commands/ram.rs b/src/commands/ram.rs index 5527bfd..99d191c 100644 --- a/src/commands/ram.rs +++ b/src/commands/ram.rs @@ -20,7 +20,6 @@ pub static HEXDUMP_ITEM: menu::Item = menu::Item { help: Some("Dump the contents of RAM as hex"), }; -#[cfg(target_os = "none")] pub static RUN_ITEM: menu::Item = menu::Item { item_type: menu::ItemType::Callback { function: run, @@ -30,13 +29,31 @@ pub static RUN_ITEM: menu::Item = menu::Item { help: Some("Jump to start of application area"), }; +pub static LOAD_ITEM: menu::Item = menu::Item { + item_type: menu::ItemType::Callback { + function: loadf, + parameters: &[ + menu::Parameter::Mandatory { + parameter_name: "address", + help: Some("The address to load from"), + }, + menu::Parameter::Mandatory { + parameter_name: "length", + help: Some("The number of bytes to load"), + }, + ], + }, + command: "loadf", + help: Some("Copy a program from ROM/RAM into the application area"), +}; + fn parse_usize(input: &str) -> Result { if let Some(digits) = input.strip_prefix("0x") { // Parse as hex usize::from_str_radix(digits, 16) } else { // Parse as decimal - usize::from_str_radix(input, 10) + input.parse::() } } @@ -47,7 +64,7 @@ fn parse_usize(input: &str) -> Result { fn hexdump(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], _ctx: &mut Ctx) { const BYTES_PER_LINE: usize = 16; - let Some(address_str) = args.get(0) else { + let Some(address_str) = args.first() else { println!("No address"); return; }; @@ -81,43 +98,51 @@ fn hexdump(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], _ctx println!(); } -#[allow(unused)] -#[repr(C)] -pub struct Api { - pub print: extern "C" fn(data: *const u8, len: usize), -} - -#[allow(unused)] -static CALLBACK_TABLE: Api = Api { print: print_fn }; - -#[allow(unused)] -extern "C" fn print_fn(data: *const u8, len: usize) { - let slice = unsafe { core::slice::from_raw_parts(data, len) }; - if let Ok(s) = core::str::from_utf8(slice) { - print!("{}", s); - } else { - // Ignore App output - not UTF-8 +/// Called when the "run" command is executed. +fn run(_menu: &menu::Menu, _item: &menu::Item, _args: &[&str], ctx: &mut Ctx) { + match ctx.tpa.execute() { + Ok(0) => { + println!(); + } + Ok(n) => { + println!("\nError Code: {}", n); + } + Err(e) => { + println!("\nFailed to execute: {:?}", e); + } } } -/// Called when the "run" command is executed. -#[cfg(target_os = "none")] -fn run(_menu: &menu::Menu, _item: &menu::Item, _args: &[&str], _ctx: &mut Ctx) { - use core::convert::TryInto; - const APPLICATION_START_ADDR: usize = 0x2000_1000; - const APPLICATION_LEN: usize = 4096; - // Application space starts 4K into Cortex-M SRAM - let application_ram: &'static mut [u8] = unsafe { - core::slice::from_raw_parts_mut(APPLICATION_START_ADDR as *mut u8, APPLICATION_LEN) +/// Called when the "loadf" command is executed. +/// +/// If you ask for an address that generates a HardFault, the OS will crash. So +/// don't. +fn loadf(_menu: &menu::Menu, _item: &menu::Item, args: &[&str], ctx: &mut Ctx) { + let Some(address_str) = args.first() else { + println!("No address"); + return; }; - let start_word: [u8; 4] = (&application_ram[0..4]).try_into().unwrap(); - let start_ptr = usize::from_le_bytes(start_word) as *const (); - let result = unsafe { - let code: extern "C" fn(*const Api) -> u32 = ::core::mem::transmute(start_ptr); - code(&CALLBACK_TABLE) + let Ok(address) = parse_usize(address_str) else { + println!("Bad address"); + return; }; - println!(); - if result != 0 { - println!("Got error code {}", result); + let Some(len_str) = args.get(1) else { + println!("No length"); + return; + }; + let Ok(len) = parse_usize(len_str) else { + println!("Bad length"); + return; + }; + + let src_slice = unsafe { core::slice::from_raw_parts(address as *const u8, len) }; + + match ctx.tpa.copy_program(src_slice) { + Ok(_) => { + println!("Ok"); + } + Err(e) => { + println!("Error: {:?}", e); + } } } diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..129a65f --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,83 @@ +//! Filesystem related types + +use chrono::{Datelike, Timelike}; +use neotron_common_bios as bios; + +use crate::API; + +pub struct BiosBlock(); + +impl embedded_sdmmc::BlockDevice for BiosBlock { + type Error = bios::Error; + + fn read( + &self, + blocks: &mut [embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + _reason: &str, + ) -> Result<(), Self::Error> { + let api = API.get(); + let byte_slice = unsafe { + core::slice::from_raw_parts_mut( + blocks.as_mut_ptr() as *mut u8, + blocks.len() * embedded_sdmmc::Block::LEN, + ) + }; + match (api.block_read)( + 0, + bios::block_dev::BlockIdx(u64::from(start_block_idx.0)), + blocks.len() as u8, + bios::ApiBuffer::new(byte_slice), + ) { + bios::Result::Ok(_) => Ok(()), + bios::Result::Err(e) => Err(e), + } + } + + fn write( + &self, + blocks: &[embedded_sdmmc::Block], + start_block_idx: embedded_sdmmc::BlockIdx, + ) -> Result<(), Self::Error> { + let api = API.get(); + let byte_slice = unsafe { + core::slice::from_raw_parts( + blocks.as_ptr() as *const u8, + blocks.len() * embedded_sdmmc::Block::LEN, + ) + }; + match (api.block_write)( + 0, + bios::block_dev::BlockIdx(u64::from(start_block_idx.0)), + blocks.len() as u8, + bios::ApiByteSlice::new(byte_slice), + ) { + bios::Result::Ok(_) => Ok(()), + bios::Result::Err(e) => Err(e), + } + } + + fn num_blocks(&self) -> Result { + let api = API.get(); + match (api.block_dev_get_info)(0) { + bios::Option::Some(info) => Ok(embedded_sdmmc::BlockCount(info.num_blocks as u32)), + bios::Option::None => Err(bios::Error::InvalidDevice), + } + } +} + +pub struct BiosTime(); + +impl embedded_sdmmc::TimeSource for BiosTime { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + let time = API.get_time(); + embedded_sdmmc::Timestamp { + year_since_1970: (time.year() - 1970) as u8, + zero_indexed_month: time.month0() as u8, + zero_indexed_day: time.day0() as u8, + hours: time.hour() as u8, + minutes: time.minute() as u8, + seconds: time.second() as u8, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dbdb67d..deb7fff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ use neotron_common_bios as bios; mod commands; mod config; +mod fs; +mod program; mod vgaconsole; // =========================================================================== @@ -48,12 +50,12 @@ static IS_PANIC: AtomicBool = AtomicBool::new(false); #[macro_export] macro_rules! print { ($($arg:tt)*) => { - if let Some(ref mut console) = unsafe { &mut crate::VGA_CONSOLE } { + if let Some(ref mut console) = unsafe { &mut $crate::VGA_CONSOLE } { #[allow(unused)] use core::fmt::Write as _; write!(console, $($arg)*).unwrap(); } - if let Some(ref mut console) = unsafe { &mut crate::SERIAL_CONSOLE } { + if let Some(ref mut console) = unsafe { &mut $crate::SERIAL_CONSOLE } { #[allow(unused)] use core::fmt::Write as _; write!(console, $($arg)*).unwrap(); @@ -64,10 +66,10 @@ macro_rules! print { /// Prints to the screen and puts a new-line on the end #[macro_export] macro_rules! println { - () => (crate::print!("\n")); + () => ($crate::print!("\n")); ($($arg:tt)*) => { - crate::print!($($arg)*); - crate::print!("\n"); + $crate::print!($($arg)*); + $crate::print!("\n"); }; } @@ -151,6 +153,7 @@ impl core::fmt::Write for SerialConsole { pub struct Ctx { config: config::Config, keyboard: pc_keyboard::EventDecoder, + tpa: program::TransientProgramArea, } impl core::fmt::Write for Ctx { @@ -195,7 +198,7 @@ unsafe fn start_up_init() { /// This is the function the BIOS calls. This is because we store the address /// of this function in the ENTRY_POINT_ADDR variable. #[no_mangle] -pub extern "C" fn main(api: *const bios::Api) -> ! { +pub extern "C" fn main(api: &bios::Api) -> ! { unsafe { start_up_init(); API.store(api); @@ -242,14 +245,40 @@ pub extern "C" fn main(api: *const bios::Api) -> ! { println!("Welcome to {}!", OS_VERSION); println!("Copyright © Jonathan 'theJPster' Pallant and the Neotron Developers, 2022"); - let ctx = Ctx { + let (tpa_start, tpa_size) = match (api.memory_get_region)(0) { + bios::Option::None => { + panic!("No TPA offered by BIOS!"); + } + bios::Option::Some(tpa) => { + if tpa.length < 256 { + panic!("TPA not large enough"); + } + let offset = tpa.start.align_offset(4); + ( + unsafe { tpa.start.add(offset) as *mut u32 }, + tpa.length - offset, + ) + } + }; + + let mut ctx = Ctx { config, keyboard: pc_keyboard::EventDecoder::new( pc_keyboard::layouts::AnyLayout::Uk105Key(pc_keyboard::layouts::Uk105Key), pc_keyboard::HandleControl::MapLettersToUnicode, ), + tpa: unsafe { + // We have to trust the values given to us by the BIOS. If it lies, we will crash. + program::TransientProgramArea::new(tpa_start, tpa_size) + }, }; + println!( + "TPA: {} bytes @ {:p}", + ctx.tpa.as_slice_u8().len(), + ctx.tpa.as_slice_u8().as_ptr() + ); + let mut buffer = [0u8; 256]; let mut menu = menu::Runner::new(&commands::OS_MENU, &mut buffer, ctx); diff --git a/src/program.rs b/src/program.rs new file mode 100644 index 0000000..4e44a54 --- /dev/null +++ b/src/program.rs @@ -0,0 +1,165 @@ +//! Program Loading and Execution + +use crate::{print, println}; + +#[allow(unused)] +static CALLBACK_TABLE: Api = Api { print: print_fn }; + +/// Ways in which loading a program can fail. +#[derive(Debug)] +pub enum Error { + /// The file was too large for RAM. + ProgramTooLarge, + /// A filesystem error occurred + Filesystem(embedded_sdmmc::Error), +} + +impl From> for Error { + fn from(value: embedded_sdmmc::Error) -> Self { + Error::Filesystem(value) + } +} + +#[allow(unused)] +#[repr(C)] +/// The API we give to applications. +pub struct Api { + pub print: extern "C" fn(data: *const u8, len: usize), +} + +/// Represents the Transient Program Area. +/// +/// This is a piece of memory that can be used for loading and executing programs. +/// +/// Only one program can be executed at a time. +pub struct TransientProgramArea { + memory_bottom: *mut u32, + memory_top: *mut u32, +} + +extern "C" { + #[cfg(all(target_os = "none", target_arch = "arm"))] + static mut _tpa_start: u32; +} + +impl TransientProgramArea { + /// Construct a new [`TransientProgramArea`]. + pub unsafe fn new(start: *mut u32, length_in_bytes: usize) -> TransientProgramArea { + let mut tpa = TransientProgramArea { + memory_bottom: start, + memory_top: start.add(length_in_bytes / core::mem::size_of::()), + }; + + // You have to take the address of a linker symbol to find out where + // points to, as the linker can only invent symbols pointing at + // addresses; it cannot actually put values in RAM. + #[cfg(all(target_os = "none", target_arch = "arm"))] + let official_tpa_start: Option<*mut u32> = Some((&mut _tpa_start) as *mut u32); + + #[cfg(not(all(target_os = "none", target_arch = "arm")))] + let official_tpa_start: Option<*mut u32> = None; + + if let Some(tpa_start) = official_tpa_start { + let range = tpa.as_slice_u32().as_ptr_range(); + if !range.contains(&(tpa_start as *const u32)) { + panic!("TPA doesn't contain system start address"); + } + let offset = tpa_start.offset_from(tpa.memory_bottom); + tpa.memory_bottom = tpa.memory_bottom.offset(offset); + } + + tpa + } + + /// Borrow the TPA region as a slice of words + pub fn as_slice_u32(&mut self) -> &mut [u32] { + unsafe { + core::slice::from_raw_parts_mut( + self.memory_bottom, + self.memory_top.offset_from(self.memory_bottom) as usize, + ) + } + } + + /// Borrow the TPA region as a slice of bytes + pub fn as_slice_u8(&mut self) -> &mut [u8] { + unsafe { + core::slice::from_raw_parts_mut( + self.memory_bottom as *mut u8, + (self.memory_top.offset_from(self.memory_bottom) as usize) + * core::mem::size_of::(), + ) + } + } + + /// Loads a program from disk into the Transient Program Area. + /// + /// The program must be in the Neotron Executable format. + pub fn load_program(&mut self, file_name: &str) -> Result<(), Error> { + println!("Loading /{} from Block Device 0", file_name); + let bios_block = crate::fs::BiosBlock(); + let time = crate::fs::BiosTime(); + let mut mgr = embedded_sdmmc::VolumeManager::new(bios_block, time); + // Open the first partition + let mut volume = mgr.get_volume(embedded_sdmmc::VolumeIdx(0))?; + let root_dir = mgr.open_root_dir(&volume)?; + let mut file = mgr.open_file_in_dir( + &mut volume, + &root_dir, + file_name, + embedded_sdmmc::Mode::ReadOnly, + )?; + // Application space starts 4K into Cortex-M SRAM + let application_ram = self.as_slice_u8(); + if file.length() as usize > application_ram.len() { + return Err(Error::ProgramTooLarge); + }; + let application_ram = &mut application_ram[0..file.length() as usize]; + mgr.read(&volume, &mut file, application_ram)?; + Ok(()) + } + + /// Copy a program from memory into the Transient Program Area. + /// + /// The program must be in the Neotron Executable format. + pub fn copy_program(&mut self, program: &[u8]) -> Result<(), Error> { + let application_ram = self.as_slice_u8(); + if program.len() > application_ram.len() { + return Err(Error::ProgramTooLarge); + } + let application_ram = &mut application_ram[0..program.len()]; + application_ram.copy_from_slice(program); + Ok(()) + } + + /// Execute a program. + /// + /// If the program returns, you get `Ok()`. The program returning + /// an exit code that is non-zero is not considered a failure from the point + /// of view of this API. You wanted to run a program, and the program was + /// run. + pub fn execute(&mut self) -> Result { + let application_ram = self.as_slice_u32(); + let start_ptr = application_ram[0] as *const (); + let result = unsafe { + let code: extern "C" fn(*const Api) -> i32 = ::core::mem::transmute(start_ptr); + code(&CALLBACK_TABLE) + }; + Ok(result) + } +} + +/// Application API to print things to the console. +#[allow(unused)] +extern "C" fn print_fn(data: *const u8, len: usize) { + let slice = unsafe { core::slice::from_raw_parts(data, len) }; + if let Ok(s) = core::str::from_utf8(slice) { + print!("{}", s); + } else { + // Ignore App output - not UTF-8 + } +} + +// =========================================================================== +// End of file +// =========================================================================== From 16102cbe0eb66e3c2fefa379812c5b9002fc6ddf Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 10 Jun 2023 22:46:03 +0100 Subject: [PATCH 02/14] Use latest API crate. --- Cargo.lock | 21 +++++ Cargo.toml | 6 +- src/lib.rs | 19 ++++ src/program.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 270 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5372c17..1f66ec7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" + [[package]] name = "byteorder" version = "1.4.3" @@ -119,6 +125,14 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "neotron-api" +version = "0.1.0" +dependencies = [ + "bitflags", + "neotron-ffi", +] + [[package]] name = "neotron-common-bios" version = "0.8.0" @@ -129,6 +143,12 @@ dependencies = [ "pc-keyboard", ] +[[package]] +name = "neotron-ffi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37886e73d87732421aaf5da617eead9d69a7daf6b0d059780f76157d9ce5372" + [[package]] name = "neotron-os" version = "0.3.3" @@ -136,6 +156,7 @@ dependencies = [ "chrono", "embedded-sdmmc", "menu", + "neotron-api", "neotron-common-bios", "pc-keyboard", "postcard", diff --git a/Cargo.toml b/Cargo.toml index 8629cf3..22cda3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "neotron-os" version = "0.3.3" -authors = ["Jonathan 'theJPster' Pallant ", "The Neotron Developers"] +authors = [ + "Jonathan 'theJPster' Pallant ", + "The Neotron Developers" +] edition = "2018" description = "The Neotron Operating System" license = "GPL-3.0-or-later" @@ -46,3 +49,4 @@ serde = { version = "1.0", default-features = false } menu = "0.3" chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.5", default-features = false } +neotron-api = { path = "../neotron-api" } diff --git a/src/lib.rs b/src/lib.rs index deb7fff..ecdc88f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,25 @@ impl Api { /// Represents the serial port we can use as a text input/output device. struct SerialConsole(u8); +impl SerialConsole { + fn write_bstr(&mut self, data: &[u8]) -> core::fmt::Result { + let api = API.get(); + let is_panic = IS_PANIC.load(Ordering::SeqCst); + let res = (api.serial_write)( + // Which port + self.0, + // Data + bios::ApiByteSlice::new(data), + // No timeout + bios::Option::None, + ); + if !is_panic { + res.unwrap(); + } + Ok(()) + } +} + impl core::fmt::Write for SerialConsole { fn write_str(&mut self, data: &str) -> core::fmt::Result { let api = API.get(); diff --git a/src/program.rs b/src/program.rs index 4e44a54..86afced 100644 --- a/src/program.rs +++ b/src/program.rs @@ -3,7 +3,29 @@ use crate::{print, println}; #[allow(unused)] -static CALLBACK_TABLE: Api = Api { print: print_fn }; +static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { + open: api_open, + close: api_close, + write: api_write, + read: api_read, + seek_set: api_seek_set, + seek_cur: api_seek_cur, + seek_end: api_seek_end, + rename: api_rename, + ioctl: api_ioctl, + opendir: api_opendir, + closedir: api_closedir, + readdir: api_readdir, + stat: api_stat, + fstat: api_fstat, + deletefile: api_deletefile, + deletedir: api_deletedir, + chdir: api_chdir, + dchdir: api_dchdir, + pwd: api_pwd, + malloc: api_malloc, + free: api_free, +}; /// Ways in which loading a program can fail. #[derive(Debug)] @@ -12,6 +34,8 @@ pub enum Error { ProgramTooLarge, /// A filesystem error occurred Filesystem(embedded_sdmmc::Error), + /// Start Address didn't look right + BadAddress(u32), } impl From> for Error { @@ -20,13 +44,6 @@ impl From> for Error { } } -#[allow(unused)] -#[repr(C)] -/// The API we give to applications. -pub struct Api { - pub print: extern "C" fn(data: *const u8, len: usize), -} - /// Represents the Transient Program Area. /// /// This is a piece of memory that can be used for loading and executing programs. @@ -139,10 +156,31 @@ impl TransientProgramArea { /// of view of this API. You wanted to run a program, and the program was /// run. pub fn execute(&mut self) -> Result { + // Read start-ptr as a 32-bit value let application_ram = self.as_slice_u32(); - let start_ptr = application_ram[0] as *const (); + let start_addr = application_ram[0]; + // But now we want RAM as u8 values, as start_ptr will be an odd number + // because it's a Thumb2 address and that's a u16 aligned value, plus 1 + // to indicate Thumb2 mode. + let application_ram = self.as_slice_u8(); + print!("Start address 0x{:08x}:", start_addr); + // Does this start pointer look OK? + if (start_addr & 1) != 1 { + println!("not thumb2 func"); + return Err(Error::BadAddress(start_addr)); + } + if !application_ram + .as_ptr_range() + .contains(&(start_addr as *const u8)) + { + println!("out of bounds"); + return Err(Error::BadAddress(start_addr)); + } + println!("OK!"); + drop(application_ram); let result = unsafe { - let code: extern "C" fn(*const Api) -> i32 = ::core::mem::transmute(start_ptr); + let code: extern "C" fn(*const neotron_api::Api) -> i32 = + ::core::mem::transmute(start_addr); code(&CALLBACK_TABLE) }; Ok(result) @@ -160,6 +198,183 @@ extern "C" fn print_fn(data: *const u8, len: usize) { } } +/// Open a file, given a path as UTF-8 string. +/// +/// If the file does not exist, or is already open, it returns an error. +/// +/// Path may be relative to current directory, or it may be an absolute +/// path. +extern "C" fn api_open( + _path: neotron_api::FfiString, + _flags: neotron_api::file::Flags, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Close a previously opened file. +extern "C" fn api_close(_fd: neotron_api::file::Handle) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Write to an open file handle, blocking until everything is written. +/// +/// Some files do not support writing and will produce an error. +extern "C" fn api_write( + fd: neotron_api::file::Handle, + buffer: neotron_api::FfiByteSlice, +) -> neotron_api::Result<()> { + if fd == neotron_api::file::Handle::new_stdout() { + if let Some(ref mut console) = unsafe { &mut crate::VGA_CONSOLE } { + console.write_bstr(buffer.as_slice()); + } + if let Some(ref mut console) = unsafe { &mut crate::SERIAL_CONSOLE } { + if let Err(_e) = console.write_bstr(buffer.as_slice()) { + return neotron_api::Result::Err(neotron_api::Error::DeviceSpecific); + } + } + neotron_api::Result::Ok(()) + } else { + neotron_api::Result::Err(neotron_api::Error::BadHandle) + } +} + +/// Read from an open file, returning how much was actually read. +/// +/// If you hit the end of the file, you might get less data than you asked for. +extern "C" fn api_read( + _fd: neotron_api::file::Handle, + _buffer: neotron_api::FfiBuffer, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Move the file offset (for the given file handle) to the given position. +/// +/// Some files do not support seeking and will produce an error. +extern "C" fn api_seek_set( + _fd: neotron_api::file::Handle, + _position: u64, +) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Move the file offset (for the given file handle) relative to the current position +/// +/// Some files do not support seeking and will produce an error. +extern "C" fn api_seek_cur( + _fd: neotron_api::file::Handle, + _offset: i64, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Move the file offset (for the given file handle) to the end of the file +/// +/// Some files do not support seeking and will produce an error. +extern "C" fn api_seek_end(_fd: neotron_api::file::Handle) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Rename a file +extern "C" fn api_rename( + _old_path: neotron_api::FfiString, + _new_path: neotron_api::FfiString, +) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Perform a special I/O control operation. +extern "C" fn api_ioctl( + _fd: neotron_api::file::Handle, + _command: u64, + _value: u64, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Open a directory, given a path as a UTF-8 string. +extern "C" fn api_opendir( + _path: neotron_api::FfiString, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Close a previously opened directory. +extern "C" fn api_closedir(_dir: neotron_api::dir::Handle) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Read from an open directory +extern "C" fn api_readdir( + _dir: neotron_api::dir::Handle, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Get information about a file +extern "C" fn api_stat( + _path: neotron_api::FfiString, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Get information about an open file +extern "C" fn api_fstat( + _fd: neotron_api::file::Handle, +) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Delete a file. +/// +/// If the file is currently open this will give an error. +extern "C" fn api_deletefile(_path: neotron_api::FfiString) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Delete a directory +/// +/// If the directory has anything in it, this will give an error. +extern "C" fn api_deletedir(_path: neotron_api::FfiString) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Change the current directory +/// +/// Relative file paths are taken to be relative to the current directory. +/// +/// Unlike on MS-DOS, there is only one current directory for the whole +/// system, not one per drive. +extern "C" fn api_chdir(_path: neotron_api::FfiString) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Change the current directory to the open directory +/// +/// Relative file paths are taken to be relative to the current directory. +/// +/// Unlike on MS-DOS, there is only one current directory for the whole +/// system, not one per drive. +extern "C" fn api_dchdir(_dir: neotron_api::dir::Handle) -> neotron_api::Result<()> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Obtain the current working directory. +extern "C" fn api_pwd(_path: neotron_api::FfiBuffer) -> neotron_api::Result { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Allocate some memory +extern "C" fn api_malloc( + _size: usize, + _alignment: usize, +) -> neotron_api::Result<*mut core::ffi::c_void> { + neotron_api::Result::Err(neotron_api::Error::Unimplemented) +} + +/// Free some previously allocated memory +extern "C" fn api_free(_ptr: *mut core::ffi::c_void, _size: usize, _alignment: usize) {} + // =========================================================================== // End of file // =========================================================================== From 70cb038c489c3d924f5059bad4a201b4e2b76240 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 10 Jun 2023 22:46:15 +0100 Subject: [PATCH 03/14] Make verbose builds optional. --- .github/workflows/rust.yml | 2 +- build.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 847660d..72f3418 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,7 +22,7 @@ jobs: rustup component add llvm-tools-preview cargo install cargo-binutils - name: Build - run: ./build.sh + run: ./build.sh --verbose - name: Upload files to Release if: github.event_name == 'push' && startswith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 diff --git a/build.sh b/build.sh index d235682..4b17559 100755 --- a/build.sh +++ b/build.sh @@ -11,9 +11,9 @@ for TARGET_ARCH in thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi; do echo "TARGET is ${TARGET_ARCH}" for BINARY in flash0002 flash0802 flash1002; do echo "BINARY is ${BINARY}" - cargo build --verbose --release --target=${TARGET_ARCH} --bin ${BINARY} + cargo build $* --release --target=${TARGET_ARCH} --bin ${BINARY} # objcopy would do the build for us first, but it doesn't have good build output - cargo objcopy --verbose --release --target=${TARGET_ARCH} --bin ${BINARY} -- -O binary ${RELEASE_DIR}/${TARGET_ARCH}-${BINARY}-libneotron_os.bin + cargo objcopy $* --release --target=${TARGET_ARCH} --bin ${BINARY} -- -O binary ${RELEASE_DIR}/${TARGET_ARCH}-${BINARY}-libneotron_os.bin # Keep the ELF file too (for debugging) cp ./target/${TARGET_ARCH}/release/${BINARY} ${RELEASE_DIR}/${TARGET_ARCH}-${BINARY}-libneotron_os.elf done From ac9fe34996e26f8a330543a0e3413f60fb2ca8a7 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 10 Jun 2023 22:48:04 +0100 Subject: [PATCH 04/14] Use published API --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 22cda3c..dd61cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,4 +49,5 @@ serde = { version = "1.0", default-features = false } menu = "0.3" chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.5", default-features = false } -neotron-api = { path = "../neotron-api" } +neotron-api = "0.1" + From c227566767f698e73fbdd4a5c7be7b73813d02fa Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 10 Jun 2023 22:51:09 +0100 Subject: [PATCH 05/14] Fix native build. Can't transmute a u32 to a pointer on a 64-bit platform. So we add an `as *const ()` as well. --- Cargo.lock | 2 ++ src/program.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1f66ec7..5e8f8eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,8 @@ checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "neotron-api" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c00f842e7006421002e67a53866b90ddd6f7d86137b52d2147f5e38b35e82f" dependencies = [ "bitflags", "neotron-ffi", diff --git a/src/program.rs b/src/program.rs index 86afced..a47a14b 100644 --- a/src/program.rs +++ b/src/program.rs @@ -180,7 +180,7 @@ impl TransientProgramArea { drop(application_ram); let result = unsafe { let code: extern "C" fn(*const neotron_api::Api) -> i32 = - ::core::mem::transmute(start_addr); + ::core::mem::transmute(start_addr as *const ()); code(&CALLBACK_TABLE) }; Ok(result) From 4dadf92c3bd836bcacf264f00df9855e8a999141 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 24 Jun 2023 18:04:57 +0100 Subject: [PATCH 06/14] Support using the Neotron OS as a library. In case you want to use it in a BIOS as a library, rather than compiling in separately. The feature flag turns off variable initialisation (the start-up already did that), and turns off the panic handler (your binary should do that). --- Cargo.toml | 2 ++ src/lib.rs | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd61cc0..9fce3cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,3 +51,5 @@ chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.5", default-features = false } neotron-api = "0.1" +[features] +lib-mode = [] diff --git a/src/lib.rs b/src/lib.rs index ecdc88f..4241ba0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,7 +188,7 @@ impl core::fmt::Write for Ctx { /// Initialise our global variables - the BIOS will not have done this for us /// (as it doesn't know where they are). -#[cfg(target_os = "none")] +#[cfg(all(target_os = "none", not(feature = "lib-mode")))] unsafe fn start_up_init() { extern "C" { @@ -205,7 +205,7 @@ unsafe fn start_up_init() { r0::init_data(&mut __sdata, &mut __edata, &__sidata); } -#[cfg(not(target_os = "none"))] +#[cfg(any(not(target_os = "none"), feature = "lib-mode"))] unsafe fn start_up_init() { // Nothing to do } @@ -217,7 +217,7 @@ unsafe fn start_up_init() { /// This is the function the BIOS calls. This is because we store the address /// of this function in the ENTRY_POINT_ADDR variable. #[no_mangle] -pub extern "C" fn main(api: &bios::Api) -> ! { +pub extern "C" fn os_main(api: &bios::Api) -> ! { unsafe { start_up_init(); API.store(api); @@ -351,6 +351,7 @@ pub extern "C" fn main(api: &bios::Api) -> ! { /// Called when we have a panic. #[inline(never)] #[panic_handler] +#[cfg(not(feature = "lib-mode"))] fn panic(info: &core::panic::PanicInfo) -> ! { IS_PANIC.store(true, Ordering::SeqCst); println!("PANIC!\n{:#?}", info); From 0fda2b08ecc9c490b0def6df6ba683ae484b72f5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 24 Jun 2023 21:09:25 +0100 Subject: [PATCH 07/14] Change the binaries to call the new entry function. --- src/bin/flash0002.rs | 2 +- src/bin/flash0802.rs | 2 +- src/bin/flash1002.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/flash0002.rs b/src/bin/flash0002.rs index 4bf5162..de2fc0f 100644 --- a/src/bin/flash0002.rs +++ b/src/bin/flash0002.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::os_main; diff --git a/src/bin/flash0802.rs b/src/bin/flash0802.rs index c1ddc87..340707b 100644 --- a/src/bin/flash0802.rs +++ b/src/bin/flash0802.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::os_main; diff --git a/src/bin/flash1002.rs b/src/bin/flash1002.rs index 7be5837..33fa480 100644 --- a/src/bin/flash1002.rs +++ b/src/bin/flash1002.rs @@ -13,4 +13,4 @@ /// of our portion of Flash. #[link_section = ".entry_point"] #[used] -pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::main; +pub static ENTRY_POINT_ADDR: extern "C" fn(&neotron_common_bios::Api) -> ! = neotron_os::os_main; From ebac51adac5ca7ac6fcebe520b26c00896299505 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 24 Jun 2023 22:32:07 +0100 Subject: [PATCH 08/14] Read UART for console input. --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4241ba0..2f75c17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ mod fs; mod program; mod vgaconsole; +pub use config::Config as OsConfig; + // =========================================================================== // Global Variables // =========================================================================== @@ -344,6 +346,25 @@ pub extern "C" fn os_main(api: &bios::Api) -> ! { println!("Failed to get HID events: {:?}", e); } } + if let Some((uart_dev, _serial_conf)) = menu.context.config.get_serial_console() { + loop { + let mut buffer = [0u8; 8]; + let wrapper = neotron_common_bios::ApiBuffer::new(&mut buffer); + match (api.serial_read)(uart_dev, wrapper, neotron_common_bios::Option::None) { + neotron_common_bios::Result::Ok(n) if n == 0 => { + break; + } + neotron_common_bios::Result::Ok(n) => { + for b in &buffer[0..n] { + menu.input_byte(*b); + } + } + _ => { + break; + } + } + } + } (api.power_idle)(); } } From f4aef60d633bd62c3ddcdcf871fc9cf80a1f5b90 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 24 Jun 2023 22:34:22 +0100 Subject: [PATCH 09/14] Remove clippy warning --- src/program.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/program.rs b/src/program.rs index a47a14b..1356bc9 100644 --- a/src/program.rs +++ b/src/program.rs @@ -177,7 +177,6 @@ impl TransientProgramArea { return Err(Error::BadAddress(start_addr)); } println!("OK!"); - drop(application_ram); let result = unsafe { let code: extern "C" fn(*const neotron_api::Api) -> i32 = ::core::mem::transmute(start_addr as *const ()); From 34dc28eff128ae923a309309b90246bb61bedfaf Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 16 Jun 2023 22:26:53 +0100 Subject: [PATCH 10/14] Use the neotron_loader. Can load ELF files from disk. --- Cargo.lock | 6 +++ Cargo.toml | 1 + src/program.rs | 142 ++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 117 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e8f8eb..636e361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37886e73d87732421aaf5da617eead9d69a7daf6b0d059780f76157d9ce5372" +[[package]] +name = "neotron-loader" +version = "0.1.0" +source = "git+https://github.com/neotron-compute/neotron-loader#14ff18addd5bf9a2f0b6929bfa124439768edc5c" + [[package]] name = "neotron-os" version = "0.3.3" @@ -160,6 +165,7 @@ dependencies = [ "menu", "neotron-api", "neotron-common-bios", + "neotron-loader", "pc-keyboard", "postcard", "r0", diff --git a/Cargo.toml b/Cargo.toml index 9fce3cb..aa7cd6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ menu = "0.3" chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.5", default-features = false } neotron-api = "0.1" +neotron-loader = { git = "https://github.com/neotron-compute/neotron-loader" } [features] lib-mode = [] diff --git a/src/program.rs b/src/program.rs index 1356bc9..2c443d5 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,6 +1,12 @@ //! Program Loading and Execution -use crate::{print, println}; +use embedded_sdmmc::File; +use neotron_loader::traits::Source; + +use crate::{ + fs::{BiosBlock, BiosTime}, + print, println, +}; #[allow(unused)] static CALLBACK_TABLE: neotron_api::Api = neotron_api::Api { @@ -34,8 +40,10 @@ pub enum Error { ProgramTooLarge, /// A filesystem error occurred Filesystem(embedded_sdmmc::Error), - /// Start Address didn't look right - BadAddress(u32), + /// An ELF error occurred + Elf(neotron_loader::Error>), + /// Tried to run when nothing was loaded + NothingLoaded, } impl From> for Error { @@ -44,6 +52,76 @@ impl From> for Error { } } +impl From>> for Error { + fn from( + value: neotron_loader::Error>, + ) -> Self { + Error::Elf(value) + } +} + +/// Something the ELF loader can use to get bytes off the disk +struct FileSource { + mgr: core::cell::RefCell>, + volume: embedded_sdmmc::Volume, + file: core::cell::RefCell, + buffer: core::cell::RefCell<[u8; Self::BUFFER_LEN]>, + offset_cached: core::cell::Cell>, +} + +impl FileSource { + const BUFFER_LEN: usize = 128; + + fn new( + mgr: embedded_sdmmc::VolumeManager, + volume: embedded_sdmmc::Volume, + file: File, + ) -> FileSource { + FileSource { + mgr: core::cell::RefCell::new(mgr), + file: core::cell::RefCell::new(file), + volume, + buffer: core::cell::RefCell::new([0u8; 128]), + offset_cached: core::cell::Cell::new(None), + } + } +} + +impl neotron_loader::traits::Source for &FileSource { + type Error = embedded_sdmmc::Error; + + fn read(&self, mut offset: u32, out_buffer: &mut [u8]) -> Result<(), Self::Error> { + for chunk in out_buffer.chunks_mut(FileSource::BUFFER_LEN) { + if let Some(offset_cached) = self.offset_cached.get() { + let cached_range = offset_cached..offset_cached + FileSource::BUFFER_LEN as u32; + if cached_range.contains(&offset) + && cached_range.contains(&(offset + chunk.len() as u32 - 1)) + { + // Do a fast copy from the cache + let start = (offset - offset_cached) as usize; + let end = (start as usize) + chunk.len(); + chunk.copy_from_slice(&self.buffer.borrow()[start..end]); + return Ok(()); + } + } + + println!("Reading from {}", offset); + self.file.borrow_mut().seek_from_start(offset).unwrap(); + self.mgr.borrow_mut().read( + &self.volume, + &mut self.file.borrow_mut(), + self.buffer.borrow_mut().as_mut_slice(), + )?; + self.offset_cached.set(Some(offset)); + chunk.copy_from_slice(&self.buffer.borrow()[0..chunk.len()]); + + offset += chunk.len() as u32; + } + + Ok(()) + } +} + /// Represents the Transient Program Area. /// /// This is a piece of memory that can be used for loading and executing programs. @@ -52,6 +130,7 @@ impl From> for Error { pub struct TransientProgramArea { memory_bottom: *mut u32, memory_top: *mut u32, + last_entry: u32, } extern "C" { @@ -65,6 +144,7 @@ impl TransientProgramArea { let mut tpa = TransientProgramArea { memory_bottom: start, memory_top: start.add(length_in_bytes / core::mem::size_of::()), + last_entry: 0, }; // You have to take the address of a linker symbol to find out where @@ -120,19 +200,32 @@ impl TransientProgramArea { // Open the first partition let mut volume = mgr.get_volume(embedded_sdmmc::VolumeIdx(0))?; let root_dir = mgr.open_root_dir(&volume)?; - let mut file = mgr.open_file_in_dir( + let file = mgr.open_file_in_dir( &mut volume, &root_dir, file_name, embedded_sdmmc::Mode::ReadOnly, )?; - // Application space starts 4K into Cortex-M SRAM - let application_ram = self.as_slice_u8(); - if file.length() as usize > application_ram.len() { - return Err(Error::ProgramTooLarge); - }; - let application_ram = &mut application_ram[0..file.length() as usize]; - mgr.read(&volume, &mut file, application_ram)?; + + let source = FileSource::new(mgr, volume, file); + let loader = neotron_loader::Loader::new(&source)?; + + let mut iter = loader.iter_program_headers(); + while let Some(Ok(ph)) = iter.next() { + if ph.p_vaddr() >= 0x2000_1000 { + println!("Loading {} bytes to 0x{:08x}", ph.p_memsz(), ph.p_vaddr()); + let ram = unsafe { + core::slice::from_raw_parts_mut(ph.p_vaddr() as *mut u8, ph.p_memsz() as usize) + }; + for b in ram.iter_mut() { + *b = 0; + } + (&source).read(ph.p_offset(), &mut ram[0..ph.p_filesz() as usize])?; + } + } + + self.last_entry = loader.e_entry(); + Ok(()) } @@ -156,32 +249,17 @@ impl TransientProgramArea { /// of view of this API. You wanted to run a program, and the program was /// run. pub fn execute(&mut self) -> Result { - // Read start-ptr as a 32-bit value - let application_ram = self.as_slice_u32(); - let start_addr = application_ram[0]; - // But now we want RAM as u8 values, as start_ptr will be an odd number - // because it's a Thumb2 address and that's a u16 aligned value, plus 1 - // to indicate Thumb2 mode. - let application_ram = self.as_slice_u8(); - print!("Start address 0x{:08x}:", start_addr); - // Does this start pointer look OK? - if (start_addr & 1) != 1 { - println!("not thumb2 func"); - return Err(Error::BadAddress(start_addr)); + if self.last_entry == 0 { + return Err(Error::NothingLoaded); } - if !application_ram - .as_ptr_range() - .contains(&(start_addr as *const u8)) - { - println!("out of bounds"); - return Err(Error::BadAddress(start_addr)); - } - println!("OK!"); + let result = unsafe { let code: extern "C" fn(*const neotron_api::Api) -> i32 = - ::core::mem::transmute(start_addr as *const ()); + ::core::mem::transmute(self.last_entry as *const ()); code(&CALLBACK_TABLE) }; + + self.last_entry = 0; Ok(result) } } From 83fb4302c8fd08e179dc608799fc606e481837a4 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 16 Jun 2023 22:37:03 +0100 Subject: [PATCH 11/14] Load segments with uncached reads. Bypassing the cache means bigger blocks so it goes much faster. --- src/program.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/program.rs b/src/program.rs index 2c443d5..77dc5c6 100644 --- a/src/program.rs +++ b/src/program.rs @@ -1,7 +1,6 @@ //! Program Loading and Execution use embedded_sdmmc::File; -use neotron_loader::traits::Source; use crate::{ fs::{BiosBlock, BiosTime}, @@ -85,6 +84,19 @@ impl FileSource { offset_cached: core::cell::Cell::new(None), } } + + fn uncached_read( + &self, + offset: u32, + out_buffer: &mut [u8], + ) -> Result<(), embedded_sdmmc::Error> { + println!("Reading from {}", offset); + self.file.borrow_mut().seek_from_start(offset).unwrap(); + self.mgr + .borrow_mut() + .read(&self.volume, &mut self.file.borrow_mut(), out_buffer)?; + Ok(()) + } } impl neotron_loader::traits::Source for &FileSource { @@ -99,7 +111,7 @@ impl neotron_loader::traits::Source for &FileSource { { // Do a fast copy from the cache let start = (offset - offset_cached) as usize; - let end = (start as usize) + chunk.len(); + let end = start + chunk.len(); chunk.copy_from_slice(&self.buffer.borrow()[start..end]); return Ok(()); } @@ -220,7 +232,7 @@ impl TransientProgramArea { for b in ram.iter_mut() { *b = 0; } - (&source).read(ph.p_offset(), &mut ram[0..ph.p_filesz() as usize])?; + source.uncached_read(ph.p_offset(), &mut ram[0..ph.p_filesz() as usize])?; } } From 4eb4791531a623f43994a9760810a0868d488f63 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 16 Jun 2023 23:11:25 +0100 Subject: [PATCH 12/14] Only load what you have to load. --- src/program.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/program.rs b/src/program.rs index 77dc5c6..c629091 100644 --- a/src/program.rs +++ b/src/program.rs @@ -224,15 +224,21 @@ impl TransientProgramArea { let mut iter = loader.iter_program_headers(); while let Some(Ok(ph)) = iter.next() { - if ph.p_vaddr() >= 0x2000_1000 { + if ph.p_vaddr() as *mut u32 >= self.memory_bottom + && ph.p_type() == neotron_loader::ProgramHeader::PT_LOAD + { println!("Loading {} bytes to 0x{:08x}", ph.p_memsz(), ph.p_vaddr()); let ram = unsafe { core::slice::from_raw_parts_mut(ph.p_vaddr() as *mut u8, ph.p_memsz() as usize) }; + // Zero all of it. for b in ram.iter_mut() { *b = 0; } - source.uncached_read(ph.p_offset(), &mut ram[0..ph.p_filesz() as usize])?; + // Replace some of those zeros with bytes from disk. + if ph.p_filesz() != 0 { + source.uncached_read(ph.p_offset(), &mut ram[0..ph.p_filesz() as usize])?; + } } } From 4c468611defd5b0075328bdadc635e7522874708 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 25 Jun 2023 11:35:20 +0100 Subject: [PATCH 13/14] Use published neotron-loader --- Cargo.lock | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 636e361..31afd2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,8 @@ checksum = "d37886e73d87732421aaf5da617eead9d69a7daf6b0d059780f76157d9ce5372" [[package]] name = "neotron-loader" version = "0.1.0" -source = "git+https://github.com/neotron-compute/neotron-loader#14ff18addd5bf9a2f0b6929bfa124439768edc5c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b8634a088b9d5b338a96b3f6ef45a3bc0b9c0f0d562c7d00e498265fd96e8f" [[package]] name = "neotron-os" diff --git a/Cargo.toml b/Cargo.toml index aa7cd6d..98d4048 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ menu = "0.3" chrono = { version = "0.4", default-features = false } embedded-sdmmc = { version = "0.5", default-features = false } neotron-api = "0.1" -neotron-loader = { git = "https://github.com/neotron-compute/neotron-loader" } +neotron-loader = "0.1" [features] lib-mode = [] From 45c5f5ad516e20364b58297244d37c242090bb34 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 25 Jun 2023 11:19:14 +0100 Subject: [PATCH 14/14] Update to 0.4.0 --- CHANGELOG.md | 5 +++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 710b428..0d1c301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased changes +## v0.4.0 + +* The `load` command now takes ELF binaries, not raw binaries. +* Neotron OS can now be used as a dependency within an application, if desired. + ## v0.3.3 * Add `dir` command diff --git a/Cargo.lock b/Cargo.lock index 31afd2e..d5a352f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,7 +159,7 @@ checksum = "b9b8634a088b9d5b338a96b3f6ef45a3bc0b9c0f0d562c7d00e498265fd96e8f" [[package]] name = "neotron-os" -version = "0.3.3" +version = "0.4.0" dependencies = [ "chrono", "embedded-sdmmc", diff --git a/Cargo.toml b/Cargo.toml index 98d4048..1bbb4af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "neotron-os" -version = "0.3.3" +version = "0.4.0" authors = [ "Jonathan 'theJPster' Pallant ", "The Neotron Developers"