From 626455d5a78b32670a09c9354fc80b1433fe7a5d Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Thu, 9 Jan 2025 01:56:08 +0800 Subject: [PATCH] Add crate axhal_plat & axhal_plat_macros --- .github/workflows/ci.yml | 6 +- Cargo.toml | 10 ++- axhal_plat/Cargo.toml | 19 +++++ axhal_plat/README.md | 3 + axhal_plat/src/console.rs | 45 ++++++++++ axhal_plat/src/init.rs | 11 +++ axhal_plat/src/irq.rs | 30 +++++++ axhal_plat/src/lib.rs | 35 ++++++++ axhal_plat/src/mem.rs | 81 ++++++++++++++++++ axhal_plat/src/power.rs | 14 ++++ axhal_plat/src/time.rs | 73 ++++++++++++++++ axhal_plat_macros/Cargo.toml | 23 +++++ axhal_plat_macros/src/lib.rs | 157 +++++++++++++++++++++++++++++++++++ 13 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 axhal_plat/Cargo.toml create mode 100644 axhal_plat/README.md create mode 100644 axhal_plat/src/console.rs create mode 100644 axhal_plat/src/init.rs create mode 100644 axhal_plat/src/irq.rs create mode 100644 axhal_plat/src/lib.rs create mode 100644 axhal_plat/src/mem.rs create mode 100644 axhal_plat/src/power.rs create mode 100644 axhal_plat/src/time.rs create mode 100644 axhal_plat_macros/Cargo.toml create mode 100644 axhal_plat_macros/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69ccb18..2273544 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,15 +37,13 @@ jobs: contents: write env: default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs + RUSTDOCFLAGS: -Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - name: Build docs continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html + run: cargo doc --no-deps --all-features - name: Deploy to Github Pages if: ${{ github.ref == env.default-branch }} uses: JamesIves/github-pages-deploy-action@v4 diff --git a/Cargo.toml b/Cargo.toml index ef46c10..eb96103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,19 @@ resolver = "2" members = [ "axhal_cpu", + "axhal_plat", + "axhal_plat_macros", ] [workspace.package] edition = "2024" -authors = ["Yuekai Jia "] +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "Su Mingxian ", + "RobertYuan <634954435@qq.com>", + "hky1999 ", +] license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" homepage = "https://github.com/arceos-org/arceos" documentation = "https://arceos-org.github.io/axhal_crates" diff --git a/axhal_plat/Cargo.toml b/axhal_plat/Cargo.toml new file mode 100644 index 0000000..b20152a --- /dev/null +++ b/axhal_plat/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "axhal_plat" +version = "0.1.0" +description = "This crate defines unified interfaces for various hardware platforms." +documentation = "https://docs.rs/axhal_plat" +keywords = ["arceos", "hal", "hardware-abstraction-layer"] +categories = ["embedded", "no-std", "hardware-support", "os"] +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +memory_addr = "0.3" +bitflags = "2.6" +crate_interface = "0.1" +handler_table = "0.1" +axhal_plat_macros = { path = "../axhal_plat_macros", version = "0.1.0" } diff --git a/axhal_plat/README.md b/axhal_plat/README.md new file mode 100644 index 0000000..ff3d58d --- /dev/null +++ b/axhal_plat/README.md @@ -0,0 +1,3 @@ +# axhal-plat + +This crate defines unified interfaces for various hardware platforms. diff --git a/axhal_plat/src/console.rs b/axhal_plat/src/console.rs new file mode 100644 index 0000000..5db1ae7 --- /dev/null +++ b/axhal_plat/src/console.rs @@ -0,0 +1,45 @@ +//! Console input and output. + +use core::fmt::{Arguments, Result, Write}; + +/// Console input and output interface. +#[def_plat_interface] +pub trait ConsoleIf { + /// Writes bytes to the console from input u8 slice. + fn write_bytes(bytes: &[u8]); + + /// Reads bytes from the console into the given mutable slice. + /// Returns the number of bytes read. + fn read_bytes(bytes: &mut [u8]) -> usize; +} + +struct EarlyConsole; + +impl Write for EarlyConsole { + fn write_str(&mut self, s: &str) -> Result { + write_bytes(s.as_bytes()); + Ok(()) + } +} + +/// Simple console print operation. +#[macro_export] +macro_rules! console_print { + ($($arg:tt)*) => { + $crate::console::__simple_print(format_args!($($arg)*)); + } +} + +/// Simple console print operation, with a newline. +#[macro_export] +macro_rules! console_println { + () => { $crate::ax_print!("\n") }; + ($($arg:tt)*) => { + $crate::console::__simple_print(format_args!("{}\n", format_args!($($arg)*))); + } +} + +#[doc(hidden)] +pub fn __simple_print(fmt: Arguments) { + EarlyConsole.write_fmt(fmt).unwrap(); +} diff --git a/axhal_plat/src/init.rs b/axhal_plat/src/init.rs new file mode 100644 index 0000000..3af7b3f --- /dev/null +++ b/axhal_plat/src/init.rs @@ -0,0 +1,11 @@ +//! Platform initialization. + +/// Platform initialization interface. +#[def_plat_interface] +pub trait InitIf { + /// Initializes the platform devices for the primary CPU. + fn platform_init(); + + /// Initializes the platform devices for secondary CPUs. + fn platform_init_secondary(); +} diff --git a/axhal_plat/src/irq.rs b/axhal_plat/src/irq.rs new file mode 100644 index 0000000..bf5b3d4 --- /dev/null +++ b/axhal_plat/src/irq.rs @@ -0,0 +1,30 @@ +//! Interrupt request (IRQ) handling. + +pub use handler_table::HandlerTable; + +/// The type if an IRQ handler. +pub type IrqHandler = handler_table::Handler; + +/// IRQ management interface. +#[def_plat_interface] +pub trait IrqIf { + /// Enables or disables the given IRQ. + fn set_enable(vector: usize, enabled: bool); + + /// Registers an IRQ handler for the given IRQ. + /// + /// Returns `true` if the handler is successfully registered. + fn register(vector: usize, handler: IrqHandler) -> bool; + + /// Unregisters the IRQ handler for the given IRQ. + /// + /// Returns the existing handler if it is registered, `None` otherwise. + fn unregister(vector: usize) -> Option; + + /// Handles the IRQ. + /// + /// This function is called by the common interrupt handler. It should look + /// up in the IRQ handler table and calls the corresponding handler. If + /// necessary, it also acknowledges the interrupt controller after handling. + fn handle(vector: usize); +} diff --git a/axhal_plat/src/lib.rs b/axhal_plat/src/lib.rs new file mode 100644 index 0000000..021109d --- /dev/null +++ b/axhal_plat/src/lib.rs @@ -0,0 +1,35 @@ +#![no_std] +#![doc = include_str!("../README.md")] + +#[macro_use] +extern crate axhal_plat_macros; + +pub mod console; +pub mod init; +pub mod irq; +pub mod mem; +pub mod power; +pub mod time; + +pub use axhal_plat_macros::{main, secondary_main}; +pub use crate_interface::impl_interface as impl_plat_interface; + +#[doc(hidden)] +pub mod __priv { + pub use crate_interface::{call_interface, def_interface}; +} + +/// Call the function decorated by [`crate::main`] for the primary core. +pub fn call_main(cpu_hw_id: usize, dtb: usize) -> ! { + unsafe { __axhal_plat_main(cpu_hw_id, dtb) } +} + +/// Call the function decorated by [`crate::secondary_main`] for secondary cores. +pub fn call_secondary_main(cpu_hw_id: usize) -> ! { + unsafe { __axhal_plat_secondary_main(cpu_hw_id) } +} + +unsafe extern "Rust" { + fn __axhal_plat_main(cpu_hw_id: usize, dtb: usize) -> !; + fn __axhal_plat_secondary_main(cpu_hw_id: usize) -> !; +} diff --git a/axhal_plat/src/mem.rs b/axhal_plat/src/mem.rs new file mode 100644 index 0000000..0444cb8 --- /dev/null +++ b/axhal_plat/src/mem.rs @@ -0,0 +1,81 @@ +//! Physical memory information. + +use core::fmt; + +use memory_addr::{PhysAddr, PhysAddrRange}; + +bitflags::bitflags! { + /// The flags of a physical memory region. + #[derive(Clone, Copy)] + pub struct MemRegionFlags: usize { + /// Readable. + const READ = 1 << 0; + /// Writable. + const WRITE = 1 << 1; + /// Executable. + const EXECUTE = 1 << 2; + /// Device memory. (e.g., MMIO regions) + const DEVICE = 1 << 4; + /// Uncachable memory. (e.g., framebuffer) + const UNCACHED = 1 << 5; + /// Reserved memory, do not use for allocation. + const RESERVED = 1 << 6; + /// Free memory for allocation. + const FREE = 1 << 7; + } +} + +impl fmt::Debug for MemRegionFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +/// A physical memory region. +#[derive(Debug, Clone, Copy)] +pub struct PhysMemRegion { + /// The start physical address of the region. + pub paddr: PhysAddr, + /// The size in bytes of the region. + pub size: usize, + /// The region flags, see [`MemRegionFlags`]. + pub flags: MemRegionFlags, + /// The region name, used for identification. + pub name: &'static str, +} + +impl PhysMemRegion { + /// Returns a [`PhysAddrRange`] that represents its physical address range. + pub fn pa_range(&self) -> PhysAddrRange { + PhysAddrRange::from_start_size(self.paddr, self.size) + } +} + +/// Fills the `.bss` section with zeros. +/// +/// It requires the symbols `_sbss` and `_ebss` to be defined in the linker script. +/// +/// # Safety +/// +/// This function is unsafe because it writes `.bss` section directly. +pub unsafe fn clear_bss() { + unsafe { + core::slice::from_raw_parts_mut(_sbss as usize as *mut u8, _ebss as usize - _sbss as usize) + .fill(0); + } +} + +unsafe extern "C" { + fn _sbss(); + fn _ebss(); +} + +/// Physical memory interface. +#[def_plat_interface] +pub trait MemIf { + /// Returns all normal memory (RAM) regions on the platform. + fn ram_regions() -> &'static [PhysMemRegion]; + + /// Returns all device memory (MMIO) regions on the platform. + fn mmio_regions() -> &'static [PhysMemRegion]; +} diff --git a/axhal_plat/src/power.rs b/axhal_plat/src/power.rs new file mode 100644 index 0000000..ea3f236 --- /dev/null +++ b/axhal_plat/src/power.rs @@ -0,0 +1,14 @@ +//! Power management. + +/// Power management interface. +#[def_plat_interface] +pub trait PowerIf { + /// Bootstraps the given CPU with the given initial stack (in physical address). + /// + /// Where `cpu_id` is the logical CPU ID (0, 1, ..., N-1, N is the number of + /// CPU cores on the platform). + fn cpu_boot(cpu_id: usize, stack_top_paddr: usize); + + /// Shutdown the whole system, including all CPUs. + fn system_off() -> !; +} diff --git a/axhal_plat/src/time.rs b/axhal_plat/src/time.rs new file mode 100644 index 0000000..bd013d5 --- /dev/null +++ b/axhal_plat/src/time.rs @@ -0,0 +1,73 @@ +//! Time-related operations. + +pub use core::time::Duration; + +/// A measurement of the system clock. +/// +/// Currently, it reuses the [`core::time::Duration`] type. But it does not +/// represent a duration, but a clock time. +pub type TimeValue = Duration; + +/// Number of milliseconds in a second. +pub const MILLIS_PER_SEC: u64 = 1_000; +/// Number of microseconds in a second. +pub const MICROS_PER_SEC: u64 = 1_000_000; +/// Number of nanoseconds in a second. +pub const NANOS_PER_SEC: u64 = 1_000_000_000; +/// Number of nanoseconds in a millisecond. +pub const NANOS_PER_MILLIS: u64 = 1_000_000; +/// Number of nanoseconds in a microsecond. +pub const NANOS_PER_MICROS: u64 = 1_000; + +/// Time-related interfaces. +#[def_plat_interface] +pub trait TimeIf { + /// Returns the current clock time in hardware ticks. + fn current_ticks() -> u64; + + /// Converts hardware ticks to nanoseconds. + fn ticks_to_nanos(ticks: u64) -> u64; + + /// Converts nanoseconds to hardware ticks. + fn nanos_to_ticks(nanos: u64) -> u64; + + /// Return epoch offset in nanoseconds (wall time offset to monotonic clock start). + fn epochoffset_nanos() -> u64; + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the specified monotonic time deadline (in nanoseconds). + fn set_oneshot_timer(deadline_ns: u64); +} + +/// Returns nanoseconds elapsed since system boot. +pub fn monotonic_time_nanos() -> u64 { + ticks_to_nanos(current_ticks()) +} + +/// Returns the time elapsed since system boot in [`TimeValue`]. +pub fn monotonic_time() -> TimeValue { + TimeValue::from_nanos(monotonic_time_nanos()) +} + +/// Returns nanoseconds elapsed since epoch (also known as realtime). +pub fn wall_time_nanos() -> u64 { + monotonic_time_nanos() + epochoffset_nanos() +} + +/// Returns the time elapsed since epoch (also known as realtime) in [`TimeValue`]. +pub fn wall_time() -> TimeValue { + TimeValue::from_nanos(monotonic_time_nanos() + epochoffset_nanos()) +} + +/// Busy waiting for the given duration. +pub fn busy_wait(dur: Duration) { + busy_wait_until(wall_time() + dur); +} + +/// Busy waiting until reaching the given deadline. +pub fn busy_wait_until(deadline: TimeValue) { + while wall_time() < deadline { + core::hint::spin_loop(); + } +} diff --git a/axhal_plat_macros/Cargo.toml b/axhal_plat_macros/Cargo.toml new file mode 100644 index 0000000..4d2134e --- /dev/null +++ b/axhal_plat_macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "axhal_plat_macros" +version = "0.1.0" +description = "Unified operations for various hardware platforms" +documentation = "https://docs.rs/axhal_plat_macros" +keywords = ["arceos", "hal", "hardware-abstraction-layer", "macros"] +categories = ["development-tools::procedural-macro-helpers"] +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } + +[dev-dependencies] +crate_interface = "0.1" + +[lib] +proc-macro = true diff --git a/axhal_plat_macros/src/lib.rs b/axhal_plat_macros/src/lib.rs new file mode 100644 index 0000000..0695782 --- /dev/null +++ b/axhal_plat_macros/src/lib.rs @@ -0,0 +1,157 @@ +//! Macros to define and access a per-CPU data structure. + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{Error, FnArg, ItemFn, ItemTrait, ReturnType, TraitItem}; + +fn compiler_error(err: Error) -> TokenStream { + err.to_compile_error().into() +} + +fn common_main(item: TokenStream, arg_num: usize, export_name: &str, err_msg: &str) -> TokenStream { + let main = syn::parse_macro_input!(item as ItemFn); + let mut err = if let ReturnType::Type(_, ty) = &main.sig.output { + quote! { #ty }.to_string() != "!" + } else { + true + }; + + let args = &main.sig.inputs; + for arg in args.iter() { + if let FnArg::Typed(pat) = arg { + let ty = &pat.ty; + if quote! { #ty }.to_string() != "usize" { + err = true; + break; + } + } + } + if args.len() != arg_num { + err = true; + } + + if err { + compiler_error(Error::new(Span::call_site(), err_msg)) + } else { + quote! { + #[unsafe(export_name = #export_name)] + #main + } + .into() + } +} + +/// Marks a function to be called on the primary core after the platform +/// initialization. +/// +/// The function signature must be `fn(cpu_id: usize, dtb: usize) -> !`, where +/// `cpu_id` is the logical CPU ID (0, 1, ..., N-1, N is the number of CPU +/// cores on the platform). +/// +/// # Example +/// +/// ```rust +/// # use axhal_plat_macros as axhal_plat; +/// #[axhal_plat::main] +/// fn primary_main(cpu_id: usize, dtb: usize) -> ! { +/// todo!() // Your code here +/// } +#[proc_macro_attribute] +pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute or `#[axhal_plat::main]`", + )); + }; + common_main( + item, + 2, + "__axhal_plat_main", + "expect a function with type `fn(cpu_id: usize, dtb: usize) -> !`", + ) +} + +/// Marks a function to be called on the secondary cores after the platform +/// initialization. +/// +/// The function signature must be `fn(cpu_id: usize) -> !`, where `cpu_id` is +/// the logical CPU ID (0, 1, ..., N-1, N is the number of CPU cores on the +/// platform). +/// +/// # Example +/// +/// ```rust +/// # use axhal_plat_macros as axhal_plat; +/// #[axhal_plat::secondary_main] +/// fn secondary_main(cpu_id: usize) -> ! { +/// todo!() // Your code here +/// } +#[proc_macro_attribute] +pub fn secondary_main(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute or `#[axhal_plat::secondary_main]`", + )); + }; + common_main( + item, + 1, + "__axhal_plat_secondary_main", + "expect a function with type `fn(cpu_id: usize) -> !`", + ) +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn def_plat_interface(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute: `#[def_plat_interface]`", + )); + } + + let trait_ast = syn::parse_macro_input!(item as ItemTrait); + let trait_name = &trait_ast.ident; + + let mut fn_list = vec![]; + for item in &trait_ast.items { + if let TraitItem::Fn(method) = item { + let attrs = &method.attrs; + let sig = &method.sig; + let fn_name = &sig.ident; + + let mut args = vec![]; + for arg in &sig.inputs { + match arg { + FnArg::Receiver(_) => { + return compiler_error(Error::new_spanned( + arg, + "`self` is not allowed in the interface definition", + )); + } + FnArg::Typed(ty) => args.push(ty.pat.clone()), + } + } + + fn_list.push(quote! { + #(#attrs)* + #[inline] + pub #sig { + crate::__priv::call_interface!(#trait_name::#fn_name, #(#args),* ) + } + }); + } + } + + quote! { + #[crate::__priv::def_interface] + #trait_ast + + #(#fn_list)* + } + .into() +}