3190 字
16 分钟
使用Rust在树莓派上编写操作系统 - 07 - 时间戳
WARNING本文所有内容均为翻译,原文:Tutorial 07 - Timestamps;原项目:Operating System development tutorials in Rust on the Raspberry Pi
概述
- 用SD卡上的镜像启动树莓派感觉很棒,但如果对每个新的二进制文件,都需要手动将其放在SD卡上就会非常麻烦。因此,本章我们将编写一个链式加载器。
- 这是我们最后一次手动拷贝镜像到SD卡上。后面的每章教程都会在
Makefile中提供一个chainboot目标,以便通过UART加载内核。 - 我们为定时器硬件添加抽象,并在
_arch/aarch64中为ARM架构实现了定时器。 - 新的计时器函数用于给UART打印的内容添加时间戳,并消除了
GPIO设备驱动程序中由循环产生的延迟,从而提高精确性。 - 增加了一个
warn!()宏。
测试
通过chainboot进行测试(在前一个教程中添加):
$ make chainboot[...]Minipush 1.0
[MP] ⏳ Waiting for /dev/ttyUSB0[MP] ✅ Serial connected[MP] 🔌 Please power the target now __ __ _ _ _ _| \/ (_)_ _ (_) | ___ __ _ __| || |\/| | | ' \| | |__/ _ \/ _` / _` ||_| |_|_|_||_|_|____\___/\__,_\__,_|
Raspberry Pi 3
[ML] Requesting binary[MP] ⏩ Pushing 12 KiB =========================================🦀 100% 0 KiB/s Time: 00:00:00[ML] Loaded! Executing the payload now
[ 0.140431] mingo version 0.7.0[ 0.140630] Booting on: Raspberry Pi 3[ 0.141085] Architectural timer resolution: 52 ns[ 0.141660] Drivers loaded:[ 0.141995] 1. BCM GPIO[ 0.142353] 2. BCM PL011 UART[W 0.142777] Spin duration smaller than architecturally supported, skipping[ 0.143621] Spinning for 1 second[ 1.144023] Spinning for 1 second[ 2.144245] Spinning for 1 second与上一章代码的区别
diff -uNr 06_uart_chainloader/Cargo.toml 07_timestamps/Cargo.toml--- 06_uart_chainloader/Cargo.toml+++ 07_timestamps/Cargo.toml@@ -1,6 +1,6 @@ [package] name = "mingo"-version = "0.6.0"+version = "0.7.0" authors = ["Andre Richter <andre.o.richter@gmail.com>"] edition = "2021"
Binary files 06_uart_chainloader/demo_payload_rpi3.img and 07_timestamps/demo_payload_rpi3.img differBinary files 06_uart_chainloader/demo_payload_rpi4.img and 07_timestamps/demo_payload_rpi4.img differ
diff -uNr 06_uart_chainloader/Makefile 07_timestamps/Makefile--- 06_uart_chainloader/Makefile+++ 07_timestamps/Makefile@@ -23,29 +23,27 @@
# BSP-specific arguments. ifeq ($(BSP),rpi3)- TARGET = aarch64-unknown-none-softfloat- KERNEL_BIN = kernel8.img- QEMU_BINARY = qemu-system-aarch64- QEMU_MACHINE_TYPE = raspi3- QEMU_RELEASE_ARGS = -serial stdio -display none- OBJDUMP_BINARY = aarch64-none-elf-objdump- NM_BINARY = aarch64-none-elf-nm- READELF_BINARY = aarch64-none-elf-readelf- LINKER_FILE = src/bsp/raspberrypi/link.ld- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53- CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img+ TARGET = aarch64-unknown-none-softfloat+ KERNEL_BIN = kernel8.img+ QEMU_BINARY = qemu-system-aarch64+ QEMU_MACHINE_TYPE = raspi3+ QEMU_RELEASE_ARGS = -serial stdio -display none+ OBJDUMP_BINARY = aarch64-none-elf-objdump+ NM_BINARY = aarch64-none-elf-nm+ READELF_BINARY = aarch64-none-elf-readelf+ LINKER_FILE = src/bsp/raspberrypi/link.ld+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a53 else ifeq ($(BSP),rpi4)- TARGET = aarch64-unknown-none-softfloat- KERNEL_BIN = kernel8.img- QEMU_BINARY = qemu-system-aarch64- QEMU_MACHINE_TYPE =- QEMU_RELEASE_ARGS = -serial stdio -display none- OBJDUMP_BINARY = aarch64-none-elf-objdump- NM_BINARY = aarch64-none-elf-nm- READELF_BINARY = aarch64-none-elf-readelf- LINKER_FILE = src/bsp/raspberrypi/link.ld- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72- CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img+ TARGET = aarch64-unknown-none-softfloat+ KERNEL_BIN = kernel8.img+ QEMU_BINARY = qemu-system-aarch64+ QEMU_MACHINE_TYPE =+ QEMU_RELEASE_ARGS = -serial stdio -display none+ OBJDUMP_BINARY = aarch64-none-elf-objdump+ NM_BINARY = aarch64-none-elf-nm+ READELF_BINARY = aarch64-none-elf-readelf+ LINKER_FILE = src/bsp/raspberrypi/link.ld+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif
QEMU_MISSING_STRING = "This board is not yet supported for QEMU."@@ -77,7 +75,7 @@ -O binary
EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)-EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb+EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
##------------------------------------------------------------------------------@@ -134,7 +132,7 @@ ##------------------------------------------------------------------------------ ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.
-qemu qemuasm:+qemu: $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else # QEMU is supported.@@ -143,17 +141,13 @@ $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
-qemuasm: $(KERNEL_BIN)- $(call colorecho, "\nLaunching QEMU with ASM output")- @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm- endif
##------------------------------------------------------------------------------ ## Push the kernel to the real HW target ##------------------------------------------------------------------------------ chainboot: $(KERNEL_BIN)- @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD)+ @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN)
##------------------------------------------------------------------------------ ## Run clippy@@ -217,8 +211,7 @@ ##------------------------------------------------------------------------------ test_boot: $(KERNEL_BIN) $(call colorecho, "\nBoot test - $(BSP)")- @$(DOCKER_TEST) $(EXEC_TEST_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \- -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD)+ @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
test: test_boot
diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s 07_timestamps/src/_arch/aarch64/cpu/boot.s--- 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s+++ 07_timestamps/src/_arch/aarch64/cpu/boot.s@@ -18,17 +18,6 @@ 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- .equ _core_id_mask, 0b11
//--------------------------------------------------------------------------------------------------@@ -50,35 +39,23 @@ // If execution reaches here, it is the boot core.
// Initialize DRAM.- ADR_ABS x0, __bss_start- ADR_ABS x1, __bss_end_exclusive+ ADR_REL x0, __bss_start+ ADR_REL x1, __bss_end_exclusive
.L_bss_init_loop: cmp x0, x1- b.eq .L_relocate_binary+ b.eq .L_prepare_rust 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.+.L_prepare_rust: // Set the stack pointer.- ADR_ABS x0, __boot_core_stack_end_exclusive+ ADR_REL x0, __boot_core_stack_end_exclusive mov sp, x0
- // Jump to the relocated Rust code.- ADR_ABS x1, _start_rust- br x1+ // Jump to Rust code.+ b _start_rust
// Infinitely wait for events (aka "park the core"). .L_parking_loop:
diff -uNr 06_uart_chainloader/src/_arch/aarch64/cpu.rs 07_timestamps/src/_arch/aarch64/cpu.rs--- 06_uart_chainloader/src/_arch/aarch64/cpu.rs+++ 07_timestamps/src/_arch/aarch64/cpu.rs@@ -19,15 +19,6 @@
pub use asm::nop;
-/// Spin for `n` cycles.-#[cfg(feature = "bsp_rpi3")]-#[inline(always)]-pub fn spin_for_cycles(n: usize) {- for _ in 0..n {- asm::nop();- }-}- /// Pause execution on the core. #[inline(always)] pub fn wait_forever() -> ! {
diff -uNr 06_uart_chainloader/src/_arch/aarch64/time.rs 07_timestamps/src/_arch/aarch64/time.rs--- 06_uart_chainloader/src/_arch/aarch64/time.rs+++ 07_timestamps/src/_arch/aarch64/time.rs@@ -0,0 +1,119 @@+// SPDX-License-Identifier: MIT OR Apache-2.0+//+// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>++//! Architectural timer primitives.+//!+//! # Orientation+//!+//! Since arch modules are imported into generic modules using the path attribute, the path of this+//! file is:+//!+//! crate::time::arch_time++use crate::{time, warn};+use core::time::Duration;+use cortex_a::{asm::barrier, registers::*};+use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};++//--------------------------------------------------------------------------------------------------+// Private Definitions+//--------------------------------------------------------------------------------------------------++const NS_PER_S: u64 = 1_000_000_000;++/// ARMv8 Generic Timer.+struct GenericTimer;++//--------------------------------------------------------------------------------------------------+// Global instances+//--------------------------------------------------------------------------------------------------++static TIME_MANAGER: GenericTimer = GenericTimer;++//--------------------------------------------------------------------------------------------------+// Private Code+//--------------------------------------------------------------------------------------------------++impl GenericTimer {+ #[inline(always)]+ fn read_cntpct(&self) -> u64 {+ // Prevent that the counter is read ahead of time due to out-of-order execution.+ unsafe { barrier::isb(barrier::SY) };+ CNTPCT_EL0.get()+ }+}++//--------------------------------------------------------------------------------------------------+// Public Code+//--------------------------------------------------------------------------------------------------++/// Return a reference to the time manager.+pub fn time_manager() -> &'static impl time::interface::TimeManager {+ &TIME_MANAGER+}++//------------------------------------------------------------------------------+// OS Interface Code+//------------------------------------------------------------------------------++impl time::interface::TimeManager for GenericTimer {+ fn resolution(&self) -> Duration {+ Duration::from_nanos(NS_PER_S / (CNTFRQ_EL0.get() as u64))+ }++ fn uptime(&self) -> Duration {+ let current_count: u64 = self.read_cntpct() * NS_PER_S;+ let frq: u64 = CNTFRQ_EL0.get() as u64;++ Duration::from_nanos(current_count / frq)+ }++ fn spin_for(&self, duration: Duration) {+ // Instantly return on zero.+ if duration.as_nanos() == 0 {+ return;+ }++ // Calculate the register compare value.+ let frq = CNTFRQ_EL0.get();+ let x = match frq.checked_mul(duration.as_nanos() as u64) {+ None => {+ warn!("Spin duration too long, skipping");+ return;+ }+ Some(val) => val,+ };+ let tval = x / NS_PER_S;++ // Check if it is within supported bounds.+ let warn: Option<&str> = if tval == 0 {+ Some("smaller")+ // The upper 32 bits of CNTP_TVAL_EL0 are reserved.+ } else if tval > u32::max_value().into() {+ Some("bigger")+ } else {+ None+ };++ if let Some(w) = warn {+ warn!(+ "Spin duration {} than architecturally supported, skipping",+ w+ );+ return;+ }++ // Set the compare value register.+ CNTP_TVAL_EL0.set(tval);++ // Kick off the counting. // Disable timer interrupt.+ CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::SET + CNTP_CTL_EL0::IMASK::SET);++ // ISTATUS will be '1' when cval ticks have passed. Busy-check it.+ while !CNTP_CTL_EL0.matches_all(CNTP_CTL_EL0::ISTATUS::SET) {}++ // Disable counting again.+ CNTP_CTL_EL0.modify(CNTP_CTL_EL0::ENABLE::CLEAR);+ }+}
diff -uNr 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs 07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs--- 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs+++ 07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs@@ -143,25 +143,19 @@ /// Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] fn disable_pud_14_15_bcm2837(&mut self) {- use crate::cpu;+ use crate::{time, time::interface::TimeManager};+ use core::time::Duration;
- // 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;+ // The Linux 2837 GPIO driver waits 1 µs between the steps.+ const DELAY: Duration = Duration::from_micros(1);
self.registers.GPPUD.write(GPPUD::PUD::Off);- cpu::spin_for_cycles(DELAY);+ time::time_manager().spin_for(DELAY);
self.registers .GPPUDCLK0 .write(GPPUDCLK0::PUDCLK15::AssertClock + GPPUDCLK0::PUDCLK14::AssertClock);- cpu::spin_for_cycles(DELAY);+ time::time_manager().spin_for(DELAY);
self.registers.GPPUD.write(GPPUD::PUD::Off); self.registers.GPPUDCLK0.set(0);
diff -uNr 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs--- 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs+++ 07_timestamps/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs@@ -278,7 +278,7 @@ }
/// Retrieve a character.- fn read_char(&mut self, blocking_mode: BlockingMode) -> Option<char> {+ fn read_char_converting(&mut self, blocking_mode: BlockingMode) -> Option<char> { // If RX FIFO is empty, if self.registers.FR.matches_all(FR::RXFE::SET) { // immediately return in non-blocking mode.@@ -293,7 +293,12 @@ }
// Read one character.- let ret = self.registers.DR.get() as u8 as char;+ 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;@@ -373,14 +378,14 @@ impl console::interface::Read for PL011Uart { fn read_char(&self) -> char { self.inner- .lock(|inner| inner.read_char(BlockingMode::Blocking).unwrap())+ .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(BlockingMode::NonBlocking))+ .lock(|inner| inner.read_char_converting(BlockingMode::NonBlocking)) .is_some() {} }
diff -uNr 06_uart_chainloader/src/bsp/raspberrypi/link.ld 07_timestamps/src/bsp/raspberrypi/link.ld--- 06_uart_chainloader/src/bsp/raspberrypi/link.ld+++ 07_timestamps/src/bsp/raspberrypi/link.ld@@ -3,6 +3,8 @@ * Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> */
+__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;
@@ -26,8 +28,7 @@
SECTIONS {- /* Set the link address to 32 MiB */- . = 0x2000000;+ . = __rpi_phys_dram_start_addr;
/*********************************************************************************************** * Boot Core Stack@@ -44,7 +45,6 @@ /*********************************************************************************************** * Code + RO Data + Global Offset Table ***********************************************************************************************/- __binary_nonzero_start = .; .text : { KEEP(*(.text._start))@@ -61,10 +61,6 @@ ***********************************************************************************************/ .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) {
diff -uNr 06_uart_chainloader/src/bsp/raspberrypi/memory.rs 07_timestamps/src/bsp/raspberrypi/memory.rs--- 06_uart_chainloader/src/bsp/raspberrypi/memory.rs+++ 07_timestamps/src/bsp/raspberrypi/memory.rs@@ -11,10 +11,9 @@ /// The board's physical memory map. #[rustfmt::skip] pub(super) mod map {- pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x8_0000;
- pub const GPIO_OFFSET: usize = 0x0020_0000;- pub const UART_OFFSET: usize = 0x0020_1000;+ pub const GPIO_OFFSET: usize = 0x0020_0000;+ pub const UART_OFFSET: usize = 0x0020_1000;
/// Physical devices. #[cfg(feature = "bsp_rpi3")]@@ -36,13 +35,3 @@ pub const PL011_UART_START: usize = START + 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 -uNr 06_uart_chainloader/src/cpu.rs 07_timestamps/src/cpu.rs--- 06_uart_chainloader/src/cpu.rs+++ 07_timestamps/src/cpu.rs@@ -14,6 +14,3 @@ // Architectural Public Reexports //-------------------------------------------------------------------------------------------------- pub use arch_cpu::{nop, wait_forever};--#[cfg(feature = "bsp_rpi3")]-pub use arch_cpu::spin_for_cycles;
diff -uNr 06_uart_chainloader/src/main.rs 07_timestamps/src/main.rs--- 06_uart_chainloader/src/main.rs+++ 07_timestamps/src/main.rs@@ -119,6 +119,7 @@ mod panic_wait; mod print; mod synchronization;+mod time;
/// Early init code. ///@@ -141,56 +142,38 @@ kernel_main() }
-const MINILOAD_LOGO: &str = r#"- __ __ _ _ _ _-| \/ (_)_ _ (_) | ___ __ _ __| |-| |\/| | | ' \| | |__/ _ \/ _` / _` |-|_| |_|_|_||_|_|____\___/\__,_\__,_|-"#;- /// The main function running after the early init. fn kernel_main() -> ! {- use bsp::console::console;- use console::interface::All;+ use core::time::Duration;+ use driver::interface::DriverManager;+ use time::interface::TimeManager;
- println!("{}", MINILOAD_LOGO);- println!("{:^37}", bsp::board_name());- println!();- println!("[ML] Requesting binary");- console().flush();-- // Discard any spurious received characters before starting with the loader protocol.- console().clear_rx();-- // Notify `Minipush` to send the binary.- for _ in 0..3 {- console().write_char(3 as char);+ info!(+ "{} version {}",+ env!("CARGO_PKG_NAME"),+ env!("CARGO_PKG_VERSION")+ );+ info!("Booting on: {}", bsp::board_name());++ info!(+ "Architectural timer resolution: {} ns",+ time::time_manager().resolution().as_nanos()+ );++ info!("Drivers loaded:");+ for (i, driver) in bsp::driver::driver_manager()+ .all_device_drivers()+ .iter()+ .enumerate()+ {+ info!(" {}. {}", i + 1, driver.compatible()); }
- // 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;- unsafe {- // Read the kernel byte by byte.- for i in 0..size {- core::ptr::write_volatile(kernel_addr.offset(i as isize), console().read_char() as u8)- }- }+ // Test a failing timer case.+ time::time_manager().spin_for(Duration::from_nanos(1));
- println!("[ML] Loaded! Executing the payload now\n");- console().flush();-- // Use black magic to create a function pointer.- let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) };-- // Jump to loaded kernel!- kernel()+ loop {+ info!("Spinning for 1 second");+ time::time_manager().spin_for(Duration::from_secs(1));+ } }
diff -uNr 06_uart_chainloader/src/print.rs 07_timestamps/src/print.rs--- 06_uart_chainloader/src/print.rs+++ 07_timestamps/src/print.rs@@ -36,3 +36,71 @@ $crate::print::_print(format_args_nl!($($arg)*)); }) }++/// Prints an info, with a newline.+#[macro_export]+macro_rules! info {+ ($string:expr) => ({+ #[allow(unused_imports)]+ use crate::time::interface::TimeManager;++ let timestamp = $crate::time::time_manager().uptime();+ let timestamp_subsec_us = timestamp.subsec_micros();++ $crate::print::_print(format_args_nl!(+ concat!("[ {:>3}.{:03}{:03}] ", $string),+ timestamp.as_secs(),+ timestamp_subsec_us / 1_000,+ timestamp_subsec_us modulo 1_000+ ));+ });+ ($format_string:expr, $($arg:tt)*) => ({+ #[allow(unused_imports)]+ use crate::time::interface::TimeManager;++ let timestamp = $crate::time::time_manager().uptime();+ let timestamp_subsec_us = timestamp.subsec_micros();++ $crate::print::_print(format_args_nl!(+ concat!("[ {:>3}.{:03}{:03}] ", $format_string),+ timestamp.as_secs(),+ timestamp_subsec_us / 1_000,+ timestamp_subsec_us modulo 1_000,+ $($arg)*+ ));+ })+}++/// Prints a warning, with a newline.+#[macro_export]+macro_rules! warn {+ ($string:expr) => ({+ #[allow(unused_imports)]+ use crate::time::interface::TimeManager;++ let timestamp = $crate::time::time_manager().uptime();+ let timestamp_subsec_us = timestamp.subsec_micros();++ $crate::print::_print(format_args_nl!(+ concat!("[W {:>3}.{:03}{:03}] ", $string),+ timestamp.as_secs(),+ timestamp_subsec_us / 1_000,+ timestamp_subsec_us modulo 1_000+ ));+ });+ ($format_string:expr, $($arg:tt)*) => ({+ #[allow(unused_imports)]+ use crate::time::interface::TimeManager;++ let timestamp = $crate::time::time_manager().uptime();+ let timestamp_subsec_us = timestamp.subsec_micros();++ $crate::print::_print(format_args_nl!(+ concat!("[W {:>3}.{:03}{:03}] ", $format_string),+ timestamp.as_secs(),+ timestamp_subsec_us / 1_000,+ timestamp_subsec_us modulo 1_000,+ $($arg)*+ ));+ })+}
diff -uNr 06_uart_chainloader/src/time.rs 07_timestamps/src/time.rs--- 06_uart_chainloader/src/time.rs+++ 07_timestamps/src/time.rs@@ -0,0 +1,37 @@+// SPDX-License-Identifier: MIT OR Apache-2.0+//+// Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>++//! Timer primitives.++#[cfg(target_arch = "aarch64")]+#[path = "_arch/aarch64/time.rs"]+mod arch_time;++//--------------------------------------------------------------------------------------------------+// Architectural Public Reexports+//--------------------------------------------------------------------------------------------------+pub use arch_time::time_manager;++//--------------------------------------------------------------------------------------------------+// Public Definitions+//--------------------------------------------------------------------------------------------------++/// Timekeeping interfaces.+pub mod interface {+ use core::time::Duration;++ /// Time management functions.+ pub trait TimeManager {+ /// The timer's resolution.+ fn resolution(&self) -> Duration;++ /// The uptime since power-on of the device.+ ///+ /// This includes time consumed by firmware and bootloaders.+ fn uptime(&self) -> Duration;++ /// Spin for a given duration.+ fn spin_for(&self, duration: Duration);+ }+}
diff -uNr 06_uart_chainloader/tests/boot_test_string.rb 07_timestamps/tests/boot_test_string.rb--- 06_uart_chainloader/tests/boot_test_string.rb+++ 07_timestamps/tests/boot_test_string.rb@@ -0,0 +1,3 @@+# frozen_string_literal: true++EXPECTED_PRINT = 'Spinning for 1 second'
diff -uNr 06_uart_chainloader/tests/chainboot_test.rb 07_timestamps/tests/chainboot_test.rb--- 06_uart_chainloader/tests/chainboot_test.rb+++ 07_timestamps/tests/chainboot_test.rb@@ -1,80 +0,0 @@-# frozen_string_literal: true--# SPDX-License-Identifier: MIT OR Apache-2.0-#-# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>--require_relative '../../common/serial/minipush'-require_relative '../../common/tests/boot_test'-require 'pty'--# Match for the last print that 'demo_payload_rpiX.img' produces.-EXPECTED_PRINT = 'Echoing input now'--# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected-# to a QEMU instance instead of a real HW.-class ChainbootTest < BootTest- MINIPUSH = '../common/serial/minipush.rb'- MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now'-- def initialize(qemu_cmd, payload_path)- super(qemu_cmd, EXPECTED_PRINT)-- @test_name = 'Boot test using Minipush'-- @payload_path = payload_path- end-- private-- # override- def post_process_and_add_output(output)- temp = output.join.split("\r\n")-- # Should a line have solo carriage returns, remove any overridden parts of the string.- temp.map! { |x| x.gsub(/.*\r/, '') }-- @test_output += temp- end-- def wait_for_minipush_power_request(mp_out)- output = []- Timeout.timeout(MAX_WAIT_SECS) do- loop do- output << mp_out.gets- break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST)- end- end- rescue Timeout::Error- @test_error = 'Timed out waiting for power request'- rescue StandardError => e- @test_error = e.message- ensure- post_process_and_add_output(output)- end-- # override- def setup- pty_main, pty_secondary = PTY.open- mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}")-- # Wait until MiniPush asks for powering the target.- wait_for_minipush_power_request(mp_out)-- # Now is the time to start QEMU with the chainloader binary. QEMU's virtual tty is connected- # to the MiniPush instance spawned above, so that the two processes talk to each other.- Process.spawn(@qemu_cmd, in: pty_main, out: pty_main)-- # The remainder of the test is done by the parent class' run_concrete_test, which listens on- # @qemu_serial. Hence, point it to MiniPush's output.- @qemu_serial = mp_out- end-end--##---------------------------------------------------------------------------------------------------## Execution starts here-##---------------------------------------------------------------------------------------------------payload_path = ARGV.pop-qemu_cmd = ARGV.join(' ')--ChainbootTest.new(qemu_cmd, payload_path).run
diff -uNr 06_uart_chainloader/update.sh 07_timestamps/update.sh--- 06_uart_chainloader/update.sh+++ 07_timestamps/update.sh@@ -1,8 +0,0 @@-#!/usr/bin/env bash--cd ../05_drivers_gpio_uart-BSP=rpi4 make-cp kernel8.img ../06_uart_chainloader/demo_payload_rpi4.img-make-cp kernel8.img ../06_uart_chainloader/demo_payload_rpi3.img-rm kernel8.img 使用Rust在树莓派上编写操作系统 - 07 - 时间戳
https://zlotus.github.io/2021/12/29/writing-os-in-rust-on-rpi-07/