From 4697a7a530ba59bd52e61e470ce2093e49929f43 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Tue, 26 Mar 2024 11:31:34 +0800 Subject: [PATCH 01/11] fix mbox get memory failed --- kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs | 7 ++++--- kernel/src/main.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs index 9cf1c7d82..f547ceeab 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs @@ -181,10 +181,11 @@ impl MBOXInner { mailbox.buffer[0] = 8 * 4; mailbox.buffer[1] = 0; mailbox.buffer[2] = 0x0001_0005; - mailbox.buffer[3] = 0; + mailbox.buffer[3] = 8; mailbox.buffer[4] = 0; mailbox.buffer[5] = 0; - mailbox.buffer[6] = 0x0000_0000; + mailbox.buffer[6] = 0; + mailbox.buffer[7] = 0x0000_0000; // let mut mbox = [ // 8 * 4, // Buffer size in bytes // 0, // Request/response code @@ -199,7 +200,7 @@ impl MBOXInner { println!("get mem failed"); return (0, 0); } - (mailbox.buffer[6], mailbox.buffer[7]) + (mailbox.buffer[5], mailbox.buffer[6]) } } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index fe882cba9..438931e77 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -58,13 +58,13 @@ unsafe fn kernel_main() -> ! { let msg_buf_exceed = "[system] Buf size limit exceed, reset buf"; - let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device"; + let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device\r\ninfo\t: show the device info"; let msg_hello_world = "HelloWorld!"; let msg_not_found = "Command not found"; let msg_reboot = "Rebooting..."; let arr_help = ArrString::new("help"); - let arr_hello: ArrString = ArrString::new("hello"); + let arr_hello = ArrString::new("hello"); let arr_reboot = ArrString::new("reboot"); let arr_info = ArrString::new("info"); From 8e8362f4ebb2cc5c1562426af0826f40d75b6678 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Tue, 26 Mar 2024 19:42:47 +0800 Subject: [PATCH 02/11] rebuild mbox component --- kernel/Makefile | 7 +++ .../src/bsp/device_driver/bcm/bcm2xxx_mbox.rs | 47 +++++++++------ kernel/src/bsp/mbox.rs | 10 ++++ kernel/src/bsp/rpi3/driver.rs | 24 ++++---- kernel/src/main.rs | 11 ++-- kernel/src/mbox.rs | 57 +++++++++++++++++++ kernel/src/mbox/null_mbox.rs | 38 +++++++++++++ 7 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 kernel/src/bsp/mbox.rs create mode 100644 kernel/src/mbox.rs create mode 100644 kernel/src/mbox/null_mbox.rs diff --git a/kernel/Makefile b/kernel/Makefile index 1259ecb81..bf39161c2 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -102,6 +102,13 @@ $(KERNEL_BIN): $(KERNEL_ELF) $(call color_progress_prefix, "Size") $(call disk_usage_KiB, $(KERNEL_BIN)) + +##------------------------------------------------------------------------------ +## Debug +##------------------------------------------------------------------------------ +debug: + qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio + ##------------------------------------------------------------------------------ ## Clean ##------------------------------------------------------------------------------ diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs index f547ceeab..d219a9cf9 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs @@ -8,7 +8,7 @@ use crate::println; use crate::{ bsp::device_driver::common::MMIODerefWrapper, - driver, + driver, mbox, synchronization::{interface::Mutex, NullLock}, }; @@ -138,7 +138,6 @@ impl MBOXInner { println!("{:x}", mailbox_ptr); println!("ptr failed"); return false; - } Self::mailbox_write(self, CHANNEL_GPU, mailbox_ptr); @@ -153,13 +152,13 @@ impl MBOXInner { pub fn get_board_revision(&mut self) -> u32 { let mut mailbox = MailboxMsg { buffer: [0; 8] }; - mailbox.buffer[0] = 7 * 4; - mailbox.buffer[1] = 0; - mailbox.buffer[2] = 0x0001_0002; - mailbox.buffer[3] = 4; - mailbox.buffer[4] = 0; - mailbox.buffer[5] = 0; - mailbox.buffer[6] = 0x0000_0000; + mailbox.buffer[0] = 7 * 4; + mailbox.buffer[1] = 0; + mailbox.buffer[2] = 0x0001_0002; + mailbox.buffer[3] = 4; + mailbox.buffer[4] = 0; + mailbox.buffer[5] = 0; + mailbox.buffer[6] = 0x0000_0000; // let mut mbox = [ // 7 * 4, // Buffer size in bytes // 0, // Request/response code @@ -178,14 +177,14 @@ impl MBOXInner { pub fn get_arm_memory(&mut self) -> (u32, u32) { let mut mailbox = MailboxMsg { buffer: [0; 8] }; - mailbox.buffer[0] = 8 * 4; - mailbox.buffer[1] = 0; - mailbox.buffer[2] = 0x0001_0005; - mailbox.buffer[3] = 8; - mailbox.buffer[4] = 0; - mailbox.buffer[5] = 0; - mailbox.buffer[6] = 0; - mailbox.buffer[7] = 0x0000_0000; + mailbox.buffer[0] = 8 * 4; + mailbox.buffer[1] = 0; + mailbox.buffer[2] = 0x0001_0005; + mailbox.buffer[3] = 8; + mailbox.buffer[4] = 0; + mailbox.buffer[5] = 0; + mailbox.buffer[6] = 0; + mailbox.buffer[7] = 0x0000_0000; // let mut mbox = [ // 8 * 4, // Buffer size in bytes // 0, // Request/response code @@ -245,3 +244,17 @@ impl driver::interface::DeviceDriver for MBOX { Ok(()) } } + +impl mbox::interface::Boardinfo for MBOX { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn get_arm_memory(&self) -> (u32, u32) { + self.inner.lock(|inner| inner.get_arm_memory()) + } + + fn get_board_revision(&self) -> u32 { + self.inner.lock(|inner| inner.get_board_revision()) + } +} + +impl mbox::interface::All for MBOX {} \ No newline at end of file diff --git a/kernel/src/bsp/mbox.rs b/kernel/src/bsp/mbox.rs new file mode 100644 index 000000000..1e13075a1 --- /dev/null +++ b/kernel/src/bsp/mbox.rs @@ -0,0 +1,10 @@ +use crate::mbox; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the console. +pub fn mbox() -> &'static dyn mbox::interface::All { + &super::driver::MBOX +} \ No newline at end of file diff --git a/kernel/src/bsp/rpi3/driver.rs b/kernel/src/bsp/rpi3/driver.rs index 238f29816..7bb69e441 100644 --- a/kernel/src/bsp/rpi3/driver.rs +++ b/kernel/src/bsp/rpi3/driver.rs @@ -5,7 +5,7 @@ //! BSP driver support. use super::memory::map::mmio; -use crate::{bsp::device_driver, console, driver as generic_driver}; +use crate::{bsp::device_driver, console, mbox, driver as generic_driver}; use core::sync::atomic::{AtomicBool, Ordering}; //-------------------------------------------------------------------------------------------------- @@ -19,6 +19,7 @@ static MINI_UART: device_driver::MiniUart = static GPIO: device_driver::GPIO = unsafe { device_driver::GPIO::new(mmio::GPIO_START) }; pub static MBOX: device_driver::MBOX = unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; + // unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; //-------------------------------------------------------------------------------------------------- // Private Code @@ -40,9 +41,10 @@ fn post_init_gpio() -> Result<(), &'static str> { } -// fn post_init_mbox() -> Result<(), &'static str> { -// Ok(()) -// } +fn post_init_mbox() -> Result<(), &'static str> { + mbox::register_mbox(&MBOX); + Ok(()) +} fn driver_uart() -> Result<(), &'static str> { let uart_descriptor = @@ -60,12 +62,12 @@ fn driver_gpio() -> Result<(), &'static str> { Ok(()) } -// fn driver_mbox() -> Result<(), &'static str> { -// let mbox_descriptor = generic_driver::DeviceDriverDescriptor::new(&MBOX, Some(post_init_mbox)); -// generic_driver::driver_manager().register_driver(mbox_descriptor); - -// Ok(()) -// } +fn driver_mbox() -> Result<(), &'static str> { + let mbox_descriptor = generic_driver::DeviceDriverDescriptor::new(&MBOX, Some(post_init_mbox)); + generic_driver::driver_manager().register_driver(mbox_descriptor); + + Ok(()) +} //-------------------------------------------------------------------------------------------------- // Public Code @@ -84,7 +86,7 @@ pub unsafe fn init() -> Result<(), &'static str> { driver_gpio()?; driver_uart()?; - // driver_mbox()?; + driver_mbox()?; INIT_DONE.store(true, Ordering::Relaxed); diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 438931e77..bd2b9fe09 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -14,6 +14,7 @@ mod driver; mod panic_wait; mod print; mod synchronization; +mod mbox; mod arrsting; @@ -81,6 +82,7 @@ unsafe fn kernel_main() -> ! { console().write_char(c); if c == '\n' { + print!("\r"); if buf == arr_help { println!("{}", msg_help); } else if buf == arr_hello { @@ -89,11 +91,12 @@ unsafe fn kernel_main() -> ! { println!("{}", msg_reboot); reboot(); } else if buf == arr_info { - println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); + println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); + // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); println!( "RAM: {} {}", - bsp::driver::MBOX.get_arm_memory().0, - bsp::driver::MBOX.get_arm_memory().1 + mbox::mbox().get_arm_memory().0, + mbox::mbox().get_arm_memory().1 ); } else { println!("{}", msg_not_found); @@ -106,7 +109,7 @@ unsafe fn kernel_main() -> ! { // arrsting::arrstrcmp(buf, help); } else if buf.get_len() == 1024 { buf.clean_buf(); - println!("{}\r\n#", msg_buf_exceed); + println!("{}", msg_buf_exceed); print!("#"); continue; } else { diff --git a/kernel/src/mbox.rs b/kernel/src/mbox.rs new file mode 100644 index 000000000..006947be2 --- /dev/null +++ b/kernel/src/mbox.rs @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +mod null_mbox; + +use crate::synchronization::{self, NullLock}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Console interfaces. +#[allow(dead_code)] +pub mod interface { + + pub trait Boardinfo { + fn get_board_revision(&self) -> u32 { + 0 + } + + fn get_arm_memory(&self) -> (u32, u32) { + (0, 0) + } + } + + /// Trait alias for a full-fledged console. + pub trait All: Boardinfo {} +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static CUR_MBOX: NullLock<&'static (dyn interface::All + Sync)> = + NullLock::new(&null_mbox::NULL_MBOX); + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- +use synchronization::interface::Mutex; + +/// Register a new console. +pub fn register_mbox(new_mbox: &'static (dyn interface::All + Sync)) { + CUR_MBOX.lock(|con| *con = new_mbox); +} + +/// Return a reference to the currently registered console. +/// +/// This is the global console used by all printing macros. +pub fn mbox() -> &'static dyn interface::All { + CUR_MBOX.lock(|con| *con) +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/kernel/src/mbox/null_mbox.rs b/kernel/src/mbox/null_mbox.rs new file mode 100644 index 000000000..3d3677bdd --- /dev/null +++ b/kernel/src/mbox/null_mbox.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 Andre Richter + +//! Null console. + +use super::interface; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct NullMBOX; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +pub static NULL_MBOX: NullMBOX = NullMBOX {}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl interface::Boardinfo for NullMBOX { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn get_arm_memory(&self) -> (u32, u32) { + (0, 0) + } + + fn get_board_revision(&self) -> u32 { + 0 + } +} + + +impl interface::All for NullMBOX {} \ No newline at end of file From 1dfa3572bb29c80cf959a948aa6d2fa0415919f9 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Tue, 26 Mar 2024 19:50:26 +0800 Subject: [PATCH 03/11] remove used code --- kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs index d219a9cf9..a2051dde4 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs @@ -220,14 +220,6 @@ impl MBOX { inner: NullLock::new(MBOXInner::new(mmio_start_addr)), } } - - pub fn get_board_revision(&self) -> u32 { - self.inner.lock(|inner| inner.get_board_revision()) - } - - pub fn get_arm_memory(&self) -> (u32, u32) { - self.inner.lock(|inner| inner.get_arm_memory()) - } } //------------------------------------------------------------------------------ From 08a4697081861fd21e11e2c1b3e4ad8dfb35f5ff Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Wed, 27 Mar 2024 15:52:20 +0800 Subject: [PATCH 04/11] split reboot part --- kernel/src/main.rs | 34 ++++------------------------------ kernel/src/power.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 30 deletions(-) create mode 100644 kernel/src/power.rs diff --git a/kernel/src/main.rs b/kernel/src/main.rs index bd2b9fe09..2a282facc 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -5,8 +5,6 @@ #![no_main] #![no_std] -use core::ptr::write_volatile; - mod bsp; mod console; mod cpu; @@ -15,6 +13,7 @@ mod panic_wait; mod print; mod synchronization; mod mbox; +mod power; mod arrsting; @@ -41,7 +40,7 @@ unsafe fn kernel_init() -> ! { } /// The main function running after the early init. -unsafe fn kernel_main() -> ! { +fn kernel_main() -> ! { use console::console; println!( @@ -89,7 +88,7 @@ unsafe fn kernel_main() -> ! { println!("{}", msg_hello_world); } else if buf == arr_reboot { println!("{}", msg_reboot); - reboot(); + power::reboot(); } else if buf == arr_info { println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); @@ -116,29 +115,4 @@ unsafe fn kernel_main() -> ! { buf.push_char(c); } } -} - -unsafe fn reboot() { - reset(100); -} - -const PM_PASSWORD: u32 = 0x5a000000; -const PM_RSTC: u32 = 0x3F10_001C; -const PM_WDOG: u32 = 0x3F10_0024; - -pub fn reset(tick: u32) { - unsafe { - let mut r = PM_PASSWORD | 0x20; - write_volatile(PM_RSTC as *mut u32, r); - r = PM_PASSWORD | tick; - write_volatile(PM_WDOG as *mut u32, r); - } -} - -pub fn cancel_reset() { - unsafe { - let r = PM_PASSWORD | 0; - write_volatile(PM_RSTC as *mut u32, r); - write_volatile(PM_WDOG as *mut u32, r); - } -} +} \ No newline at end of file diff --git a/kernel/src/power.rs b/kernel/src/power.rs new file mode 100644 index 000000000..2daac1cd3 --- /dev/null +++ b/kernel/src/power.rs @@ -0,0 +1,28 @@ +use core::ptr::write_volatile; + +pub fn reboot() { + reset(100); +} + +const PM_PASSWORD: u32 = 0x5a000000; +const PM_RSTC: u32 = 0x3F10_001C; +const PM_WDOG: u32 = 0x3F10_0024; + + +pub fn reset(tick: u32) { + unsafe { + let mut r = PM_PASSWORD | 0x20; + write_volatile(PM_RSTC as *mut u32, r); + r = PM_PASSWORD | tick; + write_volatile(PM_WDOG as *mut u32, r); + } +} + +#[allow(dead_code)] +pub fn cancel_reset() { + unsafe { + let r = PM_PASSWORD | 0; + write_volatile(PM_RSTC as *mut u32, r); + write_volatile(PM_WDOG as *mut u32, r); + } +} \ No newline at end of file From 4937da34a1b6fc303f8a0c77f61b54d251a20965 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Wed, 27 Mar 2024 16:28:28 +0800 Subject: [PATCH 05/11] split shell from main --- kernel/src/main.rs | 63 ++------------------------------------------- kernel/src/print.rs | 1 - kernel/src/shell.rs | 61 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 62 deletions(-) create mode 100644 kernel/src/shell.rs diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 2a282facc..d6e395bbe 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -14,10 +14,9 @@ mod print; mod synchronization; mod mbox; mod power; - +mod shell; mod arrsting; -use arrsting::ArrString; /// Early init code. /// @@ -56,63 +55,5 @@ fn kernel_main() -> ! { println!("[3] Chars written: {}", console().chars_written()); println!("[4] Echoing input now"); - let msg_buf_exceed = "[system] Buf size limit exceed, reset buf"; - - let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device\r\ninfo\t: show the device info"; - let msg_hello_world = "HelloWorld!"; - let msg_not_found = "Command not found"; - let msg_reboot = "Rebooting..."; - - let arr_help = ArrString::new("help"); - let arr_hello = ArrString::new("hello"); - let arr_reboot = ArrString::new("reboot"); - let arr_info = ArrString::new("info"); - - let mut buf = ArrString::new(""); - - // Discard any spurious received characters before going into echo mode. - console().clear_rx(); - - println!("TEST VER 0.0.2\r\n"); - print!("{}\r\n#", msg_help); - - loop { - let c = console().read_char(); - console().write_char(c); - - if c == '\n' { - print!("\r"); - if buf == arr_help { - println!("{}", msg_help); - } else if buf == arr_hello { - println!("{}", msg_hello_world); - } else if buf == arr_reboot { - println!("{}", msg_reboot); - power::reboot(); - } else if buf == arr_info { - println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); - // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); - println!( - "RAM: {} {}", - mbox::mbox().get_arm_memory().0, - mbox::mbox().get_arm_memory().1 - ); - } else { - println!("{}", msg_not_found); - } - - buf.clean_buf(); - print!("#"); - continue; - - // arrsting::arrstrcmp(buf, help); - } else if buf.get_len() == 1024 { - buf.clean_buf(); - println!("{}", msg_buf_exceed); - print!("#"); - continue; - } else { - buf.push_char(c); - } - } + shell::start_shell(); } \ No newline at end of file diff --git a/kernel/src/print.rs b/kernel/src/print.rs index e90dd4f38..66e45baed 100644 --- a/kernel/src/print.rs +++ b/kernel/src/print.rs @@ -3,7 +3,6 @@ // Copyright (c) 2018-2023 Andre Richter //! Printing. - use crate::console; use core::fmt; diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs new file mode 100644 index 000000000..1cb1da871 --- /dev/null +++ b/kernel/src/shell.rs @@ -0,0 +1,61 @@ +use crate::{arrsting::ArrString, console::console, mbox, power, print, println}; + + +pub fn start_shell() -> !{ + let msg_buf_exceed = "[system] Buf size limit exceed, reset buf"; + + let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device\r\ninfo\t: show the device info"; + let msg_hello_world = "HelloWorld!"; + let msg_not_found = "Command not found"; + let msg_reboot = "Rebooting..."; + + let arr_help = ArrString::new("help"); + let arr_hello = ArrString::new("hello"); + let arr_reboot = ArrString::new("reboot"); + let arr_info = ArrString::new("info"); + + let mut buf = ArrString::new(""); + + println!("TEST VER 0.0.2\r\n"); + print!("{}\r\n#", msg_help); + + loop { + let c = console().read_char(); + console().write_char(c); + + if c == '\n' { + print!("\r"); + if buf == arr_help { + println!("{}", msg_help); + } else if buf == arr_hello { + println!("{}", msg_hello_world); + } else if buf == arr_reboot { + println!("{}", msg_reboot); + power::reboot(); + } else if buf == arr_info { + println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); + // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); + println!( + "RAM: {} {}", + mbox::mbox().get_arm_memory().0, + mbox::mbox().get_arm_memory().1 + ); + } else { + println!("{}", msg_not_found); + } + + buf.clean_buf(); + print!("#"); + continue; + + // arrsting::arrstrcmp(buf, help); + } else if buf.get_len() == 1024 { + buf.clean_buf(); + println!("{}", msg_buf_exceed); + print!("#"); + continue; + } else { + buf.push_char(c); + } + } +} From 0718336ec4d739173fbc1b55159a65db7702f257 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Tue, 16 Apr 2024 18:54:24 +0800 Subject: [PATCH 06/11] bootloader and uploader --- bootloader/Cargo.lock | 26 ++ bootloader/Cargo.toml | 29 ++ bootloader/Makefile | 119 +++++ bootloader/kernel.ld | 83 ++++ bootloader/src/arrsting.rs | 51 +++ bootloader/src/bsp.rs | 5 + bootloader/src/bsp/device_driver.rs | 4 + bootloader/src/bsp/device_driver/bcm.rs | 10 + .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 236 ++++++++++ .../src/bsp/device_driver/bcm/bcm2xxx_mbox.rs | 252 +++++++++++ .../device_driver/bcm/bcm2xxx_mini_uart.rs | 402 +++++++++++++++++ .../device_driver/bcm/bcm2xxx_pl011_uart.rs | 407 ++++++++++++++++++ bootloader/src/bsp/device_driver/common.rs | 38 ++ bootloader/src/bsp/mbox.rs | 10 + bootloader/src/bsp/rpi3.rs | 8 + bootloader/src/bsp/rpi3/console.rs | 11 + bootloader/src/bsp/rpi3/cpu.rs | 14 + bootloader/src/bsp/rpi3/driver.rs | 94 ++++ bootloader/src/bsp/rpi3/kernel.ld | 83 ++++ bootloader/src/bsp/rpi3/memory.rs | 47 ++ bootloader/src/console.rs | 82 ++++ bootloader/src/console/null_console.rs | 41 ++ bootloader/src/cpu.rs | 39 ++ bootloader/src/cpu/boot.rs | 32 ++ bootloader/src/cpu/boot.s | 88 ++++ bootloader/src/driver.rs | 168 ++++++++ bootloader/src/kernel.ld | 79 ++++ bootloader/src/lib.rs | 1 + bootloader/src/main.rs | 127 ++++++ bootloader/src/mbox.rs | 57 +++ bootloader/src/mbox/null_mbox.rs | 38 ++ bootloader/src/panic_wait.rs | 64 +++ bootloader/src/power.rs | 28 ++ bootloader/src/print.rs | 36 ++ bootloader/src/shell.rs | 61 +++ bootloader/src/synchronization.rs | 77 ++++ imageUploader/Makefile | 5 + imageUploader/README.md | 0 imageUploader/imageuploader/__init__.py | 0 imageUploader/imageuploader/main.py | 100 +++++ imageUploader/poetry.lock | 30 ++ imageUploader/pyproject.toml | 16 + imageUploader/tests/__init__.py | 0 43 files changed, 3098 insertions(+) create mode 100644 bootloader/Cargo.lock create mode 100644 bootloader/Cargo.toml create mode 100644 bootloader/Makefile create mode 100644 bootloader/kernel.ld create mode 100644 bootloader/src/arrsting.rs create mode 100644 bootloader/src/bsp.rs create mode 100644 bootloader/src/bsp/device_driver.rs create mode 100644 bootloader/src/bsp/device_driver/bcm.rs create mode 100644 bootloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs create mode 100644 bootloader/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs create mode 100644 bootloader/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs create mode 100644 bootloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs create mode 100644 bootloader/src/bsp/device_driver/common.rs create mode 100644 bootloader/src/bsp/mbox.rs create mode 100644 bootloader/src/bsp/rpi3.rs create mode 100644 bootloader/src/bsp/rpi3/console.rs create mode 100644 bootloader/src/bsp/rpi3/cpu.rs create mode 100644 bootloader/src/bsp/rpi3/driver.rs create mode 100644 bootloader/src/bsp/rpi3/kernel.ld create mode 100644 bootloader/src/bsp/rpi3/memory.rs create mode 100644 bootloader/src/console.rs create mode 100644 bootloader/src/console/null_console.rs create mode 100644 bootloader/src/cpu.rs create mode 100644 bootloader/src/cpu/boot.rs create mode 100644 bootloader/src/cpu/boot.s create mode 100644 bootloader/src/driver.rs create mode 100644 bootloader/src/kernel.ld create mode 100644 bootloader/src/lib.rs create mode 100644 bootloader/src/main.rs create mode 100644 bootloader/src/mbox.rs create mode 100644 bootloader/src/mbox/null_mbox.rs create mode 100644 bootloader/src/panic_wait.rs create mode 100644 bootloader/src/power.rs create mode 100644 bootloader/src/print.rs create mode 100644 bootloader/src/shell.rs create mode 100644 bootloader/src/synchronization.rs create mode 100644 imageUploader/Makefile create mode 100644 imageUploader/README.md create mode 100644 imageUploader/imageuploader/__init__.py create mode 100644 imageUploader/imageuploader/main.py create mode 100644 imageUploader/poetry.lock create mode 100644 imageUploader/pyproject.toml create mode 100644 imageUploader/tests/__init__.py diff --git a/bootloader/Cargo.lock b/bootloader/Cargo.lock new file mode 100644 index 000000000..554f86426 --- /dev/null +++ b/bootloader/Cargo.lock @@ -0,0 +1,26 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aarch64-cpu" +version = "9.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac42a04a61c19fc8196dd728022a784baecc5d63d7e256c01ad1b3fbfab26287" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "bootloader" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "tock-registers", +] + +[[package]] +name = "tock-registers" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml new file mode 100644 index 000000000..73490eb45 --- /dev/null +++ b/bootloader/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bootloader" +version = "0.1.0" +authors = ["HelloHomework"] +edition = "2021" + +[[bin]] +name = "bootloader" +path = "src/main.rs" +test = false +bench = false + +[lib] +test = false + +#[toolchain] +#channel = "nightly" + +[features] +default = ["tock-registers"] + +[workspace] +resolver = "2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aarch64-cpu = "9.4.0" +tock-registers = { version = "0.8.x", default-features = false, features = ["register_types"], optional = true } diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000..9ada1a074 --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,119 @@ +## SPDX-License-Identifier: MIT OR Apache-2.0 +## +## Copyright (c) 2018-2023 Andre Richter +## +## This file is part of the `rust-raspberrypi-OS-tutorials` project. +## https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/ +## +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# BSP ?= rpi3 +TARGET = aarch64-unknown-none-softfloat +KERNEL_BIN = bootloader.img +QEMU_BINARY = qemu-system-aarch64 +QEMU_MACHINE_TYPE = raspi3 +QEMU_RELEASE_ARGS = -display none -d in_asm + +# LD_SCRIPT_PATH = src/ +# LD_SCRIPT_NAME = linker.ld +LD_SCRIPT_NAME = kernel.ld + +# LD_SCRIPT_PATH = $(shell pwd)/src/bsp/raspberrypi +LD_SCRIPT_PATH = src/ +KERNEL_LINKER_SCRIPT = $(LD_SCRIPT_PATH)bsp/rpi3/$(LD_SCRIPT_NAME) + + +RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 + +# Export for build.rs. +export LD_SCRIPT_PATH + + + +##-------------------------------------------------------------------------------------------------- +## Targets and Prerequisites +##-------------------------------------------------------------------------------------------------- +KERNEL_MANIFEST = Cargo.toml +# KERNEL_LINKER_SCRIPT = kernel.ld +LAST_BUILD_CONFIG = target/$(BSP).build_config + +KERNEL_ELF = target/$(TARGET)/debug/bootloader +# KERNEL_ELF = target/$(TARGET)/release/kernel +# This parses cargo's dep-info file. +# https://doc.rust-lang.org/cargo/guide/build-cache.html#dep-info-files +KERNEL_ELF_DEPS = $(filter-out %: ,$(file < $(KERNEL_ELF).d)) $(KERNEL_MANIFEST) $(LAST_BUILD_CONFIG) + + + +##-------------------------------------------------------------------------------------------------- +## Command building blocks +##-------------------------------------------------------------------------------------------------- +RUSTFLAGS = $(RUSTC_MISC_ARGS) \ + -C link-arg=--library-path=$(LD_SCRIPT_PATH) \ + -C link-arg=--script=$(KERNEL_LINKER_SCRIPT) + +RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) \ + -D warnings \ + -D missing_docs + +# FEATURES = --features bsp_$(BSP) +COMPILER_ARGS = --target=$(TARGET) \ +# --release + # $(FEATURES)\ + + +RUSTC_CMD = cargo rustc $(COMPILER_ARGS) +# RUSTC_CMD = cargo miri $(COMPILER_ARGS) +OBJCOPY_CMD = rust-objcopy \ + --strip-all \ + -O binary + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- + +all: $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Save the configuration as a file, so make understands if it changed. +##------------------------------------------------------------------------------ +$(LAST_BUILD_CONFIG): + @rm -f target/*.build_config + @mkdir -p target + @touch $(LAST_BUILD_CONFIG) + +##------------------------------------------------------------------------------ +## Compile the kernel ELF +##------------------------------------------------------------------------------ +$(KERNEL_ELF): $(KERNEL_ELF_DEPS) +# $(call color_header, "Compiling kernel ELF - $(BSP)") + @RUSTFLAGS="$(RUSTFLAGS)" $(RUSTC_CMD) + +##------------------------------------------------------------------------------ +## Generate the stripped kernel binary +##------------------------------------------------------------------------------ +$(KERNEL_BIN): $(KERNEL_ELF) + $(call color_header, "Generating stripped binary") + @$(OBJCOPY_CMD) $(KERNEL_ELF) $(KERNEL_BIN) + $(call color_progress_prefix, "Name") + @echo $(KERNEL_BIN) + $(call color_progress_prefix, "Size") + $(call disk_usage_KiB, $(KERNEL_BIN)) + + +##------------------------------------------------------------------------------ +## Debug +##------------------------------------------------------------------------------ +debug: +# qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial null -serial stdio +# qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial stdio + qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial null -serial pty + + +##------------------------------------------------------------------------------ +## Clean +##------------------------------------------------------------------------------ +clean: + rm -rf target $(KERNEL_BIN) diff --git a/bootloader/kernel.ld b/bootloader/kernel.ld new file mode 100644 index 000000000..bda4f090f --- /dev/null +++ b/bootloader/kernel.ld @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2022 Andre Richter + */ + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + /* Set the link address to 32 MiB */ + . = 0x2000000; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + __binary_nonzero_start = .; + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */ + . = ALIGN(8); + __binary_nonzero_end_exclusive = .; + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} \ No newline at end of file diff --git a/bootloader/src/arrsting.rs b/bootloader/src/arrsting.rs new file mode 100644 index 000000000..93d123059 --- /dev/null +++ b/bootloader/src/arrsting.rs @@ -0,0 +1,51 @@ +// #[derive(PartialEq)] +const MAXIUM_LEN: usize = 256; +pub struct ArrString { + data: [u8; MAXIUM_LEN], // maxium size MAXIUM_LEN + len: usize, +} + +#[allow(dead_code)] +impl ArrString { + pub fn new(s: &str) -> Self { + let mut data: [u8; MAXIUM_LEN] = [0; MAXIUM_LEN]; + for (index, c) in s.chars().enumerate() { + data[index] = c as u8; + } + ArrString { + data: data, + len: s.len(), + } + } + + pub fn get_len(&self) -> usize { + self.len + } + + pub fn get_data(&self) -> [u8 ;MAXIUM_LEN] { + self.data + } + + pub fn clean_buf(&mut self) { + for x in self.data.as_mut() { + *x = 0 as u8; + } + self.len = 0; + } + + pub fn push_char(&mut self, c: char) { + self.data[self.len] = c as u8; + self.len += 1; + } + + fn compare(&self, other: &Self) -> bool { + self.data.starts_with(&other.data) + } +} + +#[allow(dead_code)] +impl PartialEq for ArrString { + fn eq(&self, other: &Self) -> bool { + self.compare(other) + } +} diff --git a/bootloader/src/bsp.rs b/bootloader/src/bsp.rs new file mode 100644 index 000000000..fbcc24c86 --- /dev/null +++ b/bootloader/src/bsp.rs @@ -0,0 +1,5 @@ +mod device_driver; + +mod rpi3; + +pub use rpi3::*; \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver.rs b/bootloader/src/bsp/device_driver.rs new file mode 100644 index 000000000..17243ac8b --- /dev/null +++ b/bootloader/src/bsp/device_driver.rs @@ -0,0 +1,4 @@ +mod bcm; +mod common; + +pub use bcm::*; \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver/bcm.rs b/bootloader/src/bsp/device_driver/bcm.rs new file mode 100644 index 000000000..38d54f84b --- /dev/null +++ b/bootloader/src/bsp/device_driver/bcm.rs @@ -0,0 +1,10 @@ +mod bcm2xxx_gpio; +// mod bcm2xxx_pl011_uart; +mod bcm2xxx_mini_uart; +mod bcm2xxx_mbox; + + +pub use bcm2xxx_gpio::*; +// pub use bcm2xxx_pl011_uart::*; +pub use bcm2xxx_mini_uart::*; +pub use bcm2xxx_mbox::*; \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs new file mode 100644 index 000000000..cb71eabf6 --- /dev/null +++ b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! GPIO Driver. + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, + synchronization::NullLock, +}; +use tock_registers::{ + interfaces::{ReadWriteable, Writeable}, + register_bitfields, register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// GPIO registers. +// +// Descriptions taken from +// - https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +// - https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf +register_bitfields! { + u32, + + /// GPIO Function Select 1 + GPFSEL1 [ + /// Pin 15 + FSEL15 OFFSET(15) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100, // PL011 UART RX + AltFunc5 = 0b010 // PL011 UART RX + ], + + /// Pin 14 + FSEL14 OFFSET(12) NUMBITS(3) [ + Input = 0b000, + Output = 0b001, + AltFunc0 = 0b100, // PL011 UART TX + AltFunc5 = 0b010 // PL011 UART RX + ] + ], + + /// GPIO Pull-up/down Register + /// + /// BCM2837 only. + GPPUD [ + /// Controls the actuation of the internal pull-up/down control line to ALL the GPIO pins. + PUD OFFSET(0) NUMBITS(2) [ + Off = 0b00, + PullDown = 0b01, + PullUp = 0b10 + ] + ], + + /// GPIO Pull-up/down Clock Register 0 + /// + /// BCM2837 only. + GPPUDCLK0 [ + /// Pin 15 + PUDCLK15 OFFSET(15) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ], + + /// Pin 14 + PUDCLK14 OFFSET(14) NUMBITS(1) [ + NoEffect = 0, + AssertClock = 1 + ] + ], + + /// GPIO Pull-up / Pull-down Register 0 + /// + /// BCM2711 only. + GPIO_PUP_PDN_CNTRL_REG0 [ + /// Pin 15 + GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [ + NoResistor = 0b00, + PullUp = 0b01 + ], + + /// Pin 14 + GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [ + NoResistor = 0b00, + PullUp = 0b01 + ] + ] +} + +register_structs! { + #[allow(non_snake_case)] + RegisterBlock { + (0x00 => _reserved1), + (0x04 => GPFSEL1: ReadWrite), + (0x08 => _reserved2), + (0x94 => GPPUD: ReadWrite), + (0x98 => GPPUDCLK0: ReadWrite), + (0x9C => _reserved3), + (0xE4 => GPIO_PUP_PDN_CNTRL_REG0: ReadWrite), + (0xE8 => @END), + } +} + +/// Abstraction for the associated MMIO registers. +type Registers = MMIODerefWrapper; + +struct GPIOInner { + registers: Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the GPIO HW. +pub struct GPIO { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl GPIOInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + registers: Registers::new(mmio_start_addr), + } + } + + /// Disable pull-up/down on pins 14 and 15. + fn disable_pud_14_15_bcm2837(&mut self) { + use crate::cpu; + + // Make an educated guess for a good delay value (Sequence described in the BCM2837 + // peripherals PDF). + // + // - According to Wikipedia, the fastest RPi4 clocks around 1.5 GHz. + // - The Linux 2837 GPIO driver waits 1 µs between the steps. + // + // So lets try to be on the safe side and default to 2000 cycles, which would equal 1 µs + // would the CPU be clocked at 2 GHz. + const DELAY: usize = 2000; + + self.registers.GPPUD.write(GPPUD::PUD::Off); + cpu::spin_for_cycles(DELAY); + + self.registers + .GPPUDCLK0 + .write(GPPUDCLK0::PUDCLK15::AssertClock + GPPUDCLK0::PUDCLK14::AssertClock); + cpu::spin_for_cycles(DELAY); + + self.registers.GPPUD.write(GPPUD::PUD::Off); + self.registers.GPPUDCLK0.set(0); + } + + /// Map PL011 UART as standard output. + /// + /// TX to pin 14 + /// RX to pin 15 + #[allow(dead_code)] + pub fn map_pl011_uart(&mut self) { + // Select the UART on pins 14 and 15. + self.registers + .GPFSEL1 + .modify(GPFSEL1::FSEL15::AltFunc0 + GPFSEL1::FSEL14::AltFunc0); + + // Disable pull-up/down on pins 14 and 15. + self.disable_pud_14_15_bcm2837(); + } + + /// Map MINI UART as standard output. + /// + /// TX to pin 14 + /// RX to pin 15 + pub fn map_mini_uart(&mut self) { + // Select the UART on pins 14 and 15. + self.registers + .GPFSEL1 + .modify(GPFSEL1::FSEL15::AltFunc5 + GPFSEL1::FSEL14::AltFunc5); + + // Disable pull-up/down on pins 14 and 15. + self.disable_pud_14_15_bcm2837(); + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl GPIO { + pub const COMPATIBLE: &'static str = "BCM GPIO"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + inner: NullLock::new(GPIOInner::new(mmio_start_addr)), + } + } + + /// Concurrency safe version of `GPIOInner.map_pl011_uart()` + #[allow(dead_code)] + pub fn map_pl011_uart(&self) { + self.inner.lock(|inner| inner.map_pl011_uart()) + } + + /// Concurrency safe version of `GPIOInner.map_mini_uart()` + pub fn map_mini_uart(&self) { + self.inner.lock(|inner| inner.map_mini_uart()) + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } +} \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs new file mode 100644 index 000000000..a2051dde4 --- /dev/null +++ b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! MBOX Driver. + +use crate::println; + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, + driver, mbox, + synchronization::{interface::Mutex, NullLock}, +}; + +use aarch64_cpu::asm; + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_bitfields, register_structs, + registers::ReadWrite, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// MBOX registers + +const CHANNEL_GPU: u32 = 8; + +register_bitfields! { + u32, + + MBOX_READ_REG [ + DATA OFFSET(0) NUMBITS(32) [] + ], + MBOX_PEEK_REG [ + DATA OFFSET(0) NUMBITS(32) [] + ], + MBOX_SENDER_REG [ + DATA OFFSET(0) NUMBITS(32) [] + ], + MBOX_STATUS_REG [ + DATA OFFSET(0) NUMBITS(32) [ + empty = 0x4000_0000, + full = 0x8000_0000 + ] + ], + MBOX_CONFIG_REG [ + DATA OFFSET(0) NUMBITS(32) [] + ], + MBOX_WRITE_REG [ + DATA OFFSET(0) NUMBITS(32) [] + ] +} + +register_structs! { + #[allow(non_snake_case)] + RegisterBlock { + (0x00 => MBOX_READ_REG: ReadWrite), + (0x04 => _reserved1), + (0x10 => MBOX_PEEK_REG: ReadWrite), + (0x14 => MBOX_SENDER_REG: ReadWrite), + (0x18 => MBOX_STATUS_REG: ReadWrite), + (0x1C => MBOX_CONFIG_REG: ReadWrite), + (0x20 => MBOX_WRITE_REG: ReadWrite), + (0x24 => @END), + } +} + +/// Abstraction for the associated MMIO registers. +type Registers = MMIODerefWrapper; + +struct MBOXInner { + registers: Registers, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the MBOX HW. +pub struct MBOX { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +#[repr(align(64))] +#[derive(Copy, Clone)] +struct MailboxMsg { + buffer: [u32; 8], +} + +impl MBOXInner { + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + registers: Registers::new(mmio_start_addr), + } + } + + pub fn init(&mut self) {} + + fn mailbox_read(&mut self, channel: u32) -> u32 { + loop { + while self + .registers + .MBOX_STATUS_REG + .matches_all(MBOX_STATUS_REG::DATA::empty) + { + asm::nop(); + } + + let data = self.registers.MBOX_READ_REG.get(); + + if (data & 0xF) == channel { + return data; + } + } + } + + fn mailbox_write(&mut self, channel: u32, data: u32) { + while self + .registers + .MBOX_STATUS_REG + .matches_all(MBOX_STATUS_REG::DATA::full) + { + asm::nop(); + } + self.registers.MBOX_WRITE_REG.set(data | channel); + } + + fn mailbox_call(&mut self, mailbox: &mut [u32]) -> bool { + let mailbox_ptr = mailbox.as_mut_ptr() as u32; + if mailbox_ptr & 0xF != 0 { + println!("{:x}", mailbox_ptr); + println!("ptr failed"); + return false; + } + + Self::mailbox_write(self, CHANNEL_GPU, mailbox_ptr); + + if Self::mailbox_read(self, CHANNEL_GPU) != mailbox_ptr | CHANNEL_GPU { + println!("Call failed"); + return false; + } + + true + } + + pub fn get_board_revision(&mut self) -> u32 { + let mut mailbox = MailboxMsg { buffer: [0; 8] }; + mailbox.buffer[0] = 7 * 4; + mailbox.buffer[1] = 0; + mailbox.buffer[2] = 0x0001_0002; + mailbox.buffer[3] = 4; + mailbox.buffer[4] = 0; + mailbox.buffer[5] = 0; + mailbox.buffer[6] = 0x0000_0000; + // let mut mbox = [ + // 7 * 4, // Buffer size in bytes + // 0, // Request/response code + // 0x0001_0002, // Tag: Get board revision + // 4, // Buffer size in bytes + // 0, // Tag request code + // 0, // Value buffer + // 0x0000_0000, // End tag + // ]; + if !Self::mailbox_call(self, &mut mailbox.buffer) { + println!("get board failed"); + return 0; + } + mailbox.buffer[5] + } + + pub fn get_arm_memory(&mut self) -> (u32, u32) { + let mut mailbox = MailboxMsg { buffer: [0; 8] }; + mailbox.buffer[0] = 8 * 4; + mailbox.buffer[1] = 0; + mailbox.buffer[2] = 0x0001_0005; + mailbox.buffer[3] = 8; + mailbox.buffer[4] = 0; + mailbox.buffer[5] = 0; + mailbox.buffer[6] = 0; + mailbox.buffer[7] = 0x0000_0000; + // let mut mbox = [ + // 8 * 4, // Buffer size in bytes + // 0, // Request/response code + // 0x0001_0005, // Tag: Get ARM memory + // 8, // Buffer size in bytes + // 0, // Tag request code + // 0, // Value buffer + // 0, // Value buffer + // 0x0000_0000, // End tag + // ]; + if !Self::mailbox_call(self, &mut mailbox.buffer) { + println!("get mem failed"); + return (0, 0); + } + (mailbox.buffer[5], mailbox.buffer[6]) + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl MBOX { + pub const COMPATIBLE: &'static str = "RPI3 MBOX"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + inner: NullLock::new(MBOXInner::new(mmio_start_addr)), + } + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ + +impl driver::interface::DeviceDriver for MBOX { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } + + unsafe fn init(&self) -> Result<(), &'static str> { + self.inner.lock(|inner| inner.init()); + Ok(()) + } +} + +impl mbox::interface::Boardinfo for MBOX { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn get_arm_memory(&self) -> (u32, u32) { + self.inner.lock(|inner| inner.get_arm_memory()) + } + + fn get_board_revision(&self) -> u32 { + self.inner.lock(|inner| inner.get_board_revision()) + } +} + +impl mbox::interface::All for MBOX {} \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs new file mode 100644 index 000000000..40e8d83a3 --- /dev/null +++ b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! MINI UART driver. +//! +//! # Resources +//! +//! - +//! - + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, console, cpu, driver, synchronization, + synchronization::NullLock, +}; +use aarch64_cpu::asm; +use core::fmt; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_bitfields, register_structs, + registers::{ReadOnly, ReadWrite}, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// MINI UART registers. +// +// Descriptions taken from "PrimeCell UART (MINI) Technical Reference Manual" r1p5. + +register_bitfields! { + u32, + /// Auxiliary peripherals Register + AUXIRQ [ + MINI_UART_IRQ OFFSET(0) NUMBITS(1) [] + ], + + /// Auxiliary enables + AUXENB [ + MINI_UART_ENABLE OFFSET(0) NUMBITS(1) [ + Disable = 0, + Enable = 1 + ] + ], + + /// Mini Uart I/O Data + AUX_MU_IO_REG [ + /// LS 8 bits Baudrate read/write, DLAB=1 + /// Transmit data write, DLAB=0 + /// Receive data read, DLAB=0 + DATA OFFSET(0) NUMBITS(8) [] + ], + + + /// Mini Uart Interrupt Enable + AUX_MU_IER_REG [ + RX OFFSET(0) NUMBITS(1) [ + Disable = 0, + Enable = 1 + ], + + TX OFFSET(1) NUMBITS(1) [ + Disable = 0, + Enable = 1 + ] + ], + + /// Mini Uart Interrupt Identify + AUX_MU_IIR_REG [ + /// This bit is clear whenever an interrupt is pending + INTERRUPT_PENDING OFFSET(0) NUMBITS(1) [ + Disable = 0, + Enable = 1 + ], + + /// On read this register shows the interrupt ID bit + /// 00 : No interrupts + /// 01 : Transmit holding register empty + /// 10 : Receiver holds valid byte + /// 11 : + /// On write: + /// Writing with bit 1 set will clear the receive FIFO + /// Writing with bit 2 set will clear the transmit FIFO + INTERRUPT_ID OFFSET(1) NUMBITS(2) [ + NO_INTERRUPT = 0b00, + TX_HOLD = 0b01, + RX_HOLD = 0b10, + NOT_POSSIBLE = 0b11 + ] + ], + + // Mini Uart Line Control + AUX_MU_LCR_REG [ + DATA_SIZE OFFSET(0) NUMBITS(2) [ + EIGHT_BIT = 0b00, + SEVEN_BIT = 0b11 + ] + ], + + // Mini Uart Modem Control + AUX_MU_MCR_REG [ + RTS OFFSET(1) NUMBITS(1) [ + Disable = 0, + Enable = 1 + ] + ], + + // Mini Uart Line Status + AUX_MU_LSR_REG [ + DATA_READY OFFSET(0) NUMBITS(1) [ + No = 0, + Yes = 1 + ], + TX_EMPTY OFFSET(5) NUMBITS(1) [ + No = 0, + Yes = 1 + ], + TX_IDLE OFFSET(6) NUMBITS(1) [ + No = 0, + Yes = 1 + ], + ], + + /// Mini Uart Extra Control + AUX_MU_CNTL_REG [ + ALL OFFSET(0) NUMBITS(8) + ], + + /// Mini Uart Baudrate + AUX_MU_BAUD_REG [ + BUAD_RATE OFFSET(0) NUMBITS(16) [ + Default = 270 + ] + ] +} + +register_structs! { + #[allow(non_snake_case)] + pub RegisterBlock { + (0x00 => AUXIRQ: ReadOnly), + (0x04 => AUXENB: ReadWrite), + (0x08 => _reserved1), + (0x40 => AUX_MU_IO_REG: ReadWrite), + (0x44 => AUX_MU_IER_REG: ReadWrite), + (0x48 => AUX_MU_IIR_REG: ReadWrite), + (0x4c => AUX_MU_LCR_REG: ReadWrite), + (0x50 => AUX_MU_MCR_REG: ReadWrite), + (0x54 => AUX_MU_LSR_REG: ReadOnly), + (0x58 => _reserved2), + (0x60 => AUX_MU_CNTL_REG: ReadWrite), + (0x64 => _reserved3), + (0x68 => AUX_MU_BAUD_REG: ReadWrite), + (0x6C => @END), + } +} + +/// Abstraction for the associated MMIO registers. +type Registers = MMIODerefWrapper; + +#[derive(PartialEq)] +enum BlockingMode { + Blocking, + NonBlocking, +} + +struct MiniUartInner { + registers: Registers, + chars_written: usize, + chars_read: usize, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the UART. +pub struct MiniUart { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl MiniUartInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + registers: Registers::new(mmio_start_addr), + chars_written: 0, + chars_read: 0, + } + } + + /// Set up baud rate and characteristics. + pub fn init(&mut self) { + // Set AUXENB register to enable mini UART. Then mini UART register can be accessed. + self.registers.AUXENB.set(1); + + // Set AUX_MU_CNTL_REG to 0. Disable transmitter and receiver during configuration. + // Set AUX_MU_IER_REG to 0. Disable interrupt because currently you don’t need interrupt. + // Set AUX_MU_LCR_REG to 3. Set the data size to 8 bit. + // Set AUX_MU_MCR_REG to 0. Don’t need auto flow control. + // Set AUX_MU_BAUD to 270. Set baud rate to 115200 + // After booting, the system clock is 250 MHz. + // Set AUX_MU_IIR_REG to 6. + // Set AUX_MU_CNTL_REG to 3. Enable the transmitter and receiver. + self.registers.AUX_MU_CNTL_REG.set(0); + self.registers.AUX_MU_IER_REG.set(0); + self.registers.AUX_MU_LCR_REG.set(3); + self.registers.AUX_MU_MCR_REG.set(0); + self.registers + .AUX_MU_BAUD_REG + .write(AUX_MU_BAUD_REG::BUAD_RATE::Default); + // self.registers.AUX_MU_IIR_REG.set(0xc6); //todo NOT_POSSIBLE + self.registers.AUX_MU_IIR_REG.write(AUX_MU_IIR_REG::INTERRUPT_ID::NOT_POSSIBLE); //todo NOT_POSSIBLE + + self.registers.AUX_MU_CNTL_REG.set(3); + } + + /// Send a character. + fn write_char(&mut self, c: char) { + // wait while input + while self + .registers + .AUX_MU_LSR_REG + .matches_all(AUX_MU_LSR_REG::TX_IDLE::No) + { + asm::nop(); + } + + // Write the character to the buffer. + self.registers.AUX_MU_IO_REG.set(c as u32); + + self.chars_written += 1; + } + + /// Block execution until the last buffered character has been physically put on the TX wire. + fn flush(&self) { + // Spin until the busy bit is cleared. + while self + .registers + .AUX_MU_LSR_REG + .matches_all(AUX_MU_LSR_REG::TX_IDLE::No) + { + cpu::nop(); + } + } + + /// Retrieve a character. + fn read_char_converting(&mut self, blocking_mode: BlockingMode) -> Option { + // If RX FIFO is empty, + if self + .registers + .AUX_MU_LSR_REG + .matches_all(AUX_MU_LSR_REG::DATA_READY::No) + { + // immediately return in non-blocking mode. + if blocking_mode == BlockingMode::NonBlocking { + return None; + } + + // Otherwise, wait until a char was received. + while self + .registers + .AUX_MU_LSR_REG + .matches_all(AUX_MU_LSR_REG::DATA_READY::No) + { + cpu::nop(); + } + } + + // Otherwise, wait until a char was received. + while self + .registers + .AUX_MU_LSR_REG + .matches_all(AUX_MU_LSR_REG::DATA_READY::No) + { + cpu::nop(); + } + + // Read one character. + let ret = self.registers.AUX_MU_IO_REG.get() as u8 as char; + + // Convert carrige return to newline. + // if ret == '\r' { + // ret = '\n' + // } + + // Update statistics. + self.chars_read += 1; + + Some(ret) + } +} + +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl fmt::Write for MiniUartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.write_char(c); + } + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl MiniUart { + pub const COMPATIBLE: &'static str = "MINI UART"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + inner: NullLock::new(MiniUartInner::new(mmio_start_addr)), + } + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for MiniUart { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } + + unsafe fn init(&self) -> Result<(), &'static str> { + self.inner.lock(|inner| inner.init()); + + Ok(()) + } +} + +impl console::interface::Write for MiniUart { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn write_char(&self, c: char) { + self.inner.lock(|inner| inner.write_char(c)); + } + + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + // Fully qualified syntax for the call to `core::fmt::Write::write_fmt()` to increase + // readability. + self.inner.lock(|inner| fmt::Write::write_fmt(inner, args)) + } + + fn flush(&self) { + // Spin until TX FIFO empty is set. + self.inner.lock(|inner| inner.flush()); + } +} + +impl console::interface::Read for MiniUart { + fn read_char(&self) -> char { + self.inner + .lock(|inner| inner.read_char_converting(BlockingMode::Blocking).unwrap()) + } + + fn clear_rx(&self) { + // Read from the RX FIFO until it is indicating empty. + while self + .inner + .lock(|inner| inner.read_char_converting(BlockingMode::NonBlocking)) + .is_some() + {} + } +} + +impl console::interface::Statistics for MiniUart { + fn chars_written(&self) -> usize { + self.inner.lock(|inner| inner.chars_written) + } + + fn chars_read(&self) -> usize { + self.inner.lock(|inner| inner.chars_read) + } +} + +impl console::interface::All for MiniUart {} diff --git a/bootloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs new file mode 100644 index 000000000..1edf35938 --- /dev/null +++ b/bootloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! PL011 UART driver. +//! +//! # Resources +//! +//! - +//! - + +use crate::{ + bsp::device_driver::common::MMIODerefWrapper, console, cpu, driver, synchronization, + synchronization::NullLock, +}; +use core::fmt; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_bitfields, register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +// PL011 UART registers. +// +// Descriptions taken from "PrimeCell UART (PL011) Technical Reference Manual" r1p5. +register_bitfields! { + u32, + + /// Flag Register. + FR [ + /// Transmit FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// Line Control Register, LCR_H. + /// + /// - If the FIFO is disabled, this bit is set when the transmit holding register is empty. + /// - If the FIFO is enabled, the TXFE bit is set when the transmit FIFO is empty. + /// - This bit does not indicate if there is data in the transmit shift register. + TXFE OFFSET(7) NUMBITS(1) [], + + /// Transmit FIFO full. The meaning of this bit depends on the state of the FEN bit in the + /// LCR_H Register. + /// + /// - If the FIFO is disabled, this bit is set when the transmit holding register is full. + /// - If the FIFO is enabled, the TXFF bit is set when the transmit FIFO is full. + TXFF OFFSET(5) NUMBITS(1) [], + + /// Receive FIFO empty. The meaning of this bit depends on the state of the FEN bit in the + /// LCR_H Register. + /// + /// - If the FIFO is disabled, this bit is set when the receive holding register is empty. + /// - If the FIFO is enabled, the RXFE bit is set when the receive FIFO is empty. + RXFE OFFSET(4) NUMBITS(1) [], + + /// UART busy. If this bit is set to 1, the UART is busy transmitting data. This bit remains + /// set until the complete byte, including all the stop bits, has been sent from the shift + /// register. + /// + /// This bit is set as soon as the transmit FIFO becomes non-empty, regardless of whether + /// the UART is enabled or not. + BUSY OFFSET(3) NUMBITS(1) [] + ], + + /// Integer Baud Rate Divisor. + IBRD [ + /// The integer baud rate divisor. + BAUD_DIVINT OFFSET(0) NUMBITS(16) [] + ], + + /// Fractional Baud Rate Divisor. + FBRD [ + /// The fractional baud rate divisor. + BAUD_DIVFRAC OFFSET(0) NUMBITS(6) [] + ], + + /// Line Control Register. + LCR_H [ + /// Word length. These bits indicate the number of data bits transmitted or received in a + /// frame. + #[allow(clippy::enum_variant_names)] + WLEN OFFSET(5) NUMBITS(2) [ + FiveBit = 0b00, + SixBit = 0b01, + SevenBit = 0b10, + EightBit = 0b11 + ], + + /// Enable FIFOs: + /// + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become 1-byte-deep holding + /// registers. + /// + /// 1 = Transmit and receive FIFO buffers are enabled (FIFO mode). + FEN OFFSET(4) NUMBITS(1) [ + FifosDisabled = 0, + FifosEnabled = 1 + ] + ], + + /// Control Register. + CR [ + /// Receive enable. If this bit is set to 1, the receive section of the UART is enabled. + /// Data reception occurs for either UART signals or SIR signals depending on the setting of + /// the SIREN bit. When the UART is disabled in the middle of reception, it completes the + /// current character before stopping. + RXE OFFSET(9) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// Transmit enable. If this bit is set to 1, the transmit section of the UART is enabled. + /// Data transmission occurs for either UART signals, or SIR signals depending on the + /// setting of the SIREN bit. When the UART is disabled in the middle of transmission, it + /// completes the current character before stopping. + TXE OFFSET(8) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// UART enable: + /// + /// 0 = UART is disabled. If the UART is disabled in the middle of transmission or + /// reception, it completes the current character before stopping. + /// + /// 1 = The UART is enabled. Data transmission and reception occurs for either UART signals + /// or SIR signals depending on the setting of the SIREN bit + UARTEN OFFSET(0) NUMBITS(1) [ + /// If the UART is disabled in the middle of transmission or reception, it completes the + /// current character before stopping. + Disabled = 0, + Enabled = 1 + ] + ], + + /// Interrupt Clear Register. + ICR [ + /// Meta field for all pending interrupts. + ALL OFFSET(0) NUMBITS(11) [] + ] +} + +register_structs! { + #[allow(non_snake_case)] + pub RegisterBlock { + (0x00 => DR: ReadWrite), + (0x04 => _reserved1), + (0x18 => FR: ReadOnly), + (0x1c => _reserved2), + (0x24 => IBRD: WriteOnly), + (0x28 => FBRD: WriteOnly), + (0x2c => LCR_H: WriteOnly), + (0x30 => CR: WriteOnly), + (0x34 => _reserved3), + (0x44 => ICR: WriteOnly), + (0x48 => @END), + } +} + +/// Abstraction for the associated MMIO registers. +type Registers = MMIODerefWrapper; + +#[derive(PartialEq)] +enum BlockingMode { + Blocking, + NonBlocking, +} + +struct PL011UartInner { + registers: Registers, + chars_written: usize, + chars_read: usize, +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Representation of the UART. +pub struct PL011Uart { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl PL011UartInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + registers: Registers::new(mmio_start_addr), + chars_written: 0, + chars_read: 0, + } + } + + /// Set up baud rate and characteristics. + /// + /// This results in 8N1 and 921_600 baud. + /// + /// The calculation for the BRD is (we set the clock to 48 MHz in config.txt): + /// `(48_000_000 / 16) / 921_600 = 3.2552083`. + /// + /// This means the integer part is `3` and goes into the `IBRD`. + /// The fractional part is `0.2552083`. + /// + /// `FBRD` calculation according to the PL011 Technical Reference Manual: + /// `INTEGER((0.2552083 * 64) + 0.5) = 16`. + /// + /// Therefore, the generated baud rate divider is: `3 + 16/64 = 3.25`. Which results in a + /// genrated baud rate of `48_000_000 / (16 * 3.25) = 923_077`. + /// + /// Error = `((923_077 - 921_600) / 921_600) * 100 = 0.16%`. + pub fn init(&mut self) { + // Execution can arrive here while there are still characters queued in the TX FIFO and + // actively being sent out by the UART hardware. If the UART is turned off in this case, + // those queued characters would be lost. + // + // For example, this can happen during runtime on a call to panic!(), because panic!() + // initializes its own UART instance and calls init(). + // + // Hence, flush first to ensure all pending characters are transmitted. + self.flush(); + + // Turn the UART off temporarily. + self.registers.CR.set(0); + + // Clear all pending interrupts. + self.registers.ICR.write(ICR::ALL::CLEAR); + + // From the PL011 Technical Reference Manual: + // + // The LCR_H, IBRD, and FBRD registers form the single 30-bit wide LCR Register that is + // updated on a single write strobe generated by a LCR_H write. So, to internally update the + // contents of IBRD or FBRD, a LCR_H write must always be performed at the end. + // + // Set the baud rate, 8N1 and FIFO enabled. + self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(3)); + self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); + self.registers + .LCR_H + .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); + + // Turn the UART on. + self.registers + .CR + .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); + } + + /// Send a character. + fn write_char(&mut self, c: char) { + // Spin while TX FIFO full is set, waiting for an empty slot. + while self.registers.FR.matches_all(FR::TXFF::SET) { + cpu::nop(); + } + + // Write the character to the buffer. + self.registers.DR.set(c as u32); + + self.chars_written += 1; + } + + /// Block execution until the last buffered character has been physically put on the TX wire. + fn flush(&self) { + // Spin until the busy bit is cleared. + while self.registers.FR.matches_all(FR::BUSY::SET) { + cpu::nop(); + } + } + + /// Retrieve a character. + fn read_char_converting(&mut self, blocking_mode: BlockingMode) -> Option { + // If RX FIFO is empty, + if self.registers.FR.matches_all(FR::RXFE::SET) { + // immediately return in non-blocking mode. + if blocking_mode == BlockingMode::NonBlocking { + return None; + } + + // Otherwise, wait until a char was received. + while self.registers.FR.matches_all(FR::RXFE::SET) { + cpu::nop(); + } + } + + // Read one character. + let mut ret = self.registers.DR.get() as u8 as char; + + // Convert carrige return to newline. + if ret == '\r' { + ret = '\n' + } + + // Update statistics. + self.chars_read += 1; + + Some(ret) + } +} + +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +/// +/// See [`src/print.rs`]. +/// +/// [`src/print.rs`]: ../../print/index.html +impl fmt::Write for PL011UartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.write_char(c); + } + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl PL011Uart { + pub const COMPATIBLE: &'static str = "BCM PL011 UART"; + + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(mmio_start_addr: usize) -> Self { + Self { + inner: NullLock::new(PL011UartInner::new(mmio_start_addr)), + } + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use synchronization::interface::Mutex; + +impl driver::interface::DeviceDriver for PL011Uart { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } + + unsafe fn init(&self) -> Result<(), &'static str> { + self.inner.lock(|inner| inner.init()); + + Ok(()) + } +} + +impl console::interface::Write for PL011Uart { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn write_char(&self, c: char) { + self.inner.lock(|inner| inner.write_char(c)); + } + + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + // Fully qualified syntax for the call to `core::fmt::Write::write_fmt()` to increase + // readability. + self.inner.lock(|inner| fmt::Write::write_fmt(inner, args)) + } + + fn flush(&self) { + // Spin until TX FIFO empty is set. + self.inner.lock(|inner| inner.flush()); + } +} + +impl console::interface::Read for PL011Uart { + fn read_char(&self) -> char { + self.inner + .lock(|inner| inner.read_char_converting(BlockingMode::Blocking).unwrap()) + } + + fn clear_rx(&self) { + // Read from the RX FIFO until it is indicating empty. + while self + .inner + .lock(|inner| inner.read_char_converting(BlockingMode::NonBlocking)) + .is_some() + {} + } +} + +impl console::interface::Statistics for PL011Uart { + fn chars_written(&self) -> usize { + self.inner.lock(|inner| inner.chars_written) + } + + fn chars_read(&self) -> usize { + self.inner.lock(|inner| inner.chars_read) + } +} + +impl console::interface::All for PL011Uart {} \ No newline at end of file diff --git a/bootloader/src/bsp/device_driver/common.rs b/bootloader/src/bsp/device_driver/common.rs new file mode 100644 index 000000000..cd900024b --- /dev/null +++ b/bootloader/src/bsp/device_driver/common.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020-2023 Andre Richter + +//! Common device driver code. + +use core::{marker::PhantomData, ops}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct MMIODerefWrapper { + start_addr: usize, + phantom: PhantomData T>, +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl MMIODerefWrapper { + /// Create an instance. + pub const unsafe fn new(start_addr: usize) -> Self { + Self { + start_addr, + phantom: PhantomData, + } + } +} + +impl ops::Deref for MMIODerefWrapper { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.start_addr as *const _) } + } +} \ No newline at end of file diff --git a/bootloader/src/bsp/mbox.rs b/bootloader/src/bsp/mbox.rs new file mode 100644 index 000000000..1e13075a1 --- /dev/null +++ b/bootloader/src/bsp/mbox.rs @@ -0,0 +1,10 @@ +use crate::mbox; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the console. +pub fn mbox() -> &'static dyn mbox::interface::All { + &super::driver::MBOX +} \ No newline at end of file diff --git a/bootloader/src/bsp/rpi3.rs b/bootloader/src/bsp/rpi3.rs new file mode 100644 index 000000000..71a1c355b --- /dev/null +++ b/bootloader/src/bsp/rpi3.rs @@ -0,0 +1,8 @@ +pub mod cpu; +pub mod driver; +pub mod memory; + +#[allow(dead_code)] +pub fn board_name() -> &'static str { + "Raspberry Pi 3" +} diff --git a/bootloader/src/bsp/rpi3/console.rs b/bootloader/src/bsp/rpi3/console.rs new file mode 100644 index 000000000..9d09183da --- /dev/null +++ b/bootloader/src/bsp/rpi3/console.rs @@ -0,0 +1,11 @@ +use crate::console; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the console. +pub fn console() -> &'static dyn console::interface::All { + // &super::driver::PL011_UART + &super::driver::MINI_UART +} \ No newline at end of file diff --git a/bootloader/src/bsp/rpi3/cpu.rs b/bootloader/src/bsp/rpi3/cpu.rs new file mode 100644 index 000000000..fc374a0c9 --- /dev/null +++ b/bootloader/src/bsp/rpi3/cpu.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! BSP Processor code. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Used by `arch` code to find the early boot core. +#[no_mangle] +#[link_section = ".text._start_arguments"] +pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file diff --git a/bootloader/src/bsp/rpi3/driver.rs b/bootloader/src/bsp/rpi3/driver.rs new file mode 100644 index 000000000..7bb69e441 --- /dev/null +++ b/bootloader/src/bsp/rpi3/driver.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! BSP driver support. + +use super::memory::map::mmio; +use crate::{bsp::device_driver, console, mbox, driver as generic_driver}; +use core::sync::atomic::{AtomicBool, Ordering}; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static MINI_UART: device_driver::MiniUart = + unsafe { device_driver::MiniUart::new(mmio::MINI_UART_START) }; +// static PL011_UART: device_driver::PL011Uart = +// unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; +static GPIO: device_driver::GPIO = unsafe { device_driver::GPIO::new(mmio::GPIO_START) }; +pub static MBOX: device_driver::MBOX = + unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; + // unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + // console::register_console(&PL011_UART); + console::register_console(&MINI_UART); + + Ok(()) +} + +/// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + // GPIO.map_pl011_uart(); + GPIO.map_mini_uart(); + Ok(()) +} + + +fn post_init_mbox() -> Result<(), &'static str> { + mbox::register_mbox(&MBOX); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = + // generic_driver::DeviceDriverDescriptor::new(&PL011_UART, Some(post_init_uart)); + generic_driver::DeviceDriverDescriptor::new(&MINI_UART, Some(post_init_uart)); + generic_driver::driver_manager().register_driver(uart_descriptor); + + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = generic_driver::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + generic_driver::driver_manager().register_driver(gpio_descriptor); + + Ok(()) +} + +fn driver_mbox() -> Result<(), &'static str> { + let mbox_descriptor = generic_driver::DeviceDriverDescriptor::new(&MBOX, Some(post_init_mbox)); + generic_driver::driver_manager().register_driver(mbox_descriptor); + + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + driver_gpio()?; + driver_uart()?; + driver_mbox()?; + + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} \ No newline at end of file diff --git a/bootloader/src/bsp/rpi3/kernel.ld b/bootloader/src/bsp/rpi3/kernel.ld new file mode 100644 index 000000000..bda4f090f --- /dev/null +++ b/bootloader/src/bsp/rpi3/kernel.ld @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2022 Andre Richter + */ + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + /* Set the link address to 32 MiB */ + . = 0x2000000; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + __binary_nonzero_start = .; + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */ + . = ALIGN(8); + __binary_nonzero_end_exclusive = .; + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} \ No newline at end of file diff --git a/bootloader/src/bsp/rpi3/memory.rs b/bootloader/src/bsp/rpi3/memory.rs new file mode 100644 index 000000000..c6a49432f --- /dev/null +++ b/bootloader/src/bsp/rpi3/memory.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! BSP Memory Management. + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The board's physical memory map. +#[rustfmt::skip] +#[allow(dead_code)] +pub(super) mod map { + pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x8_0000; + + pub const GPIO_OFFSET: usize = 0x0020_0000; + pub const PL011_UART_OFFSET: usize = 0x0020_1000; + pub const MINI_UART_OFFSET: usize = 0x0021_5000; + pub const MAILBOX_OFFSET: usize = 0x0000_B880; + + // There is a VideoCore/ARM MMU translating physical addresses to bus addresses. + // The MMU maps physical address 0x3f000000 to bus address 0x7e000000. + // In your code, you should use physical addresses instead of bus addresses. + // However, the reference uses bus addresses. You should translate them into physical one. + pub mod mmio { + use super::*; + + pub const START: usize = 0x3F00_0000; + pub const GPIO_START: usize = START + GPIO_OFFSET; + pub const MAILBOX_START: usize = START + MAILBOX_OFFSET; + pub const PL011_UART_START: usize = START + PL011_UART_OFFSET; + pub const MINI_UART_START :usize = START + MINI_UART_OFFSET; + + } + +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// The address on which the Raspberry firmware loads every binary by default. +#[inline(always)] +pub fn board_default_load_addr() -> *const u64 { + map::BOARD_DEFAULT_LOAD_ADDRESS as _ +} diff --git a/bootloader/src/console.rs b/bootloader/src/console.rs new file mode 100644 index 000000000..abcd247d1 --- /dev/null +++ b/bootloader/src/console.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! System console. + +mod null_console; + +use crate::synchronization::{self, NullLock}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Console interfaces. +#[allow(dead_code)] +pub mod interface { + use core::fmt; + + /// Console write functions. + pub trait Write { + /// Write a single character. + fn write_char(&self, c: char); + + /// Write a Rust format string. + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; + + /// Block until the last buffered character has been physically put on the TX wire. + fn flush(&self); + } + + /// Console read functions. + pub trait Read { + /// Read a single character. + fn read_char(&self) -> char { + ' ' + } + + /// Clear RX buffers, if any. + fn clear_rx(&self); + } + + /// Console statistics. + pub trait Statistics { + /// Return the number of characters written. + fn chars_written(&self) -> usize { + 0 + } + + /// Return the number of characters read. + fn chars_read(&self) -> usize { + 0 + } + } + + /// Trait alias for a full-fledged console. + pub trait All: Write + Read + Statistics {} +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static CUR_CONSOLE: NullLock<&'static (dyn interface::All + Sync)> = + NullLock::new(&null_console::NULL_CONSOLE); + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- +use synchronization::interface::Mutex; + +/// Register a new console. +pub fn register_console(new_console: &'static (dyn interface::All + Sync)) { + CUR_CONSOLE.lock(|con| *con = new_console); +} + +/// Return a reference to the currently registered console. +/// +/// This is the global console used by all printing macros. +pub fn console() -> &'static dyn interface::All { + CUR_CONSOLE.lock(|con| *con) +} \ No newline at end of file diff --git a/bootloader/src/console/null_console.rs b/bootloader/src/console/null_console.rs new file mode 100644 index 000000000..c6ff3b76f --- /dev/null +++ b/bootloader/src/console/null_console.rs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 Andre Richter + +//! Null console. + +use super::interface; +use core::fmt; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct NullConsole; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +pub static NULL_CONSOLE: NullConsole = NullConsole {}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl interface::Write for NullConsole { + fn write_char(&self, _c: char) {} + + fn write_fmt(&self, _args: fmt::Arguments) -> fmt::Result { + fmt::Result::Ok(()) + } + + fn flush(&self) {} +} + +impl interface::Read for NullConsole { + fn clear_rx(&self) {} +} + +impl interface::Statistics for NullConsole {} +impl interface::All for NullConsole {} \ No newline at end of file diff --git a/bootloader/src/cpu.rs b/bootloader/src/cpu.rs new file mode 100644 index 000000000..1234294f2 --- /dev/null +++ b/bootloader/src/cpu.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! Architectural processor code. +//! +//! # Orientation +//! +//! Since arch modules are imported into generic modules using the path attribute, the path of this +//! file is: +//! +//! crate::cpu::arch_cpu + +use aarch64_cpu::asm; +pub use asm::nop; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- +mod boot; + +//-------------------------------------------------------------------------------------------------- +// Architectural Public Reexports +//-------------------------------------------------------------------------------------------------- + +/// Spin for `n` cycles. +pub fn spin_for_cycles(n: usize) { + for _ in 0..n { + asm::nop(); + } +} + +/// Pause execution on the core. +#[inline(always)] +pub fn wait_forever() -> ! { + loop { + asm::wfe() + } +} \ No newline at end of file diff --git a/bootloader/src/cpu/boot.rs b/bootloader/src/cpu/boot.rs new file mode 100644 index 000000000..477b1e5c6 --- /dev/null +++ b/bootloader/src/cpu/boot.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2021-2023 Andre Richter + +//! Architectural boot code. +//! +//! # Orientation +//! +//! Since arch modules are imported into generic modules using the path attribute, the path of this +//! file is: +//! +//! crate::cpu::boot::arch_boot + +use core::arch::global_asm; + +// Assembly counterpart to this file. +global_asm!( + include_str!("boot.s"), + CONST_CORE_ID_MASK = const 0b11 +); + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// The Rust entry of the `kernel` binary. +/// +/// The function is called from the assembly `_start` function. +#[no_mangle] +pub unsafe fn _start_rust() -> ! { + crate::kernel_init() +} \ No newline at end of file diff --git a/bootloader/src/cpu/boot.s b/bootloader/src/cpu/boot.s new file mode 100644 index 000000000..e0c64be88 --- /dev/null +++ b/bootloader/src/cpu/boot.s @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2021-2022 Andre Richter + +//-------------------------------------------------------------------------------------------------- +// Definitions +//-------------------------------------------------------------------------------------------------- + +// Load the address of a symbol into a register, PC-relative. +// +// The symbol must lie within +/- 4 GiB of the Program Counter. +// +// # Resources +// +// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html +.macro ADR_REL register, symbol + adrp \register, \symbol + add \register, \register, #:lo12:\symbol +.endm + +// Load the address of a symbol into a register, absolute. +// +// # Resources +// +// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html +.macro ADR_ABS register, symbol + movz \register, #:abs_g2:\symbol + movk \register, #:abs_g1_nc:\symbol + movk \register, #:abs_g0_nc:\symbol +.endm + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- +.section .text._start + +//------------------------------------------------------------------------------ +// fn _start() +//------------------------------------------------------------------------------ +_start: + // Only proceed on the boot core. Park it otherwise. + mrs x0, MPIDR_EL1 + and x0, x0, {CONST_CORE_ID_MASK} + ldr x1, BOOT_CORE_ID // provided by bsp/__board_name__/cpu.rs + cmp x0, x1 + b.ne .L_parking_loop + + // If execution reaches here, it is the boot core. + + // Initialize DRAM. + ADR_ABS x0, __bss_start + ADR_ABS x1, __bss_end_exclusive + +.L_bss_init_loop: + cmp x0, x1 + b.eq .L_relocate_binary + stp xzr, xzr, [x0], #16 + b .L_bss_init_loop + + // Next, relocate the binary. +.L_relocate_binary: + ADR_REL x0, __binary_nonzero_start // The address the binary got loaded to. + ADR_ABS x1, __binary_nonzero_start // The address the binary was linked to. + ADR_ABS x2, __binary_nonzero_end_exclusive + +.L_copy_loop: + ldr x3, [x0], #8 + str x3, [x1], #8 + cmp x1, x2 + b.lo .L_copy_loop + + // Prepare the jump to Rust code. + // Set the stack pointer. + ADR_ABS x0, __boot_core_stack_end_exclusive + mov sp, x0 + + // Jump to the relocated Rust code. + ADR_ABS x1, _start_rust + br x1 + + // Infinitely wait for events (aka "park the core"). +.L_parking_loop: + wfe + b .L_parking_loop + +.size _start, . - _start +.type _start, function +.global _start \ No newline at end of file diff --git a/bootloader/src/driver.rs b/bootloader/src/driver.rs new file mode 100644 index 000000000..7ff8f2f3b --- /dev/null +++ b/bootloader/src/driver.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! Driver support. + +use crate::{ + println, + synchronization::{interface::Mutex, NullLock}, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +const NUM_DRIVERS: usize = 5; + +struct DriverManagerInner { + next_index: usize, + descriptors: [Option; NUM_DRIVERS], +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Driver interfaces. +pub mod interface { + /// Device Driver functions. + pub trait DeviceDriver { + /// Return a compatibility string for identifying the driver. + fn compatible(&self) -> &'static str; + + /// Called by the kernel to bring up the device. + /// + /// # Safety + /// + /// - During init, drivers might do stuff with system-wide impact. + unsafe fn init(&self) -> Result<(), &'static str> { + Ok(()) + } + } +} + +/// Tpye to be used as an optional callback after a driver's init() has run. +pub type DeviceDriverPostInitCallback = unsafe fn() -> Result<(), &'static str>; + +/// A descriptor for device drivers. +#[derive(Copy, Clone)] +pub struct DeviceDriverDescriptor { + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, +} + +/// Provides device driver management functions. +pub struct DriverManager { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static DRIVER_MANAGER: DriverManager = DriverManager::new(); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl DriverManagerInner { + /// Create an instance. + pub const fn new() -> Self { + Self { + next_index: 0, + descriptors: [None; NUM_DRIVERS], + } + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl DeviceDriverDescriptor { + /// Create an instance. + pub fn new( + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, + ) -> Self { + Self { + device_driver, + post_init_callback, + } + } +} + +/// Return a reference to the global DriverManager. +pub fn driver_manager() -> &'static DriverManager { + &DRIVER_MANAGER +} + +impl DriverManager { + /// Create an instance. + pub const fn new() -> Self { + Self { + inner: NullLock::new(DriverManagerInner::new()), + } + } + + /// Register a device driver with the kernel. + pub fn register_driver(&self, descriptor: DeviceDriverDescriptor) { + self.inner.lock(|inner| { + inner.descriptors[inner.next_index] = Some(descriptor); + inner.next_index += 1; + }) + } + + /// Helper for iterating over registered drivers. + fn for_each_descriptor<'a>(&'a self, f: impl FnMut(&'a DeviceDriverDescriptor)) { + self.inner.lock(|inner| { + inner + .descriptors + .iter() + .filter_map(|x| x.as_ref()) + .for_each(f) + }) + } + + /// Fully initialize all drivers. + /// + /// # Safety + /// + /// - During init, drivers might do stuff with system-wide impact. + pub unsafe fn init_drivers(&self) { + self.for_each_descriptor(|descriptor| { + // 1. Initialize driver. + if let Err(x) = descriptor.device_driver.init() { + panic!( + "Error initializing driver: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + + // 2. Call corresponding post init callback. + if let Some(callback) = &descriptor.post_init_callback { + if let Err(x) = callback() { + panic!( + "Error during driver post-init callback: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + } + }); + } + + /// Enumerate all registered device drivers. + #[allow(dead_code)] + pub fn enumerate(&self) { + let mut i: usize = 1; + self.for_each_descriptor(|descriptor| { + println!(" {}. {}", i, descriptor.device_driver.compatible()); + + i += 1; + }); + } +} \ No newline at end of file diff --git a/bootloader/src/kernel.ld b/bootloader/src/kernel.ld new file mode 100644 index 000000000..4b3e54208 --- /dev/null +++ b/bootloader/src/kernel.ld @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: MIT OR Apache-2.0 + * + * Copyright (c) 2018-2022 Andre Richter + */ + +__rpi_phys_dram_start_addr = 0; + +/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ +__rpi_phys_binary_load_addr = 0x80000; + + +ENTRY(__rpi_phys_binary_load_addr) + +/* Flags: + * 4 == R + * 5 == RX + * 6 == RW + * + * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. + * It doesn't mean all of them need actually be loaded. + */ +PHDRS +{ + segment_boot_core_stack PT_LOAD FLAGS(6); + segment_code PT_LOAD FLAGS(5); + segment_data PT_LOAD FLAGS(6); +} + +SECTIONS +{ + . = __rpi_phys_dram_start_addr; + + /*********************************************************************************************** + * Boot Core Stack + ***********************************************************************************************/ + .boot_core_stack (NOLOAD) : + { + /* ^ */ + /* | stack */ + . += __rpi_phys_binary_load_addr; /* | growth */ + /* | direction */ + __boot_core_stack_end_exclusive = .; /* | */ + } :segment_boot_core_stack + + /*********************************************************************************************** + * Code + RO Data + Global Offset Table + ***********************************************************************************************/ + .text : + { + KEEP(*(.text._start)) + *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ + *(.text._start_rust) /* The Rust entry point */ + *(.text*) /* Everything else */ + } :segment_code + + .rodata : ALIGN(8) { *(.rodata*) } :segment_code + + /*********************************************************************************************** + * Data + BSS + ***********************************************************************************************/ + .data : { *(.data*) } :segment_data + + /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*); + . = ALIGN(16); + __bss_end_exclusive = .; + } :segment_data + + /*********************************************************************************************** + * Misc + ***********************************************************************************************/ + .got : { *(.got*) } + ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") + + /DISCARD/ : { *(.comment*) } +} \ No newline at end of file diff --git a/bootloader/src/lib.rs b/bootloader/src/lib.rs new file mode 100644 index 000000000..2e7f0d4d7 --- /dev/null +++ b/bootloader/src/lib.rs @@ -0,0 +1 @@ +#![no_std] \ No newline at end of file diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs new file mode 100644 index 000000000..bcca39b40 --- /dev/null +++ b/bootloader/src/main.rs @@ -0,0 +1,127 @@ +#![feature(asm_const)] +#![feature(format_args_nl)] +#![feature(panic_info_message)] +#![feature(trait_alias)] +#![no_main] +#![no_std] + +mod arrsting; +mod bsp; +mod console; +mod cpu; +mod driver; +mod mbox; +mod panic_wait; +mod power; +mod print; +mod shell; +mod synchronization; + +/// Early init code. +/// +/// # Safety +/// +/// - Only a single core must be active and running this function. +/// - The init calls in this function must appear in the correct order. +unsafe fn kernel_init() -> ! { + // Initialize the BSP driver subsystem. + if let Err(x) = bsp::driver::init() { + panic!("Error initializing BSP driver subsystem: {}", x); + } + + // Initialize all device drivers. + driver::driver_manager().init_drivers(); + // println! is usable from here on. + + // Transition from unsafe to safe. + kernel_main() +} + +// const PI_LOADER_LOGO: &str = r#" +// ___ _ __ _ +// / _ (_) / / ___ __ _ __| | ___ _ __ +// / /_)/ |/ / / _ \ / _` |/ _` |/ _ \ '__| +// / ___/| / /__| (_) | (_| | (_| | __/ | +// \/ |_\____/\___/ \__,_|\__,_|\___|_| +// "#; + +/// The main function running after the early init. +fn kernel_main() -> ! { + use console::console; + + // println!( + // "[0] {} version {}", + // env!("CARGO_PKG_NAME"), + // env!("CARGO_PKG_VERSION") + // ); + // println!("[1] Booting on: {}", bsp::board_name()); + + // println!("[2] Drivers loaded:"); + // driver::driver_manager().enumerate(); + + // println!("[3] Chars written: {}", console().chars_written()); + // println!("[4] Echoing input now"); + + // println!("{}", PI_LOADER_LOGO); + // println!("BoardName: {}", bsp::board_name()); + // println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); + // println!( + // "RAM: {} {}", + // mbox::mbox().get_arm_memory().0, + // mbox::mbox().get_arm_memory().1 + // ); + // println!(); + // print!("3"); + // console().flush(); + + // Discard any spurious received characters before starting with the loader protocol. + // console().clear_rx(); + + // Read the binary's size. + let mut size: u32 = u32::from(console().read_char() as u8); + size |= u32::from(console().read_char() as u8) << 8; + size |= u32::from(console().read_char() as u8) << 16; + size |= u32::from(console().read_char() as u8) << 24; + + // Trust it's not too big. + console().write_char('O'); + console().write_char('K'); + + let kernel_addr: *mut u8 = bsp::memory::board_default_load_addr() as *mut u8; + + // println!("image size: {}", size); + + unsafe { + // Read the kernel byte by byte. + let mut checksum: u16 = 0; + for i in 0..size { + let c = console().read_char(); + checksum = (checksum %100 + c as u16 % 100) % 100; + + core::ptr::write_volatile(kernel_addr.offset(i as isize), c as u8); + // if i % 1024 == 0{ + // println!("pos:{} data:{} checksum:{}",i,c as u16, checksum); + // } + } + } + // unsafe { + // let kernel = core::slice::from_raw_parts_mut(0x80000 as *mut u8, size as usize); + // for i in 0..size { + // let c = console().read_char(); + // kernel[i as usize] = c as u8; + // println!("pos:{} data:{}",i,c as u32); + // } + // } + + println!("[ML] Loaded! Executing the payload now"); + console().flush(); + + // Use black magic to create a function pointer. + let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) }; + + // println!("[ML] Try to jump to bootloader"); + // Jump to loaded kernel! + kernel(); + + // shell::start_shell(); +} diff --git a/bootloader/src/mbox.rs b/bootloader/src/mbox.rs new file mode 100644 index 000000000..006947be2 --- /dev/null +++ b/bootloader/src/mbox.rs @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +mod null_mbox; + +use crate::synchronization::{self, NullLock}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Console interfaces. +#[allow(dead_code)] +pub mod interface { + + pub trait Boardinfo { + fn get_board_revision(&self) -> u32 { + 0 + } + + fn get_arm_memory(&self) -> (u32, u32) { + (0, 0) + } + } + + /// Trait alias for a full-fledged console. + pub trait All: Boardinfo {} +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static CUR_MBOX: NullLock<&'static (dyn interface::All + Sync)> = + NullLock::new(&null_mbox::NULL_MBOX); + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- +use synchronization::interface::Mutex; + +/// Register a new console. +pub fn register_mbox(new_mbox: &'static (dyn interface::All + Sync)) { + CUR_MBOX.lock(|con| *con = new_mbox); +} + +/// Return a reference to the currently registered console. +/// +/// This is the global console used by all printing macros. +pub fn mbox() -> &'static dyn interface::All { + CUR_MBOX.lock(|con| *con) +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/bootloader/src/mbox/null_mbox.rs b/bootloader/src/mbox/null_mbox.rs new file mode 100644 index 000000000..3d3677bdd --- /dev/null +++ b/bootloader/src/mbox/null_mbox.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2022-2023 Andre Richter + +//! Null console. + +use super::interface; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct NullMBOX; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +pub static NULL_MBOX: NullMBOX = NullMBOX {}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl interface::Boardinfo for NullMBOX { + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn get_arm_memory(&self) -> (u32, u32) { + (0, 0) + } + + fn get_board_revision(&self) -> u32 { + 0 + } +} + + +impl interface::All for NullMBOX {} \ No newline at end of file diff --git a/bootloader/src/panic_wait.rs b/bootloader/src/panic_wait.rs new file mode 100644 index 000000000..c10ffc9a7 --- /dev/null +++ b/bootloader/src/panic_wait.rs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! A panic handler that infinitely waits. + +use crate::{cpu, println}; +use core::panic::PanicInfo; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// Stop immediately if called a second time. +/// +/// # Note +/// +/// Using atomics here relieves us from needing to use `unsafe` for the static variable. +/// +/// On `AArch64`, which is the only implemented architecture at the time of writing this, +/// [`AtomicBool::load`] and [`AtomicBool::store`] are lowered to ordinary load and store +/// instructions. They are therefore safe to use even with MMU + caching deactivated. +/// +/// [`AtomicBool::load`]: core::sync::atomic::AtomicBool::load +/// [`AtomicBool::store`]: core::sync::atomic::AtomicBool::store +fn panic_prevent_reenter() { + use core::sync::atomic::{AtomicBool, Ordering}; + + // #[cfg(not(target_arch = "aarch64"))] + // compile_error!("Add the target_arch to above's check if the following code is safe to use"); + + static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false); + + if !PANIC_IN_PROGRESS.load(Ordering::Relaxed) { + PANIC_IN_PROGRESS.store(true, Ordering::Relaxed); + + return; + } + + cpu::wait_forever() +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + // Protect against panic infinite loops if any of the following code panics itself. + panic_prevent_reenter(); + + let (location, line, column) = match info.location() { + Some(loc) => (loc.file(), loc.line(), loc.column()), + _ => ("???", 0, 0), + }; + + println!( + "Kernel panic!\n\n\ + Panic location:\n File '{}', line {}, column {}\n\n\ + {}", + location, + line, + column, + info.message().unwrap_or(&format_args!("")), + ); + + cpu::wait_forever() +} \ No newline at end of file diff --git a/bootloader/src/power.rs b/bootloader/src/power.rs new file mode 100644 index 000000000..695728eab --- /dev/null +++ b/bootloader/src/power.rs @@ -0,0 +1,28 @@ +use core::ptr::write_volatile; + +pub fn reboot() { + reset(100); +} + +const PM_PASSWORD: u32 = 0x5a000000; +const PM_RSTC: u32 = 0x3F10_001C; +const PM_WDOG: u32 = 0x3F10_0024; + +#[allow(dead_code)] +pub fn reset(tick: u32) { + unsafe { + let mut r = PM_PASSWORD | 0x20; + write_volatile(PM_RSTC as *mut u32, r); + r = PM_PASSWORD | tick; + write_volatile(PM_WDOG as *mut u32, r); + } +} + +#[allow(dead_code)] +pub fn cancel_reset() { + unsafe { + let r = PM_PASSWORD | 0; + write_volatile(PM_RSTC as *mut u32, r); + write_volatile(PM_WDOG as *mut u32, r); + } +} \ No newline at end of file diff --git a/bootloader/src/print.rs b/bootloader/src/print.rs new file mode 100644 index 000000000..66e45baed --- /dev/null +++ b/bootloader/src/print.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2018-2023 Andre Richter + +//! Printing. +use crate::console; +use core::fmt; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + console::console().write_fmt(args).unwrap(); +} + +/// Prints without a newline. +/// +/// Carbon copy from +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::print::_print(format_args!($($arg)*))); +} + +/// Prints with a newline. +/// +/// Carbon copy from +#[macro_export] +macro_rules! println { + () => ($crate::print!("\r\n")); + ($($arg:tt)*) => ({ + $crate::print::_print(format_args!($($arg)*)); + ($crate::print!("\r\n")); + }) +} \ No newline at end of file diff --git a/bootloader/src/shell.rs b/bootloader/src/shell.rs new file mode 100644 index 000000000..956df19ed --- /dev/null +++ b/bootloader/src/shell.rs @@ -0,0 +1,61 @@ +use crate::{arrsting::ArrString, console::console, mbox, power, print, println}; + +#[allow(dead_code)] +pub fn start_shell() -> !{ + let msg_buf_exceed = "[system] Buf size limit exceed, reset buf"; + + let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device\r\ninfo\t: show the device info"; + let msg_hello_world = "HelloWorld!"; + let msg_not_found = "Command not found"; + let msg_reboot = "Rebooting..."; + + let arr_help = ArrString::new("help"); + let arr_hello = ArrString::new("hello"); + let arr_reboot = ArrString::new("reboot"); + let arr_info = ArrString::new("info"); + + let mut buf = ArrString::new(""); + + println!("TEST VER 0.0.2\r\n"); + print!("{}\r\n#", msg_help); + + loop { + let c = console().read_char(); + console().write_char(c); + + if c == '\n' { + print!("\r"); + if buf == arr_help { + println!("{}", msg_help); + } else if buf == arr_hello { + println!("{}", msg_hello_world); + } else if buf == arr_reboot { + println!("{}", msg_reboot); + power::reboot(); + } else if buf == arr_info { + println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); + // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); + println!( + "RAM: {} {}", + mbox::mbox().get_arm_memory().0, + mbox::mbox().get_arm_memory().1 + ); + } else { + println!("{}", msg_not_found); + } + + buf.clean_buf(); + print!("#"); + continue; + + // arrsting::arrstrcmp(buf, help); + } else if buf.get_len() == 1024 { + buf.clean_buf(); + println!("{}", msg_buf_exceed); + print!("#"); + continue; + } else { + buf.push_char(c); + } + } +} diff --git a/bootloader/src/synchronization.rs b/bootloader/src/synchronization.rs new file mode 100644 index 000000000..b87cd46a0 --- /dev/null +++ b/bootloader/src/synchronization.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2020-2023 Andre Richter + +//! Synchronization primitives. +//! +//! # Resources +//! +//! - +//! - +//! - + +use core::cell::UnsafeCell; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Synchronization interfaces. +pub mod interface { + + /// Any object implementing this trait guarantees exclusive access to the data wrapped within + /// the Mutex for the duration of the provided closure. + pub trait Mutex { + /// The type of the data that is wrapped by this mutex. + type Data; + + /// Locks the mutex and grants the closure temporary mutable access to the wrapped data. + fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R; + } +} + +/// A pseudo-lock for teaching purposes. +/// +/// In contrast to a real Mutex implementation, does not protect against concurrent access from +/// other cores to the contained data. This part is preserved for later lessons. +/// +/// The lock will only be used as long as it is safe to do so, i.e. as long as the kernel is +/// executing single-threaded, aka only running on a single core with interrupts disabled. +pub struct NullLock +where + T: ?Sized, +{ + data: UnsafeCell, +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +unsafe impl Send for NullLock where T: ?Sized + Send {} +unsafe impl Sync for NullLock where T: ?Sized + Send {} + +impl NullLock { + /// Create an instance. + pub const fn new(data: T) -> Self { + Self { + data: UnsafeCell::new(data), + } + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ + +impl interface::Mutex for NullLock { + type Data = T; + + fn lock<'a, R>(&'a self, f: impl FnOnce(&'a mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + let data = unsafe { &mut *self.data.get() }; + + f(data) + } +} \ No newline at end of file diff --git a/imageUploader/Makefile b/imageUploader/Makefile new file mode 100644 index 000000000..6cac35fdd --- /dev/null +++ b/imageUploader/Makefile @@ -0,0 +1,5 @@ + +all: ../kernel/kernel8.img + poetry run python ./imageuploader/main.py ../kernel/kernel8.img /dev/tty.usbserial-0001 +#poetry run python $(MAIN) ../kernel/kernel8.img /dev/ttys016 + diff --git a/imageUploader/README.md b/imageUploader/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/imageUploader/imageuploader/__init__.py b/imageUploader/imageuploader/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/imageUploader/imageuploader/main.py b/imageUploader/imageuploader/main.py new file mode 100644 index 000000000..8f2ef3535 --- /dev/null +++ b/imageUploader/imageuploader/main.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import sys +import serial +import time +from pathlib import Path + +def send_file(filename, serial_port): + ser = None + try: + # Open the serial port + ser = serial.Serial(serial_port, baudrate=115200, timeout=1) + print(f"Opened serial port {serial_port}") + + # Read the file content + with open(filename, 'rb') as file: + file_content = file.read() + print(f"Opened file {filename}") + + # Send the size of the file as 32-bit little-endian + file_size = len(file_content) + ser.write(file_size.to_bytes(4, 'little')) + + text = ser.readline() + print(text) + + # Send the file content + byte_counter = 0 + checksum = 0 + for chunk in chunks(file_content, file_size, 1): + + ser.write(chunk) + + checksum = (checksum % 100+ int.from_bytes(chunk, "little") % 100 ) % 100 + # time.sleep(0.01) + # print(f"pos:{byte_counter} data:{int.from_bytes(chunk, "little")} checksum:{checksum}"); + + byte_counter += len(chunk) + + if byte_counter % 256 == 0: + time.sleep(0.02) # Add a delay every 256 bytes + print(f"uploading: {byte_counter}/{len(file_content)}") + # print(f"context: {list(chunk)}") + # print(f"context: {int.from_bytes(chunk, "little") }") + print(f"checksum: {checksum}") + + # overofbound + # checksum = 0 + # for i in range(file_size): + # ser.write(file_content[i]) + # byte_counter += 1; + # checksum = (checksum + file_content[i] ) % 1024 + + # if byte_counter % 1024 == 0: + # print(f"context: {file_content[i]}") + # print(f"uploading: {byte_counter}/{file_size}"); + # print(f"checksum: {checksum}") + # time.sleep(0.05) # Add a delay every 1024 bytes + + + print(f"uploading: {byte_counter}/{len(file_content)}"); + print(f"checksum: {checksum}") + print("File sent successfully.") + + text = ser.readline() + print(text) + + except Exception as e: + print(f"Error: {e}") + finally: + if ser is not None and ser.is_open: + ser.close() + print("Serial port closed.") + +def chunks(lst, size ,n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + # print(f"chunk start form {i} to {i+n}") + if i+n > size: + # print("END________") + yield lst[i:size] + else: + yield lst[i:i + n] + +def main(): + if len(sys.argv) != 3: + print("Usage: python sender.py ") + sys.exit(1) + + filename = sys.argv[1] + if not Path(filename).is_file(): + print(f"Error: File '{filename}' not found.") + sys.exit(1) + + serial_port = sys.argv[2] + + send_file(filename, serial_port) + +if __name__ == "__main__": + main() diff --git a/imageUploader/poetry.lock b/imageUploader/poetry.lock new file mode 100644 index 000000000..cc4075ff4 --- /dev/null +++ b/imageUploader/poetry.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "progress" +version = "1.6" +description = "Easy to use progress bars" +optional = false +python-versions = "*" +files = [ + {file = "progress-1.6.tar.gz", hash = "sha256:c9c86e98b5c03fa1fe11e3b67c1feda4788b8d0fe7336c2ff7d5644ccfba34cd"}, +] + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "292b01046672f02e0ba676e10e29fb6c817b754d9a8090722f94420b0bbc8003" diff --git a/imageUploader/pyproject.toml b/imageUploader/pyproject.toml new file mode 100644 index 000000000..d77cc6ffd --- /dev/null +++ b/imageUploader/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "imageuploader" +version = "0.1.0" +description = "" +authors = ["Tsung-Che Lu "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.12" +pyserial = "^3.5" +progress = "^1.6" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/imageUploader/tests/__init__.py b/imageUploader/tests/__init__.py new file mode 100644 index 000000000..e69de29bb From 974d3d9fc0c389f05b66eab3e0919186b7b3f34f Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Wed, 17 Apr 2024 17:24:36 +0800 Subject: [PATCH 07/11] gen cpio --- .gitignore | 4 +++- rootfs/1.txt | 1 + rootfs/2.txt | 1 + rootfs/3.txt | 2 ++ rootfs/Makefile | 3 +++ 5 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 rootfs/1.txt create mode 100644 rootfs/2.txt create mode 100644 rootfs/3.txt create mode 100644 rootfs/Makefile diff --git a/.gitignore b/.gitignore index b2a1d4c41..427586be8 100644 --- a/.gitignore +++ b/.gitignore @@ -90,4 +90,6 @@ hw0/ *.elf *.img target/ -kernel*.img \ No newline at end of file +kernel*.img + +initramfs.cpio \ No newline at end of file diff --git a/rootfs/1.txt b/rootfs/1.txt new file mode 100644 index 000000000..d96dc9570 --- /dev/null +++ b/rootfs/1.txt @@ -0,0 +1 @@ +abcdef \ No newline at end of file diff --git a/rootfs/2.txt b/rootfs/2.txt new file mode 100644 index 000000000..712eee5f1 --- /dev/null +++ b/rootfs/2.txt @@ -0,0 +1 @@ +ghijk \ No newline at end of file diff --git a/rootfs/3.txt b/rootfs/3.txt new file mode 100644 index 000000000..4535be624 --- /dev/null +++ b/rootfs/3.txt @@ -0,0 +1,2 @@ +lmnop +qrs \ No newline at end of file diff --git a/rootfs/Makefile b/rootfs/Makefile new file mode 100644 index 000000000..cfd83dd3b --- /dev/null +++ b/rootfs/Makefile @@ -0,0 +1,3 @@ + +all: ./1.txt ./2.txt./ 3.txt + find ./*.txt | cpio -o -H newc >! ../initramfs.cpio \ No newline at end of file From 381efc5f631a0cd0fb428e570f6a35040ac98967 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Wed, 17 Apr 2024 17:25:05 +0800 Subject: [PATCH 08/11] remove \r translation --- kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs index 0eb99c3f5..40e8d83a3 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs @@ -285,12 +285,12 @@ impl MiniUartInner { } // Read one character. - let mut ret = self.registers.AUX_MU_IO_REG.get() as u8 as char; + let ret = self.registers.AUX_MU_IO_REG.get() as u8 as char; // Convert carrige return to newline. - if ret == '\r' { - ret = '\n' - } + // if ret == '\r' { + // ret = '\n' + // } // Update statistics. self.chars_read += 1; From c52a28aaacbef9d1ccfaba2e4e3dc6f0f3bcbdd9 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Fri, 19 Apr 2024 14:46:05 +0800 Subject: [PATCH 09/11] bump allocator --- .../device_driver/bcm/bcm2xxx_mini_uart.rs | 8 +- kernel/src/bsp/rpi3/memory.rs | 16 +++- kernel/src/main.rs | 5 + kernel/src/memory.rs | 4 + kernel/src/memory/bump_allocator.rs | 96 +++++++++++++++++++ kernel/src/shell.rs | 22 +++++ 6 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 kernel/src/memory.rs create mode 100644 kernel/src/memory/bump_allocator.rs diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs index 40e8d83a3..daa9aa953 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs @@ -285,12 +285,12 @@ impl MiniUartInner { } // Read one character. - let ret = self.registers.AUX_MU_IO_REG.get() as u8 as char; + let mut ret = self.registers.AUX_MU_IO_REG.get() as u8 as char; // Convert carrige return to newline. - // if ret == '\r' { - // ret = '\n' - // } + if ret == '\r' { + ret = '\n'; + } // Update statistics. self.chars_read += 1; diff --git a/kernel/src/bsp/rpi3/memory.rs b/kernel/src/bsp/rpi3/memory.rs index 041976de3..e042df3a9 100644 --- a/kernel/src/bsp/rpi3/memory.rs +++ b/kernel/src/bsp/rpi3/memory.rs @@ -10,7 +10,7 @@ /// The board's physical memory map. #[rustfmt::skip] -pub(super) mod map { +pub(crate) mod map { pub const GPIO_OFFSET: usize = 0x0020_0000; // pub const PL011_UART_OFFSET: usize = 0x0020_1000; @@ -28,8 +28,20 @@ pub(super) mod map { pub const GPIO_START: usize = START + GPIO_OFFSET; pub const MAILBOX_START: usize = START + MAILBOX_OFFSET; // pub const PL011_UART_START: usize = START + UART_OFFSET; - pub const MINI_UART_START :usize = START + MINI_UART_OFFSET; + pub const MINI_UART_START: usize = START + MINI_UART_OFFSET; } + /* + After rpi3 is booted, some physical memory is already in use. For example, + there are already spin tables for multicore boot(0x0000 - 0x1000), f + latten device tree, initramfs, and your kernel image in the physical memory. + Your memory allocator should not allocate these memory blocks if you still need to use them. + */ + pub mod sdram { + + pub const RAM_START: usize = 0x1000_0000; + pub const RAM_END: usize = 0x8000_0000; + } + } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index d6e395bbe..2dd3fc353 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -16,7 +16,9 @@ mod mbox; mod power; mod shell; mod arrsting; +mod memory; +use crate::bsp::memory::map::sdram; /// Early init code. /// @@ -34,6 +36,9 @@ unsafe fn kernel_init() -> ! { driver::driver_manager().init_drivers(); // println! is usable from here on. + // Initialize real memory + memory::ALLOCATOR.init(sdram::RAM_START,sdram::RAM_END); + // Transition from unsafe to safe. kernel_main() } diff --git a/kernel/src/memory.rs b/kernel/src/memory.rs new file mode 100644 index 000000000..4028bbe8a --- /dev/null +++ b/kernel/src/memory.rs @@ -0,0 +1,4 @@ +pub mod bump_allocator; + +#[global_allocator] +pub static ALLOCATOR: bump_allocator::BumpAllocator = bump_allocator::BumpAllocator::new(); \ No newline at end of file diff --git a/kernel/src/memory/bump_allocator.rs b/kernel/src/memory/bump_allocator.rs new file mode 100644 index 000000000..d1be4d601 --- /dev/null +++ b/kernel/src/memory/bump_allocator.rs @@ -0,0 +1,96 @@ +/* +inspired from https://os.phil-opp.com +*/ +use crate::synchronization::{interface::Mutex, NullLock}; +// use crate::{print, println}; + +use core::alloc::{GlobalAlloc, Layout}; +use crate::println; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct BumpAllocator { + inner: NullLock, +} + +struct BumpAllocatorInner { + heap_start: usize, + heap_end: usize, + next: usize, + allocations: usize, +} + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl BumpAllocatorInner { + // Creates a new empty bump allocator. + pub const fn new() -> Self { + BumpAllocatorInner { + heap_start: 0, + heap_end: 0, + next: 0, + allocations: 0, + } + } + /// Initializes the bump allocator with the given heap bounds. + /// + /// This method is unsafe because the caller must ensure that the given + /// memory range is unused. Also, this method must be called only once. + pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { + self.heap_start = heap_start; + self.heap_end = heap_start + heap_size; + self.next = heap_start; + } + + fn alloc(&mut self, layout: Layout) -> *mut u8 { + println!("START alloc"); + let alloc_start = self.next; + self.next = alloc_start + layout.size(); + self.allocations += 1; + alloc_start as *mut u8 + } + + #[allow(dead_code)] + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + todo!(); + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl BumpAllocator { + #[allow(dead_code)] + pub const COMPATIBLE: &'static str = "MEMORY_ALLOCATOR"; + /// Create an instance. + /// + /// # Safety + pub const fn new() -> Self { + Self { + inner: NullLock::new(BumpAllocatorInner::new()), + } + } + + pub unsafe fn init(&self, heap_start: usize, heap_size: usize) { + self.inner.lock(|inner| inner.init(heap_start, heap_size)); + } +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ + +unsafe impl GlobalAlloc for BumpAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.inner.lock(|inner| inner.alloc(layout)) + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + // todo!() + } +} diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs index 1cb1da871..2cab0021b 100644 --- a/kernel/src/shell.rs +++ b/kernel/src/shell.rs @@ -1,3 +1,8 @@ +extern crate alloc; + +use alloc::boxed::Box; +use core::ptr; + use crate::{arrsting::ArrString, console::console, mbox, power, print, println}; @@ -13,6 +18,7 @@ pub fn start_shell() -> !{ let arr_hello = ArrString::new("hello"); let arr_reboot = ArrString::new("reboot"); let arr_info = ArrString::new("info"); + let msg_alloc_test=ArrString::new("alloc"); let mut buf = ArrString::new(""); @@ -40,6 +46,10 @@ pub fn start_shell() -> !{ mbox::mbox().get_arm_memory().0, mbox::mbox().get_arm_memory().1 ); + } else if buf == msg_alloc_test { + println!("Allocate test start!"); + check_alloc(); + println!("Allocate test End!"); } else { println!("{}", msg_not_found); } @@ -59,3 +69,15 @@ pub fn start_shell() -> !{ } } } + +// This is a test for checking memory allocation +fn check_alloc() { + let long_lived = Box::new(1); // new + for i in 0..32 { + let x = Box::new(i); + println!("{}", i); + println!{"{:p}" ,ptr::addr_of!(*x)}; + assert_eq!(*x, i); + } + assert_eq!(*long_lived, 1); // new +} \ No newline at end of file From b8a804b73f794d8dd3bdb4382c64c2183dc3b771 Mon Sep 17 00:00:00 2001 From: Tsung-Che Lu Date: Thu, 9 May 2024 18:55:36 +0800 Subject: [PATCH 10/11] HW3 --- bootloader/Makefile | 12 +- bootloader/src/cpu/boot.s | 4 + bootloader/src/main.rs | 20 +- bootloader/src/panic_wait.rs | 2 + bootloader/test.log | 5 + imageUploader/Makefile | 3 +- imageUploader/imageuploader/main.py | 3 - kernel/Makefile | 5 +- kernel/kernel.ld | 83 ------- kernel/src/arrsting.rs | 2 +- kernel/src/bsp/device_driver.rs | 2 +- kernel/src/bsp/device_driver/bcm.rs | 5 +- .../src/bsp/device_driver/bcm/bcm2xxx_gpio.rs | 2 +- .../src/bsp/device_driver/bcm/bcm2xxx_mbox.rs | 2 +- .../device_driver/bcm/bcm2xxx_mini_uart.rs | 4 +- kernel/src/bsp/device_driver/common.rs | 2 +- kernel/src/{bsp.rs => bsp/mod.rs} | 2 +- kernel/src/bsp/rpi3/cpu.rs | 2 +- kernel/src/bsp/rpi3/driver.rs | 15 +- kernel/src/bsp/rpi3/kernel.ld | 3 + kernel/src/{console.rs => console/mod.rs} | 2 +- kernel/src/console/null_console.rs | 2 +- kernel/src/cpu.rs | 2 +- kernel/src/cpu/boot.rs | 2 +- kernel/src/cpu/boot.s | 20 ++ kernel/src/device_tree/dt.rs | 165 ++++++++++++++ kernel/src/device_tree/dt_header.rs | 102 +++++++++ kernel/src/device_tree/dt_prop.rs | 81 +++++++ kernel/src/device_tree/dt_string.rs | 46 ++++ kernel/src/device_tree/dt_struct.rs | 15 ++ kernel/src/device_tree/mod.rs | 76 +++++++ kernel/src/driver.rs | 208 +++++++++--------- kernel/src/fs/init_ram_fs.rs | 132 +++++++++++ kernel/src/fs/mod.rs | 1 + kernel/src/lib.rs | 2 +- kernel/src/main.rs | 31 ++- kernel/src/{mbox.rs => mbox/mod.rs} | 2 +- kernel/src/mbox/null_mbox.rs | 21 +- kernel/src/memory/bump_allocator.rs | 18 +- kernel/src/{memory.rs => memory/mod.rs} | 2 +- kernel/src/panic_wait.rs | 2 +- kernel/src/power.rs | 27 ++- kernel/src/print.rs | 2 +- kernel/src/shell.rs | 127 ++++++----- kernel/src/synchronization.rs | 2 +- kernel/test.log | Bin 0 -> 615807 bytes resources/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 34587 bytes 47 files changed, 948 insertions(+), 320 deletions(-) create mode 100644 bootloader/test.log delete mode 100644 kernel/kernel.ld rename kernel/src/{bsp.rs => bsp/mod.rs} (64%) rename kernel/src/{console.rs => console/mod.rs} (99%) create mode 100644 kernel/src/device_tree/dt.rs create mode 100644 kernel/src/device_tree/dt_header.rs create mode 100644 kernel/src/device_tree/dt_prop.rs create mode 100644 kernel/src/device_tree/dt_string.rs create mode 100644 kernel/src/device_tree/dt_struct.rs create mode 100644 kernel/src/device_tree/mod.rs create mode 100644 kernel/src/fs/init_ram_fs.rs create mode 100644 kernel/src/fs/mod.rs rename kernel/src/{mbox.rs => mbox/mod.rs} (99%) rename kernel/src/{memory.rs => memory/mod.rs} (79%) create mode 100644 kernel/test.log create mode 100644 resources/bcm2710-rpi-3-b-plus.dtb diff --git a/bootloader/Makefile b/bootloader/Makefile index 9ada1a074..55c802c99 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -109,8 +109,16 @@ $(KERNEL_BIN): $(KERNEL_ELF) debug: # qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial null -serial stdio # qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial stdio - qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) -display none -serial null -serial pty - + qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) \ + -display none -serial null -serial pty \ + -dtb ../resources/bcm2710-rpi-3-b-plus.dtb \ + -initrd ../resources/initramfs.cpio + +# debug: +# qemu-system-aarch64 -M raspi3b -kernel $(KERNEL_BIN) \ +# -display none -serial null -serial pty \ +# -dtb ../resources/bcm2710-rpi-3-b-plus.dtb \ +# -initrd ../resources/initramfs.cpio ##------------------------------------------------------------------------------ ## Clean diff --git a/bootloader/src/cpu/boot.s b/bootloader/src/cpu/boot.s index e0c64be88..a6da7fc31 100644 --- a/bootloader/src/cpu/boot.s +++ b/bootloader/src/cpu/boot.s @@ -38,6 +38,10 @@ // fn _start() //------------------------------------------------------------------------------ _start: + // x0 will save dtb address, so backup to 0x60000 + ldr x15, =0x50000 + str x0, [x15] + // Only proceed on the boot core. Park it otherwise. mrs x0, MPIDR_EL1 and x0, x0, {CONST_CORE_ID_MASK} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index bcca39b40..6e20bc5ba 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -77,6 +77,18 @@ fn kernel_main() -> ! { // Discard any spurious received characters before starting with the loader protocol. // console().clear_rx(); + // loop { + // println!("OK!"); + // } + + // let dtb_addr = 0x50000 as *const u8; + // let dtb_addr = unsafe { core::ptr::read_volatile(dtb_addr as *const u32) }; + + // loop { + // println!("OK!"); + // println!("DTB address: {:#x}", dtb_addr); + // } + // Read the binary's size. let mut size: u32 = u32::from(console().read_char() as u8); size |= u32::from(console().read_char() as u8) << 8; @@ -84,8 +96,8 @@ fn kernel_main() -> ! { size |= u32::from(console().read_char() as u8) << 24; // Trust it's not too big. - console().write_char('O'); - console().write_char('K'); + // console().write_char('O'); + // console().write_char('K'); let kernel_addr: *mut u8 = bsp::memory::board_default_load_addr() as *mut u8; @@ -96,8 +108,8 @@ fn kernel_main() -> ! { let mut checksum: u16 = 0; for i in 0..size { let c = console().read_char(); - checksum = (checksum %100 + c as u16 % 100) % 100; - + checksum = (checksum % 100 + c as u16 % 100) % 100; + core::ptr::write_volatile(kernel_addr.offset(i as isize), c as u8); // if i % 1024 == 0{ // println!("pos:{} data:{} checksum:{}",i,c as u16, checksum); diff --git a/bootloader/src/panic_wait.rs b/bootloader/src/panic_wait.rs index c10ffc9a7..8d4a6b257 100644 --- a/bootloader/src/panic_wait.rs +++ b/bootloader/src/panic_wait.rs @@ -50,6 +50,7 @@ fn panic(info: &PanicInfo) -> ! { _ => ("???", 0, 0), }; + loop{ println!( "Kernel panic!\n\n\ Panic location:\n File '{}', line {}, column {}\n\n\ @@ -59,6 +60,7 @@ fn panic(info: &PanicInfo) -> ! { column, info.message().unwrap_or(&format_args!("")), ); +} cpu::wait_forever() } \ No newline at end of file diff --git a/bootloader/test.log b/bootloader/test.log new file mode 100644 index 000000000..c897b2f16 --- /dev/null +++ b/bootloader/test.log @@ -0,0 +1,5 @@ +qemu-system-aarch64 -M raspi3b -kernel bootloader.img \ + -display none -serial null -serial pty \ + -dtb ../resources/bcm2710-rpi-3-b-plus.dtb \ + -initrd ../resources/initramfs.cpio +char device redirected to /dev/ttys019 (label serial1) diff --git a/imageUploader/Makefile b/imageUploader/Makefile index 6cac35fdd..d5b46df63 100644 --- a/imageUploader/Makefile +++ b/imageUploader/Makefile @@ -1,5 +1,6 @@ all: ../kernel/kernel8.img - poetry run python ./imageuploader/main.py ../kernel/kernel8.img /dev/tty.usbserial-0001 + poetry run python ./imageuploader/main.py ../kernel/kernel8.img /dev/tty.usbserial-0001 \ + && screen /dev/tty.usbserial-0001 115200 #poetry run python $(MAIN) ../kernel/kernel8.img /dev/ttys016 diff --git a/imageUploader/imageuploader/main.py b/imageUploader/imageuploader/main.py index 8f2ef3535..ed93d7eeb 100644 --- a/imageUploader/imageuploader/main.py +++ b/imageUploader/imageuploader/main.py @@ -21,9 +21,6 @@ def send_file(filename, serial_port): file_size = len(file_content) ser.write(file_size.to_bytes(4, 'little')) - text = ser.readline() - print(text) - # Send the file content byte_counter = 0 checksum = 0 diff --git a/kernel/Makefile b/kernel/Makefile index bf39161c2..425acbb0e 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -107,7 +107,10 @@ $(KERNEL_BIN): $(KERNEL_ELF) ## Debug ##------------------------------------------------------------------------------ debug: - qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -serial null -serial stdio + qemu-system-aarch64 -M raspi3b -kernel kernel8.img \ + -display none -serial null -serial stdio \ + -dtb ../resources/bcm2710-rpi-3-b-plus.dtb \ + -initrd ../resources/initramfs.cpio ##------------------------------------------------------------------------------ ## Clean diff --git a/kernel/kernel.ld b/kernel/kernel.ld deleted file mode 100644 index bda4f090f..000000000 --- a/kernel/kernel.ld +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: MIT OR Apache-2.0 - * - * Copyright (c) 2018-2022 Andre Richter - */ - -/* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ -__rpi_phys_binary_load_addr = 0x80000; - - -ENTRY(__rpi_phys_binary_load_addr) - -/* Flags: - * 4 == R - * 5 == RX - * 6 == RW - * - * Segments are marked PT_LOAD below so that the ELF file provides virtual and physical addresses. - * It doesn't mean all of them need actually be loaded. - */ -PHDRS -{ - segment_boot_core_stack PT_LOAD FLAGS(6); - segment_code PT_LOAD FLAGS(5); - segment_data PT_LOAD FLAGS(6); -} - -SECTIONS -{ - /* Set the link address to 32 MiB */ - . = 0x2000000; - - /*********************************************************************************************** - * Boot Core Stack - ***********************************************************************************************/ - .boot_core_stack (NOLOAD) : - { - /* ^ */ - /* | stack */ - . += __rpi_phys_binary_load_addr; /* | growth */ - /* | direction */ - __boot_core_stack_end_exclusive = .; /* | */ - } :segment_boot_core_stack - - /*********************************************************************************************** - * Code + RO Data + Global Offset Table - ***********************************************************************************************/ - __binary_nonzero_start = .; - .text : - { - KEEP(*(.text._start)) - *(.text._start_arguments) /* Constants (or statics in Rust speak) read by _start(). */ - *(.text._start_rust) /* The Rust entry point */ - *(.text*) /* Everything else */ - } :segment_code - - .rodata : ALIGN(8) { *(.rodata*) } :segment_code - - /*********************************************************************************************** - * Data + BSS - ***********************************************************************************************/ - .data : { *(.data*) } :segment_data - - /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */ - . = ALIGN(8); - __binary_nonzero_end_exclusive = .; - - /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ - .bss (NOLOAD) : ALIGN(16) - { - __bss_start = .; - *(.bss*); - . = ALIGN(16); - __bss_end_exclusive = .; - } :segment_data - - /*********************************************************************************************** - * Misc - ***********************************************************************************************/ - .got : { *(.got*) } - ASSERT(SIZEOF(.got) == 0, "Relocation support not expected") - - /DISCARD/ : { *(.comment*) } -} \ No newline at end of file diff --git a/kernel/src/arrsting.rs b/kernel/src/arrsting.rs index 93d123059..07cd8e650 100644 --- a/kernel/src/arrsting.rs +++ b/kernel/src/arrsting.rs @@ -22,7 +22,7 @@ impl ArrString { self.len } - pub fn get_data(&self) -> [u8 ;MAXIUM_LEN] { + pub fn get_data(&self) -> [u8; MAXIUM_LEN] { self.data } diff --git a/kernel/src/bsp/device_driver.rs b/kernel/src/bsp/device_driver.rs index 17243ac8b..964f7af7c 100644 --- a/kernel/src/bsp/device_driver.rs +++ b/kernel/src/bsp/device_driver.rs @@ -1,4 +1,4 @@ mod bcm; mod common; -pub use bcm::*; \ No newline at end of file +pub use bcm::*; diff --git a/kernel/src/bsp/device_driver/bcm.rs b/kernel/src/bsp/device_driver/bcm.rs index 38d54f84b..f68a61a0d 100644 --- a/kernel/src/bsp/device_driver/bcm.rs +++ b/kernel/src/bsp/device_driver/bcm.rs @@ -1,10 +1,9 @@ mod bcm2xxx_gpio; // mod bcm2xxx_pl011_uart; -mod bcm2xxx_mini_uart; mod bcm2xxx_mbox; - +mod bcm2xxx_mini_uart; pub use bcm2xxx_gpio::*; // pub use bcm2xxx_pl011_uart::*; +pub use bcm2xxx_mbox::*; pub use bcm2xxx_mini_uart::*; -pub use bcm2xxx_mbox::*; \ No newline at end of file diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs index cb71eabf6..9b7ca7997 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs @@ -233,4 +233,4 @@ impl driver::interface::DeviceDriver for GPIO { fn compatible(&self) -> &'static str { Self::COMPATIBLE } -} \ No newline at end of file +} diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs index a2051dde4..7c160185c 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mbox.rs @@ -249,4 +249,4 @@ impl mbox::interface::Boardinfo for MBOX { } } -impl mbox::interface::All for MBOX {} \ No newline at end of file +impl mbox::interface::All for MBOX {} diff --git a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs index daa9aa953..43a5b6187 100644 --- a/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs +++ b/kernel/src/bsp/device_driver/bcm/bcm2xxx_mini_uart.rs @@ -218,7 +218,9 @@ impl MiniUartInner { .AUX_MU_BAUD_REG .write(AUX_MU_BAUD_REG::BUAD_RATE::Default); // self.registers.AUX_MU_IIR_REG.set(0xc6); //todo NOT_POSSIBLE - self.registers.AUX_MU_IIR_REG.write(AUX_MU_IIR_REG::INTERRUPT_ID::NOT_POSSIBLE); //todo NOT_POSSIBLE + self.registers + .AUX_MU_IIR_REG + .write(AUX_MU_IIR_REG::INTERRUPT_ID::NOT_POSSIBLE); //todo NOT_POSSIBLE self.registers.AUX_MU_CNTL_REG.set(3); } diff --git a/kernel/src/bsp/device_driver/common.rs b/kernel/src/bsp/device_driver/common.rs index cd900024b..dfe7d8ef3 100644 --- a/kernel/src/bsp/device_driver/common.rs +++ b/kernel/src/bsp/device_driver/common.rs @@ -35,4 +35,4 @@ impl ops::Deref for MMIODerefWrapper { fn deref(&self) -> &Self::Target { unsafe { &*(self.start_addr as *const _) } } -} \ No newline at end of file +} diff --git a/kernel/src/bsp.rs b/kernel/src/bsp/mod.rs similarity index 64% rename from kernel/src/bsp.rs rename to kernel/src/bsp/mod.rs index fbcc24c86..d6e8cdc8c 100644 --- a/kernel/src/bsp.rs +++ b/kernel/src/bsp/mod.rs @@ -2,4 +2,4 @@ mod device_driver; mod rpi3; -pub use rpi3::*; \ No newline at end of file +pub use rpi3::*; diff --git a/kernel/src/bsp/rpi3/cpu.rs b/kernel/src/bsp/rpi3/cpu.rs index fc374a0c9..65cf5abbe 100644 --- a/kernel/src/bsp/rpi3/cpu.rs +++ b/kernel/src/bsp/rpi3/cpu.rs @@ -11,4 +11,4 @@ /// Used by `arch` code to find the early boot core. #[no_mangle] #[link_section = ".text._start_arguments"] -pub static BOOT_CORE_ID: u64 = 0; \ No newline at end of file +pub static BOOT_CORE_ID: u64 = 0; diff --git a/kernel/src/bsp/rpi3/driver.rs b/kernel/src/bsp/rpi3/driver.rs index 7bb69e441..c7dbe64fc 100644 --- a/kernel/src/bsp/rpi3/driver.rs +++ b/kernel/src/bsp/rpi3/driver.rs @@ -5,7 +5,7 @@ //! BSP driver support. use super::memory::map::mmio; -use crate::{bsp::device_driver, console, mbox, driver as generic_driver}; +use crate::{bsp::device_driver, console, driver as generic_driver, mbox}; use core::sync::atomic::{AtomicBool, Ordering}; //-------------------------------------------------------------------------------------------------- @@ -17,9 +17,8 @@ static MINI_UART: device_driver::MiniUart = // static PL011_UART: device_driver::PL011Uart = // unsafe { device_driver::PL011Uart::new(mmio::PL011_UART_START) }; static GPIO: device_driver::GPIO = unsafe { device_driver::GPIO::new(mmio::GPIO_START) }; -pub static MBOX: device_driver::MBOX = - unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; - // unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; +pub static MBOX: device_driver::MBOX = unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; +// unsafe { device_driver::MBOX::new(mmio::MAILBOX_START) }; //-------------------------------------------------------------------------------------------------- // Private Code @@ -40,7 +39,6 @@ fn post_init_gpio() -> Result<(), &'static str> { Ok(()) } - fn post_init_mbox() -> Result<(), &'static str> { mbox::register_mbox(&MBOX); Ok(()) @@ -65,7 +63,7 @@ fn driver_gpio() -> Result<(), &'static str> { fn driver_mbox() -> Result<(), &'static str> { let mbox_descriptor = generic_driver::DeviceDriverDescriptor::new(&MBOX, Some(post_init_mbox)); generic_driver::driver_manager().register_driver(mbox_descriptor); - + Ok(()) } @@ -83,12 +81,11 @@ pub unsafe fn init() -> Result<(), &'static str> { if INIT_DONE.load(Ordering::Relaxed) { return Err("Init already done"); } - + driver_gpio()?; driver_uart()?; driver_mbox()?; - INIT_DONE.store(true, Ordering::Relaxed); Ok(()) -} \ No newline at end of file +} diff --git a/kernel/src/bsp/rpi3/kernel.ld b/kernel/src/bsp/rpi3/kernel.ld index 4b3e54208..4caa2f49e 100644 --- a/kernel/src/bsp/rpi3/kernel.ld +++ b/kernel/src/bsp/rpi3/kernel.ld @@ -67,6 +67,9 @@ SECTIONS *(.bss*); . = ALIGN(16); __bss_end_exclusive = .; + __dtb_address = .; + . += 16; + . = ALIGN(16); } :segment_data /*********************************************************************************************** diff --git a/kernel/src/console.rs b/kernel/src/console/mod.rs similarity index 99% rename from kernel/src/console.rs rename to kernel/src/console/mod.rs index abcd247d1..6370ac24d 100644 --- a/kernel/src/console.rs +++ b/kernel/src/console/mod.rs @@ -79,4 +79,4 @@ pub fn register_console(new_console: &'static (dyn interface::All + Sync)) { /// This is the global console used by all printing macros. pub fn console() -> &'static dyn interface::All { CUR_CONSOLE.lock(|con| *con) -} \ No newline at end of file +} diff --git a/kernel/src/console/null_console.rs b/kernel/src/console/null_console.rs index c6ff3b76f..e92a022b6 100644 --- a/kernel/src/console/null_console.rs +++ b/kernel/src/console/null_console.rs @@ -38,4 +38,4 @@ impl interface::Read for NullConsole { } impl interface::Statistics for NullConsole {} -impl interface::All for NullConsole {} \ No newline at end of file +impl interface::All for NullConsole {} diff --git a/kernel/src/cpu.rs b/kernel/src/cpu.rs index 1234294f2..884372a05 100644 --- a/kernel/src/cpu.rs +++ b/kernel/src/cpu.rs @@ -36,4 +36,4 @@ pub fn wait_forever() -> ! { loop { asm::wfe() } -} \ No newline at end of file +} diff --git a/kernel/src/cpu/boot.rs b/kernel/src/cpu/boot.rs index 477b1e5c6..2a6c46492 100644 --- a/kernel/src/cpu/boot.rs +++ b/kernel/src/cpu/boot.rs @@ -29,4 +29,4 @@ global_asm!( #[no_mangle] pub unsafe fn _start_rust() -> ! { crate::kernel_init() -} \ No newline at end of file +} diff --git a/kernel/src/cpu/boot.s b/kernel/src/cpu/boot.s index 9696e86b4..565e08c06 100644 --- a/kernel/src/cpu/boot.s +++ b/kernel/src/cpu/boot.s @@ -18,6 +18,18 @@ add \register, \register, #:lo12:\symbol .endm +// Load the address of a symbol into a register, absolute. +// +// # Resources +// +// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html +.macro ADR_ABS register, symbol + movz \register, #:abs_g3:\symbol + movk \register, #:abs_g2_nc:\symbol + movk \register, #:abs_g1_nc:\symbol + movk \register, #:abs_g0_nc:\symbol +.endm + //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- @@ -27,6 +39,14 @@ // fn _start() //------------------------------------------------------------------------------ _start: + // ldr x1, 0x50000 + // str x0, [x1] + + // load address 0x60000's value and save it into __dtb_address + ADR_ABS x2, __dtb_address + ldr x1, =0x50000 + str x1, [x2] + // Only proceed on the boot core. Park it otherwise. mrs x0, MPIDR_EL1 and x0, x0, {CONST_CORE_ID_MASK} diff --git a/kernel/src/device_tree/dt.rs b/kernel/src/device_tree/dt.rs new file mode 100644 index 000000000..f492accbd --- /dev/null +++ b/kernel/src/device_tree/dt.rs @@ -0,0 +1,165 @@ +extern crate alloc; + +use super::dt_prop::Property; +use super::dt_string::load_string; +use super::dt_string::DtString; +use alloc::string::String; +use alloc::vec::Vec; + +#[derive(Debug)] +pub struct Dt { + name: String, + properties: Vec, + children: Vec
, + length: u32, +} + +#[allow(unused_assignments)] +impl Dt { + pub fn load(dt_struct_addr: u32, dt_string: &DtString) -> Dt { + let mut dt = Dt { + name: String::new(), + properties: Vec::new(), + children: Vec::new(), + length: 0, + }; + + //println!("[Device Tree Dt Root] dt_struct_addr: {:#x}", dt_struct_addr); + + let dt_struct_begin = unsafe { core::ptr::read_volatile(dt_struct_addr as *const u32) }; + // let dt_struct_begin = unsafe { &*(dt_struct_addr as *const u32) }; + // let dt_struct_begin = dt_struct_addr; + + // check fdt struct start with begin + if dt_struct_begin.swap_bytes() != FdtTag::FdtBeginNode as u32 { + //println!("[Device Tree Dt Root] FdtBeginNode Error, value {:#x}, expected: {:#x}", dt_struct_begin, MAGIC_NUMBER); + } else { + //println!("[Device Tree Dt Root] FdtBeginNode OK!"); + } + let struct_root_addr: u32 = dt_struct_addr + 4; + + // create root Device tree node + dt.name = load_string(struct_root_addr); + //println!("[Device Tree Dt Root] struct_root_addr: {:#x}, Root name: {}",struct_root_addr , dt.name); + + // get the next tag addr + // add nullterm + let mut index_addr = struct_root_addr + dt.name.len() as u32 + 1; + index_addr = align_up(index_addr,4); + // let mut index_addr = align_addr(struct_root_addr + dt.name.len() as u32 + 1, 4) + 4; + //println!("[Device Tree Dt Root] index_addr: {:#x}", index_addr); + + // let mut counter:u32 = 0; + + loop { + let tag = unsafe { core::ptr::read_volatile(index_addr as *const u32) }; + let tag = tag.swap_bytes(); + + //println!("[Device Tree Dt] index_addr: {:#x} FdtTag: {:#x} counter: {}", index_addr, tag, counter); + // counter+=1; + + let tag = FdtTag::try_from(tag).unwrap(); + + match tag { + // 0x1 + FdtTag::FdtBeginNode => { + //println!("[Device Tree Dt FdtBeginNode]"); + let child = Dt::load(index_addr, dt_string); + index_addr += child.length; + dt.children.push(child); + } + // 0x2 + FdtTag::FdtEndNode => { + //println!("[Device Tree Dt FdtEndNode]"); + index_addr += 4; + break; + } + // 0x3 + FdtTag::FdtProp => { + index_addr += 4; + //println!("[Device Tree Dt FdtProp] index_addr: {:#x}",index_addr); + let property = Property::load(index_addr, dt_string); + index_addr += property.get_length(); + // index_addr += 8; + //println!("[Device Tree Dt FdtProp] property name: {}, propLen: {:#x}, index_addr: {:#x}",property.name, property.get_length(), index_addr); + dt.properties.push(property); + index_addr = align_up(index_addr,4); + } + // 0x4 + FdtTag::FdtNop => { + //println!("[Device Tree Dt FdtNop]"); + index_addr += 4; + continue; + } + // 0x5 + FdtTag::FdtEnd => { + //println!("[Device Tree Dt FdtEnd]"); + // index_addr; + break; + } + } + } + + dt.length = index_addr - dt_struct_addr; + + // return + dt + } + + pub fn get_prop(&self, name: &str) -> Option<&Property> { + if let Some(prop) = self.properties.iter().find(|prop| prop.name == name) { + return Some(prop); + } else { + for child in self.children.iter() { + if let Some(prop) = child.get_prop(name) { + return Some(prop); + } + } + } + None + } +} + +#[repr(u32)] +pub enum FdtTag { + FdtBeginNode = 0x1, // start name full name + FdtEndNode = 0x2, // end + FdtProp = 0x3, + FdtNop = 0x4, + FdtEnd = 0x9, +} + +impl TryFrom for FdtTag { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + match value { + 0x1 => Ok(FdtTag::FdtBeginNode), + 0x2 => Ok(FdtTag::FdtEndNode), + 0x3 => Ok(FdtTag::FdtProp), + 0x4 => Ok(FdtTag::FdtNop), + 0x9 => Ok(FdtTag::FdtEnd), + _ => Err("Invalid Token"), + } + } +} + + +fn align_up(addr: u32, align: u32) -> u32 { + match addr % align { + 0 => addr, + remainder => addr + align - remainder, + } +} + + +// align the addr to n bytes +// fn align_addr(addr: u32, n: u32) -> u32 { +// // (index_addr + 3) & !3; +// let offset = n - (addr % n); +// if offset == n { +// addr +// } else { +// addr + offset +// } +// } diff --git a/kernel/src/device_tree/dt_header.rs b/kernel/src/device_tree/dt_header.rs new file mode 100644 index 000000000..14ae5dfb8 --- /dev/null +++ b/kernel/src/device_tree/dt_header.rs @@ -0,0 +1,102 @@ +// https://github.com/repnop/fdt/tree/masterfv +// https://devicetree-specification.readthedocs.io/en/stable/flattened-format.html +// https://www.wowotech.net/device_model/dt-code-file-struct-parse.html +// flatten device tree + +const MAGIC_NUMBER: u32 = 0xd00dfeed; + +/// Possible errors when attempting to create an `Fdt` +// #[derive(Debug, Clone, Copy, PartialEq)] +// pub enum FdtError { +// /// The FDT had an invalid magic value +// BadMagic, +// /// The given pointer was null +// BadPtr, +// /// The slice passed in was too small to fit the given total size of the FDT +// /// structure +// BufferTooSmall, +// } + +/// A flattened devicetree located somewhere in memory +/// +/// Note on `Debug` impl: by default the `Debug` impl of this struct will not +/// print any useful information, if you would like a best-effort tree print +/// which looks similar to `dtc`'s output, enable the `pretty-printing` feature +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FdtHeader { + magic: u32, + totalsize: u32, + off_dt_struct: u32, + off_dt_strings: u32, + off_mem_rsvmap: u32, + version: u32, + last_comp_version: u32, + boot_cpuid_phys: u32, + size_dt_strings: u32, + size_dt_struct: u32, +} + +impl FdtHeader { + pub fn valid_magic(&self) -> bool { + // magic number + self.magic == MAGIC_NUMBER + } + + pub fn get_off_dt_struct(&self) -> u32 { + self.off_dt_struct + } + + pub fn get_off_dt_strings(&self) -> u32 { + self.off_dt_strings + } + + // pub fn get_size_dt_struct(&self) -> u32 { + // self.size_dt_struct + // } + + // pub fn get_size_dt_strings(&self) -> u32 { + // self.size_dt_strings + // } + + // pub fn get_struct_range(&self) -> core::ops::Range { + // let start = self.off_dt_struct as usize; + // let end = start + self.size_dt_struct as usize; + + // start..end + // } + + // pub fn get_strings_range(&self) -> core::ops::Range { + // let start = self.off_dt_strings as usize; + // let end = start + self.size_dt_strings as usize; + + // start..end + // } + + pub fn load(dtb_addr: u32) -> FdtHeader { + let header_ptr = unsafe { &*(dtb_addr as *const FdtHeader) }; + // cause device tree use big-endian format, + // so we need to use swap_bytes to change to little-endian + // println!("[fdt_header] Load magic: {:#x}", header_ptr.magic.swap_bytes()); + // println!("[fdt_header] Load totalsize: {:#x}", header_ptr.totalsize.swap_bytes()); + // println!("[fdt_header] Load off_dt_struct: {:#x}", header_ptr.off_dt_struct.swap_bytes()); + // println!("[fdt_header] Load off_dt_strings: {:#x}", header_ptr.off_dt_strings.swap_bytes()); + // println!("[fdt_header] Load version: {:#x}", header_ptr.version.swap_bytes()); + // println!("[fdt_header] Load last_comp_version: {:#x}", header_ptr.last_comp_version.swap_bytes()); + // println!("[fdt_header] Load boot_cpuid_phys: {:#x}", header_ptr.boot_cpuid_phys.swap_bytes()); + // println!("[fdt_header] Load size_dt_strings: {:#x}", header_ptr.size_dt_strings.swap_bytes()); + // println!("[fdt_header] Load size_dt_struct: {:#x}", header_ptr.size_dt_struct.swap_bytes()); + FdtHeader { + magic: header_ptr.magic.swap_bytes(), + totalsize: header_ptr.totalsize.swap_bytes(), + off_dt_struct: header_ptr.off_dt_struct.swap_bytes(), + off_dt_strings: header_ptr.off_dt_strings.swap_bytes(), + off_mem_rsvmap: header_ptr.off_mem_rsvmap.swap_bytes(), + version: header_ptr.version.swap_bytes(), + last_comp_version: header_ptr.last_comp_version.swap_bytes(), + boot_cpuid_phys: header_ptr.boot_cpuid_phys.swap_bytes(), + size_dt_strings: header_ptr.size_dt_strings.swap_bytes(), + size_dt_struct: header_ptr.size_dt_struct.swap_bytes(), + } + } +} diff --git a/kernel/src/device_tree/dt_prop.rs b/kernel/src/device_tree/dt_prop.rs new file mode 100644 index 000000000..4ad93e91c --- /dev/null +++ b/kernel/src/device_tree/dt_prop.rs @@ -0,0 +1,81 @@ +extern crate alloc; + +use super::dt_string::DtString; +use alloc::string::String; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum PropValue { + Integer(u32), + String(String), +} + +struct PropHeader { + length: u32, + nameoff: u32, +} + +impl PropHeader { + fn load(addr: u32) -> PropHeader { + let header = unsafe { &*(addr as *const PropHeader) }; + PropHeader { + length: header.length.swap_bytes(), + nameoff: header.nameoff.swap_bytes(), + } + } +} + +#[derive(Debug)] +pub struct Property { + pub length: u32, + pub name: String, + pub value: PropValue, +} + +impl Property { + pub fn load(property_addr: u32, strings: &DtString) -> Property { + let header = PropHeader::load(property_addr); + // println!( + // "[Device Tree Prop] Prop Header Loaded ,length: {}, nameof: {}", + // header.length, + // header.nameoff + // ); + let name: String = strings.get(header.nameoff); + //println!("[Device Tree Prop] String Loaded"); + let value = match header.length { + 4 | 8 => { + //println!("[Device Tree Prop] Int property_addr: {:#x}", property_addr + 8); + let value = unsafe { core::ptr::read_volatile((property_addr + 8) as *const u32) } + .swap_bytes(); + PropValue::Integer(value) + } + _ => { + //println!("[Device Tree Prop] String property_addr: {:#x}", property_addr + 8); + let value = load_data(property_addr + 8, header.length); + PropValue::String(value) + } + }; + Property { + length: header.length + 8, + name, + value, + } + } + + pub fn get_length(&self) -> u32 { + self.length + } +} + +// get string end with '\0' +pub fn load_data(base_address: u32, len: u32) -> String { + let mut addr = base_address; + let mut string = String::new(); + for _counter in 0..len { + let c = unsafe { core::ptr::read_volatile(addr as *const u8) }; + string.push(c as char); + addr += 1; + } + //println!("[Device Tree load_data] String: {} Addr: {:#x}", string ,addr); + string +} diff --git a/kernel/src/device_tree/dt_string.rs b/kernel/src/device_tree/dt_string.rs new file mode 100644 index 000000000..52d7b34a3 --- /dev/null +++ b/kernel/src/device_tree/dt_string.rs @@ -0,0 +1,46 @@ +extern crate alloc; +use alloc::string::String; + +pub struct DtString { + base_address: u32, +} + +impl DtString { + pub fn load(base_address: u32) -> DtString { + DtString { base_address } + } + + // get string end with '\0' + pub fn get(&self, offset: u32) -> String { + let mut addr = self.base_address + offset; + let mut string = String::new(); + //println!("[Device Tree Dtstring] String: {} Addr: {:#x}", string ,addr); + loop { + let c = unsafe { core::ptr::read_volatile(addr as *const u8) }; + if c == 0 { + break; + } + string.push(c as char); + addr += 1; + } + //println!("[Device Tree Dtstring] String: {} Addr: {:#x}", string ,addr); + string + } +} + +// get string end with '\0' +pub fn load_string(base_address: u32) -> String { + let mut addr = base_address; + let mut string = String::new(); + //println!("[Device Tree load_string] String: {} Addr: {:#x}", string ,addr); + loop { + let c = unsafe { core::ptr::read_volatile(addr as *const u8) }; + if c == 0 { + break; + } + string.push(c as char); + addr += 1; + } + //println!("[Device Tree load_string] String: {} Addr: {:#x}", string ,addr); + string +} diff --git a/kernel/src/device_tree/dt_struct.rs b/kernel/src/device_tree/dt_struct.rs new file mode 100644 index 000000000..84f182c6b --- /dev/null +++ b/kernel/src/device_tree/dt_struct.rs @@ -0,0 +1,15 @@ +// #[repr(C)] +// #[derive(Clone, Copy)] +// pub struct FdtNodeHeader<'a> { +// name: &'a [u8], +// tag: u32, +// } + +// #[repr(C)] +// #[derive(Clone, Copy)] +// pub struct FdtProperty<'a> { +// tag: u32, +// len: u32, +// nameoff: u32, +// data: &'a [u8], // Flexible array member not directly representable in Rust +// } diff --git a/kernel/src/device_tree/mod.rs b/kernel/src/device_tree/mod.rs new file mode 100644 index 000000000..cb0b916be --- /dev/null +++ b/kernel/src/device_tree/mod.rs @@ -0,0 +1,76 @@ +use crate::println; + +pub mod dt; +mod dt_header; +mod dt_prop; +mod dt_string; +mod dt_struct; + +// use special memory address to pass x0 from bootloader to kernel +extern "C" { + static __dtb_address: u32; +} + +fn get_dtb_address() -> u32 { + // let ptr_dtb_address = unsafe { __dtb_address as *const u32 }; + + let dtb_address = unsafe { core::ptr::read_volatile(__dtb_address as *const u32) }; + + // let dtb_address = unsafe { *ptr_dtb_address as *const u32 }; + + // loop{ + // println!("[Device Tree] Value of ptr_dtb_address: {:#x}", dtb_address as u32); + // } + // let ptr_dtb_magic = unsafe { *dtb_address as *const u32 }; + // let dtb_magic = unsafe { *ptr_dtb_magic }; + + //println!("[Device Tree] Value of __dtb_address: {:#x}", unsafe {__dtb_address }); + //println!("[Device Tree] Value of ptr_dtb_address: {:#x}", ptr_dtb_address as u32); + //println!("[Device Tree] Value of dtb_address: {:#x}", dtb_address as u32); + //println!("[Device Tree] Value of dtb_magic: {:#x}", dtb_magic.swap_bytes()); + + // checking dtb address has valid magic value + // if dtb_magic.swap_bytes() != 0xd00dfeed { + // loop{ + // println!("[Device Tree] invalid dtb address"); + // } + // //println!("[Device Tree] invalid dtb address"); + // } + + // unsafe{*dtb_address} + dtb_address as u32 +} + +pub fn load_dtb() -> dt::Dt { + let dtb_address = get_dtb_address(); + // println!("[Device Tree] dtb_address: {:#x}", dtb_address); + + let fdt_header = dt_header::FdtHeader::load(dtb_address); + + if fdt_header.valid_magic() == false { + println!("[Device Tree] fdtHeader magic non valid"); + } + + let strings_addr = dtb_address + fdt_header.get_off_dt_strings(); + //println!("[Device Tree header] off_dt_strings: {:#x}",strings_addr); + let dt_strings = dt_string::DtString::load(strings_addr); + + let dt_struct_addr = dtb_address + fdt_header.get_off_dt_struct(); + //println!("[Device Tree] dt_struct_addr: {:#x}",dt_struct_addr); + + let dt = dt::Dt::load(dt_struct_addr, &dt_strings); + + dt +} + +pub fn get_initrd_start() -> Option { + let dt = load_dtb(); + let node = dt.get_prop("linux,initrd-start"); + match node { + Some(node) => match node.value { + dt_prop::PropValue::Integer(value) => Some(value), + _ => None, + }, + None => None, + } +} diff --git a/kernel/src/driver.rs b/kernel/src/driver.rs index 2ee74f16a..feef34e20 100644 --- a/kernel/src/driver.rs +++ b/kernel/src/driver.rs @@ -5,8 +5,8 @@ //! Driver support. use crate::{ - println, - synchronization::{interface::Mutex, NullLock}, + println, + synchronization::{interface::Mutex, NullLock}, }; //-------------------------------------------------------------------------------------------------- @@ -16,8 +16,8 @@ use crate::{ const NUM_DRIVERS: usize = 5; struct DriverManagerInner { - next_index: usize, - descriptors: [Option; NUM_DRIVERS], + next_index: usize, + descriptors: [Option; NUM_DRIVERS], } //-------------------------------------------------------------------------------------------------- @@ -26,20 +26,20 @@ struct DriverManagerInner { /// Driver interfaces. pub mod interface { - /// Device Driver functions. - pub trait DeviceDriver { - /// Return a compatibility string for identifying the driver. - fn compatible(&self) -> &'static str; - - /// Called by the kernel to bring up the device. - /// - /// # Safety - /// - /// - During init, drivers might do stuff with system-wide impact. - unsafe fn init(&self) -> Result<(), &'static str> { - Ok(()) - } - } + /// Device Driver functions. + pub trait DeviceDriver { + /// Return a compatibility string for identifying the driver. + fn compatible(&self) -> &'static str; + + /// Called by the kernel to bring up the device. + /// + /// # Safety + /// + /// - During init, drivers might do stuff with system-wide impact. + unsafe fn init(&self) -> Result<(), &'static str> { + Ok(()) + } + } } /// Tpye to be used as an optional callback after a driver's init() has run. @@ -48,13 +48,13 @@ pub type DeviceDriverPostInitCallback = unsafe fn() -> Result<(), &'static str>; /// A descriptor for device drivers. #[derive(Copy, Clone)] pub struct DeviceDriverDescriptor { - device_driver: &'static (dyn interface::DeviceDriver + Sync), - post_init_callback: Option, + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, } /// Provides device driver management functions. pub struct DriverManager { - inner: NullLock, + inner: NullLock, } //-------------------------------------------------------------------------------------------------- @@ -68,13 +68,13 @@ static DRIVER_MANAGER: DriverManager = DriverManager::new(); //-------------------------------------------------------------------------------------------------- impl DriverManagerInner { - /// Create an instance. - pub const fn new() -> Self { - Self { - next_index: 0, - descriptors: [None; NUM_DRIVERS], - } - } + /// Create an instance. + pub const fn new() -> Self { + Self { + next_index: 0, + descriptors: [None; NUM_DRIVERS], + } + } } //-------------------------------------------------------------------------------------------------- @@ -82,86 +82,86 @@ impl DriverManagerInner { //-------------------------------------------------------------------------------------------------- impl DeviceDriverDescriptor { - /// Create an instance. - pub fn new( - device_driver: &'static (dyn interface::DeviceDriver + Sync), - post_init_callback: Option, - ) -> Self { - Self { - device_driver, - post_init_callback, - } - } + /// Create an instance. + pub fn new( + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, + ) -> Self { + Self { + device_driver, + post_init_callback, + } + } } /// Return a reference to the global DriverManager. pub fn driver_manager() -> &'static DriverManager { - &DRIVER_MANAGER + &DRIVER_MANAGER } impl DriverManager { - /// Create an instance. - pub const fn new() -> Self { - Self { - inner: NullLock::new(DriverManagerInner::new()), - } - } - - /// Register a device driver with the kernel. - pub fn register_driver(&self, descriptor: DeviceDriverDescriptor) { - self.inner.lock(|inner| { - inner.descriptors[inner.next_index] = Some(descriptor); - inner.next_index += 1; - }) - } - - /// Helper for iterating over registered drivers. - fn for_each_descriptor<'a>(&'a self, f: impl FnMut(&'a DeviceDriverDescriptor)) { - self.inner.lock(|inner| { - inner - .descriptors - .iter() - .filter_map(|x| x.as_ref()) - .for_each(f) - }) - } - - /// Fully initialize all drivers. - /// - /// # Safety - /// - /// - During init, drivers might do stuff with system-wide impact. - pub unsafe fn init_drivers(&self) { - self.for_each_descriptor(|descriptor| { - // 1. Initialize driver. - if let Err(x) = descriptor.device_driver.init() { - panic!( - "Error initializing driver: {}: {}", - descriptor.device_driver.compatible(), - x - ); - } - - // 2. Call corresponding post init callback. - if let Some(callback) = &descriptor.post_init_callback { - if let Err(x) = callback() { - panic!( - "Error during driver post-init callback: {}: {}", - descriptor.device_driver.compatible(), - x - ); - } - } - }); - } - - /// Enumerate all registered device drivers. - pub fn enumerate(&self) { - let mut i: usize = 1; - self.for_each_descriptor(|descriptor| { - println!(" {}. {}", i, descriptor.device_driver.compatible()); - - i += 1; - }); - } -} \ No newline at end of file + /// Create an instance. + pub const fn new() -> Self { + Self { + inner: NullLock::new(DriverManagerInner::new()), + } + } + + /// Register a device driver with the kernel. + pub fn register_driver(&self, descriptor: DeviceDriverDescriptor) { + self.inner.lock(|inner| { + inner.descriptors[inner.next_index] = Some(descriptor); + inner.next_index += 1; + }) + } + + /// Helper for iterating over registered drivers. + fn for_each_descriptor<'a>(&'a self, f: impl FnMut(&'a DeviceDriverDescriptor)) { + self.inner.lock(|inner| { + inner + .descriptors + .iter() + .filter_map(|x| x.as_ref()) + .for_each(f) + }) + } + + /// Fully initialize all drivers. + /// + /// # Safety + /// + /// - During init, drivers might do stuff with system-wide impact. + pub unsafe fn init_drivers(&self) { + self.for_each_descriptor(|descriptor| { + // 1. Initialize driver. + if let Err(x) = descriptor.device_driver.init() { + panic!( + "Error initializing driver: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + + // 2. Call corresponding post init callback. + if let Some(callback) = &descriptor.post_init_callback { + if let Err(x) = callback() { + panic!( + "Error during driver post-init callback: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + } + }); + } + + /// Enumerate all registered device drivers. + pub fn enumerate(&self) { + let mut i: usize = 1; + self.for_each_descriptor(|descriptor| { + println!(" {}. {}", i, descriptor.device_driver.compatible()); + + i += 1; + }); + } +} diff --git a/kernel/src/fs/init_ram_fs.rs b/kernel/src/fs/init_ram_fs.rs new file mode 100644 index 000000000..06783c076 --- /dev/null +++ b/kernel/src/fs/init_ram_fs.rs @@ -0,0 +1,132 @@ +/* +https://man.freebsd.org/cgi/man.cgi?query=cpio&sektion=5 +https://github.com/rcore-os/cpio/blob/main/src/lib.rs +*/ +extern crate alloc; + +use crate::println; + +const CPIO_END: &str = "TRAILER!!!\0"; +const MAGIC_NUMBER: &[u8] = b"070701"; +const HEADER_LEN: usize = 110; + +pub(crate) struct Cpio { + data: *const u8, +} + +#[repr(C, packed)] +struct CpioNewcHeader { + c_magic: [u8; 6], + c_ino: [u8; 8], + c_mode: [u8; 8], + c_uid: [u8; 8], + c_gid: [u8; 8], + c_nlink: [u8; 8], + c_mtime: [u8; 8], + c_filesize: [u8; 8], + c_devmajor: [u8; 8], + c_devminor: [u8; 8], + c_rdevmajor: [u8; 8], + c_rdevminor: [u8; 8], + c_namesize: [u8; 8], + c_check: [u8; 8], +} + +impl Cpio { + pub fn load(data: *const u8) -> Cpio { + Cpio { data } + } + + pub fn print_file_list(&self) { + let mut address = self.data; + loop { + let header = unsafe { &*(address as *const CpioNewcHeader) }; + if header.c_magic != *MAGIC_NUMBER { + println!("[InitRamFs] Invalid CPIO magic"); + break; + } + let namesize = u64::from_str_radix( + unsafe { core::str::from_utf8_unchecked(&header.c_namesize) }, + 16, + ) + .unwrap(); + let filesize = u64::from_str_radix( + unsafe { core::str::from_utf8_unchecked(&header.c_filesize) }, + 16, + ) + .unwrap(); + let name = unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts( + address.add(HEADER_LEN), + namesize as usize, + )) + }; + + if name == CPIO_END { + break; + } + + println!("{} {}", filesize, name); + + address = unsafe { address.add(pad_to_4(HEADER_LEN + namesize as usize)) }; + address = unsafe { address.add(pad_to_4(filesize as usize)) }; + } + } + + pub fn get_file(&self, filename: &str) -> Option<&[u8]> { + let mut address = self.data; + loop { + let header = unsafe { &*(address as *const CpioNewcHeader) }; + if header.c_magic != *MAGIC_NUMBER { + break; + } + let namesize = u64::from_str_radix( + unsafe { core::str::from_utf8_unchecked(&header.c_namesize) }, + 16, + ) + .unwrap(); + let filesize = u64::from_str_radix( + unsafe { core::str::from_utf8_unchecked(&header.c_filesize) }, + 16, + ) + .unwrap(); + let name = unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts( + address.add(HEADER_LEN), + namesize as usize, + )) + }; + + if name == CPIO_END { + break; + } + + address = unsafe { address.add(pad_to_4(HEADER_LEN + namesize as usize)) }; + + let mut found = true; + for i in 0..namesize as usize { + if name.as_bytes()[i] == 0 { + break; + } + if name.as_bytes()[i] != filename.as_bytes()[i] { + found = false; + break; + } + } + + if found { + return Some(unsafe { core::slice::from_raw_parts(address, filesize as usize) }); + } + + address = unsafe { address.add(pad_to_4(filesize as usize)) }; + } + None + } +} + +fn pad_to_4(len: usize) -> usize { + match len % 4 { + 0 => len, + x => len + (4 - x), + } +} diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs new file mode 100644 index 000000000..6939b67b6 --- /dev/null +++ b/kernel/src/fs/mod.rs @@ -0,0 +1 @@ +pub mod init_ram_fs; diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 2e7f0d4d7..0c9ac1ac8 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1 +1 @@ -#![no_std] \ No newline at end of file +#![no_std] diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 2dd3fc353..abfd11b89 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -5,21 +5,26 @@ #![no_main] #![no_std] +mod arrsting; mod bsp; mod console; mod cpu; +mod device_tree; mod driver; -mod panic_wait; -mod print; -mod synchronization; +mod fs; mod mbox; +mod memory; +mod panic_wait; mod power; +mod print; mod shell; -mod arrsting; -mod memory; +mod synchronization; use crate::bsp::memory::map::sdram; +extern "C" { + static __dtb_address: u64; +} /// Early init code. /// /// # Safety @@ -37,7 +42,19 @@ unsafe fn kernel_init() -> ! { // println! is usable from here on. // Initialize real memory - memory::ALLOCATOR.init(sdram::RAM_START,sdram::RAM_END); + memory::ALLOCATOR.init(sdram::RAM_START, sdram::RAM_END); + + // let dtb_addr = 0x50000 as *const u8; + // let dtb_addr = unsafe { core::ptr::read_volatile(dtb_addr as *const u32) }; + + // loop{ + // println!("dtb_addr: {:#x}", dtb_addr); + // println!("dtb_addr_padding {:#x}", core::ptr::read_volatile(__dtb_address as *const u32)); + // } + + // Init device tree + let initrd_address = device_tree::get_initrd_start().unwrap(); + println!("initrd_address: {:#x}", initrd_address); // Transition from unsafe to safe. kernel_main() @@ -61,4 +78,4 @@ fn kernel_main() -> ! { println!("[4] Echoing input now"); shell::start_shell(); -} \ No newline at end of file +} diff --git a/kernel/src/mbox.rs b/kernel/src/mbox/mod.rs similarity index 99% rename from kernel/src/mbox.rs rename to kernel/src/mbox/mod.rs index 006947be2..126455c54 100644 --- a/kernel/src/mbox.rs +++ b/kernel/src/mbox/mod.rs @@ -54,4 +54,4 @@ pub fn mbox() -> &'static dyn interface::All { //------------------------------------------------------------------------------ // OS Interface Code -//------------------------------------------------------------------------------ \ No newline at end of file +//------------------------------------------------------------------------------ diff --git a/kernel/src/mbox/null_mbox.rs b/kernel/src/mbox/null_mbox.rs index 3d3677bdd..aaba00587 100644 --- a/kernel/src/mbox/null_mbox.rs +++ b/kernel/src/mbox/null_mbox.rs @@ -23,16 +23,15 @@ pub static NULL_MBOX: NullMBOX = NullMBOX {}; //-------------------------------------------------------------------------------------------------- impl interface::Boardinfo for NullMBOX { - /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to - /// serialize access. - fn get_arm_memory(&self) -> (u32, u32) { - (0, 0) - } - - fn get_board_revision(&self) -> u32 { - 0 - } + /// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to + /// serialize access. + fn get_arm_memory(&self) -> (u32, u32) { + (0, 0) + } + + fn get_board_revision(&self) -> u32 { + 0 + } } - -impl interface::All for NullMBOX {} \ No newline at end of file +impl interface::All for NullMBOX {} diff --git a/kernel/src/memory/bump_allocator.rs b/kernel/src/memory/bump_allocator.rs index d1be4d601..9a8c53983 100644 --- a/kernel/src/memory/bump_allocator.rs +++ b/kernel/src/memory/bump_allocator.rs @@ -4,8 +4,11 @@ inspired from https://os.phil-opp.com use crate::synchronization::{interface::Mutex, NullLock}; // use crate::{print, println}; -use core::alloc::{GlobalAlloc, Layout}; -use crate::println; +// use crate::println; +use core::{ + alloc::{GlobalAlloc, Layout}, + usize, +}; //-------------------------------------------------------------------------------------------------- // Public Definitions @@ -47,8 +50,9 @@ impl BumpAllocatorInner { } fn alloc(&mut self, layout: Layout) -> *mut u8 { - println!("START alloc"); - let alloc_start = self.next; + // println!("[Alloc] START alloc"); + let alloc_start = align_up(self.next, layout.align()); + // println!("[Alloc] alloc_start: {:#x}", alloc_start); self.next = alloc_start + layout.size(); self.allocations += 1; alloc_start as *mut u8 @@ -60,6 +64,12 @@ impl BumpAllocatorInner { } } +fn align_up(addr: usize, align: usize) -> usize { + match addr % align { + 0 => addr, + remainder => addr + align - remainder, + } +} //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- diff --git a/kernel/src/memory.rs b/kernel/src/memory/mod.rs similarity index 79% rename from kernel/src/memory.rs rename to kernel/src/memory/mod.rs index 4028bbe8a..380c0d2d2 100644 --- a/kernel/src/memory.rs +++ b/kernel/src/memory/mod.rs @@ -1,4 +1,4 @@ pub mod bump_allocator; #[global_allocator] -pub static ALLOCATOR: bump_allocator::BumpAllocator = bump_allocator::BumpAllocator::new(); \ No newline at end of file +pub static ALLOCATOR: bump_allocator::BumpAllocator = bump_allocator::BumpAllocator::new(); diff --git a/kernel/src/panic_wait.rs b/kernel/src/panic_wait.rs index c10ffc9a7..6e4a719ce 100644 --- a/kernel/src/panic_wait.rs +++ b/kernel/src/panic_wait.rs @@ -61,4 +61,4 @@ fn panic(info: &PanicInfo) -> ! { ); cpu::wait_forever() -} \ No newline at end of file +} diff --git a/kernel/src/power.rs b/kernel/src/power.rs index 2daac1cd3..21dcef680 100644 --- a/kernel/src/power.rs +++ b/kernel/src/power.rs @@ -1,28 +1,27 @@ use core::ptr::write_volatile; pub fn reboot() { - reset(100); + reset(100); } const PM_PASSWORD: u32 = 0x5a000000; const PM_RSTC: u32 = 0x3F10_001C; const PM_WDOG: u32 = 0x3F10_0024; - pub fn reset(tick: u32) { - unsafe { - let mut r = PM_PASSWORD | 0x20; - write_volatile(PM_RSTC as *mut u32, r); - r = PM_PASSWORD | tick; - write_volatile(PM_WDOG as *mut u32, r); - } + unsafe { + let mut r = PM_PASSWORD | 0x20; + write_volatile(PM_RSTC as *mut u32, r); + r = PM_PASSWORD | tick; + write_volatile(PM_WDOG as *mut u32, r); + } } #[allow(dead_code)] pub fn cancel_reset() { - unsafe { - let r = PM_PASSWORD | 0; - write_volatile(PM_RSTC as *mut u32, r); - write_volatile(PM_WDOG as *mut u32, r); - } -} \ No newline at end of file + unsafe { + let r = PM_PASSWORD | 0; + write_volatile(PM_RSTC as *mut u32, r); + write_volatile(PM_WDOG as *mut u32, r); + } +} diff --git a/kernel/src/print.rs b/kernel/src/print.rs index 66e45baed..80779f0c6 100644 --- a/kernel/src/print.rs +++ b/kernel/src/print.rs @@ -33,4 +33,4 @@ macro_rules! println { $crate::print::_print(format_args!($($arg)*)); ($crate::print!("\r\n")); }) -} \ No newline at end of file +} diff --git a/kernel/src/shell.rs b/kernel/src/shell.rs index 2cab0021b..a5013c191 100644 --- a/kernel/src/shell.rs +++ b/kernel/src/shell.rs @@ -1,71 +1,76 @@ extern crate alloc; -use alloc::boxed::Box; +use alloc::{boxed::Box, string::String}; use core::ptr; -use crate::{arrsting::ArrString, console::console, mbox, power, print, println}; +use crate::{console::console, device_tree, fs, mbox, power, print, println}; +pub fn start_shell() -> ! { + println!("TEST VER 0.0.3\r\n"); + print_help(); -pub fn start_shell() -> !{ - let msg_buf_exceed = "[system] Buf size limit exceed, reset buf"; - - let msg_help = "help\t: print this help menu\r\nhello\t: print Hello World!\r\nreboot\t: reboot the device\r\ninfo\t: show the device info"; - let msg_hello_world = "HelloWorld!"; - let msg_not_found = "Command not found"; - let msg_reboot = "Rebooting..."; - - let arr_help = ArrString::new("help"); - let arr_hello = ArrString::new("hello"); - let arr_reboot = ArrString::new("reboot"); - let arr_info = ArrString::new("info"); - let msg_alloc_test=ArrString::new("alloc"); - - let mut buf = ArrString::new(""); - - println!("TEST VER 0.0.2\r\n"); - print!("{}\r\n#", msg_help); + let mut input_string = String::from(""); loop { let c = console().read_char(); console().write_char(c); + input_string.push(c); + // println!("[shell] input_string: {}", input_string); - if c == '\n' { + if c == '\r' || c == '\n' { + let (cmd, arg1) = input_string.split_once(char::is_whitespace).unwrap(); + let arg1 = arg1.trim_start(); + // println!("[shell] cmd: {}, arg1: {}", cmd, arg1); print!("\r"); - if buf == arr_help { - println!("{}", msg_help); - } else if buf == arr_hello { - println!("{}", msg_hello_world); - } else if buf == arr_reboot { - println!("{}", msg_reboot); - power::reboot(); - } else if buf == arr_info { - println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); - // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); - println!( - "RAM: {} {}", - mbox::mbox().get_arm_memory().0, - mbox::mbox().get_arm_memory().1 - ); - } else if buf == msg_alloc_test { - println!("Allocate test start!"); - check_alloc(); - println!("Allocate test End!"); - } else { - println!("{}", msg_not_found); - } - buf.clean_buf(); - print!("#"); - continue; - - // arrsting::arrstrcmp(buf, help); - } else if buf.get_len() == 1024 { - buf.clean_buf(); - println!("{}", msg_buf_exceed); + match cmd { + "help" => { + print_help(); + } + "hello" => { + println!("Hello World!"); + } + "reboot" => { + println!("Rebooting..."); + power::reboot(); + } + "info" => { + println!("BoardVersion: {:x}", mbox::mbox().get_board_revision()); + // println!("BoardVersion: {:x}", bsp::driver::MBOX.get_board_revision()); + println!( + "RAM: {} {}", + mbox::mbox().get_arm_memory().0, + mbox::mbox().get_arm_memory().1 + ) + } + "alloc" => { + println!("Allocate test start!"); + check_alloc(); + println!("Allocate test End!"); + } + "ls" => { + let fs: fs::init_ram_fs::Cpio = fs::init_ram_fs::Cpio::load( + device_tree::get_initrd_start().unwrap() as *const u8, + ); + fs.print_file_list(); + } + "cat" => { + // memory::get_initramfs_files(arg_1); + let fs: fs::init_ram_fs::Cpio = fs::init_ram_fs::Cpio::load( + device_tree::get_initrd_start().unwrap() as *const u8, + ); + if let Some(data) = fs.get_file(arg1) { + print!("{}", core::str::from_utf8(data).unwrap()); + } else { + println!("File not found: {}", arg1); + } + } + _ => { + println!("Unknown command: {:?}", cmd); + } + } + input_string.clear(); print!("#"); - continue; - } else { - buf.push_char(c); } } } @@ -76,8 +81,18 @@ fn check_alloc() { for i in 0..32 { let x = Box::new(i); println!("{}", i); - println!{"{:p}" ,ptr::addr_of!(*x)}; + println! {"{:p}" ,ptr::addr_of!(*x)}; assert_eq!(*x, i); } assert_eq!(*long_lived, 1); // new -} \ No newline at end of file +} + +fn print_help() { + println!("help\t: print this help menu"); + println!("hello\t: print Hello World!"); + println!("reboot\t: reboot the device"); + println!("info\t: show the device info"); + println!("alloc\t: test allocator"); + println!("ls\t: list file"); + println!("cat\t: print file"); +} diff --git a/kernel/src/synchronization.rs b/kernel/src/synchronization.rs index b87cd46a0..94c83de1c 100644 --- a/kernel/src/synchronization.rs +++ b/kernel/src/synchronization.rs @@ -74,4 +74,4 @@ impl interface::Mutex for NullLock { f(data) } -} \ No newline at end of file +} diff --git a/kernel/test.log b/kernel/test.log new file mode 100644 index 0000000000000000000000000000000000000000..9853b9b6f8518ec7c4b54db48d3b67cf7f35bc66 GIT binary patch literal 615807 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 VJA5s`Ae2z74eA1D(o;+S7Jr=|L0td< literal 0 HcmV?d00001 diff --git a/resources/bcm2710-rpi-3-b-plus.dtb b/resources/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..471a6ae50b0fa84cfdd272e6f4cafc8532d047b0 GIT binary patch literal 34587 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 Date: Thu, 9 May 2024 18:58:02 +0800 Subject: [PATCH 11/11] hw2 --- rootfs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rootfs/Makefile b/rootfs/Makefile index cfd83dd3b..b88d274f9 100644 --- a/rootfs/Makefile +++ b/rootfs/Makefile @@ -1,3 +1,3 @@ all: ./1.txt ./2.txt./ 3.txt - find ./*.txt | cpio -o -H newc >! ../initramfs.cpio \ No newline at end of file + find ./*.txt | cpio -o -H newc >! ../resources/initramfs.cpio \ No newline at end of file