From cc2679d029e13ecea379a46087b9219765d1a5af Mon Sep 17 00:00:00 2001 From: elliott10 Date: Tue, 22 Aug 2023 17:44:41 +0800 Subject: [PATCH] Supports car automotive platform bsta1000b Build cmd: make A=apps/fs/shell ARCH=aarch64 SMP=8 PLATFORM=aarch64-bsta1000b APP_FEATURES="axstd/driver-ramdisk,axstd/irq" fada --- .github/workflows/build.yml | 3 + .gitignore | 1 + Cargo.lock | 8 ++ Cargo.toml | 1 + Makefile | 2 + crates/arm_gic/src/lib.rs | 44 +++++++ crates/dw_apb_uart/Cargo.toml | 13 ++ crates/dw_apb_uart/src/lib.rs | 118 ++++++++++++++++++ modules/axhal/Cargo.toml | 1 + modules/axhal/src/lib.rs | 1 + .../platform/aarch64_bsta1000b/dw_apb_uart.rs | 44 +++++++ .../src/platform/aarch64_bsta1000b/mem.rs | 33 +++++ .../src/platform/aarch64_bsta1000b/misc.rs | 60 +++++++++ .../src/platform/aarch64_bsta1000b/mod.rs | 62 +++++++++ .../src/platform/aarch64_bsta1000b/mp.rs | 23 ++++ .../axhal/src/platform/aarch64_common/gic.rs | 8 +- .../axhal/src/platform/aarch64_common/mod.rs | 5 +- .../src/platform/aarch64_common/pl011.rs | 2 +- .../axhal/src/platform/aarch64_common/psci.rs | 113 +++++++++++++++-- .../axhal/src/platform/aarch64_raspi/mod.rs | 7 +- modules/axhal/src/platform/mod.rs | 3 + modules/axruntime/src/mp.rs | 4 +- platforms/aarch64-bsta1000b.toml | 54 ++++++++ platforms/aarch64-qemu-virt.toml | 5 +- platforms/aarch64-raspi4.toml | 2 +- scripts/make/bsta1000b-fada.mk | 4 + scripts/make/raspi4.mk | 5 +- tools/bsta1000b/bsta1000b-fada-arceos.its | 50 ++++++++ tools/bsta1000b/bsta1000b-fada.dtb | Bin 0 -> 59621 bytes 29 files changed, 651 insertions(+), 25 deletions(-) create mode 100644 crates/dw_apb_uart/Cargo.toml create mode 100644 crates/dw_apb_uart/src/lib.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mem.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/misc.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mod.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mp.rs create mode 100644 platforms/aarch64-bsta1000b.toml create mode 100644 scripts/make/bsta1000b-fada.mk create mode 100755 tools/bsta1000b/bsta1000b-fada-arceos.its create mode 100644 tools/bsta1000b/bsta1000b-fada.dtb diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4472f73221..70f3747579 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,6 +132,9 @@ jobs: - name: Build fs/shell for aarch64-raspi4 run: make PLATFORM=aarch64-raspi4 A=apps/fs/shell FEATURES=driver-bcm2835-sdhci + - name: Build helloworld for aarch64-bsta1000b + run: make PLATFORM=aarch64-bsta1000b A=apps/helloworld + build-apps-for-std: runs-on: ${{ matrix.os }} strategy: diff --git a/.gitignore b/.gitignore index 1ae218476f..9d91ac5720 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.img actual.out qemu.log +rusty-tags.vi diff --git a/Cargo.lock b/Cargo.lock index 6219f64a46..c1929b9403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,7 @@ dependencies = [ "bitflags 2.3.3", "cfg-if", "crate_interface", + "dw_apb_uart", "handler_table", "kernel_guard", "lazy_init", @@ -959,6 +960,13 @@ dependencies = [ "virtio-drivers", ] +[[package]] +name = "dw_apb_uart" +version = "0.1.0" +dependencies = [ + "tock-registers", +] + [[package]] name = "either" version = "1.9.0" diff --git a/Cargo.toml b/Cargo.toml index e1491ef042..7b77147621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/allocator", "crates/arm_gic", "crates/arm_pl011", + "crates/dw_apb_uart", "crates/axerrno", "crates/axfs_devfs", "crates/axfs_ramfs", diff --git a/Makefile b/Makefile index ba7a60ef18..b351596584 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,8 @@ include scripts/make/qemu.mk include scripts/make/test.mk ifeq ($(PLATFORM_NAME), aarch64-raspi4) include scripts/make/raspi4.mk +else ifeq ($(PLATFORM_NAME), aarch64-bsta1000b) + include scripts/make/bsta1000b-fada.mk endif build: $(OUT_DIR) $(OUT_BIN) diff --git a/crates/arm_gic/src/lib.rs b/crates/arm_gic/src/lib.rs index 6e8ac25ff0..56e3578732 100644 --- a/crates/arm_gic/src/lib.rs +++ b/crates/arm_gic/src/lib.rs @@ -45,3 +45,47 @@ pub enum TriggerMode { /// level is active, and deasserted whenever the level is not active. Level = 1, } + +/// Different types of interrupt that the GIC handles. +pub enum InterruptType { + /// Software-generated interrupt. + /// + /// SGIs are typically used for inter-processor communication and are + /// generated by a write to an SGI register in the GIC. + SGI, + /// Private Peripheral Interrupt. + /// + /// Peripheral interrupts that are private to one core. + PPI, + /// Shared Peripheral Interrupt. + /// + /// Peripheral interrupts that can delivered to any connected core. + SPI, +} + +/// Translate an interrupt of a given type to a GIC INTID. +pub const fn translate_irq(id: usize, int_type: InterruptType) -> Option { + match int_type { + InterruptType::SGI => { + if id < SGI_RANGE.end { + Some(id) + } else { + None + } + } + InterruptType::PPI => { + if id < PPI_RANGE.end - PPI_RANGE.start { + Some(id + PPI_RANGE.start) + } else { + None + } + } + InterruptType::SPI => { + if id < SPI_RANGE.end - SPI_RANGE.start { + Some(id + SPI_RANGE.start) + } else { + None + } + } + } +} diff --git a/crates/dw_apb_uart/Cargo.toml b/crates/dw_apb_uart/Cargo.toml new file mode 100644 index 0000000000..503e44baa5 --- /dev/null +++ b/crates/dw_apb_uart/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dw_apb_uart" +authors = ["Luoyuan Xiao "] +version = "0.1.0" +edition = "2021" +license = "GPL-2.0" +repository = "https://github.com/elliott10/arceos/tree/main/crates/dw_apb_uart" +description = "Uart snps,dw-apb-uart driver in Rust for BST A1000b FADA board." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tock-registers = "0.8" diff --git a/crates/dw_apb_uart/src/lib.rs b/crates/dw_apb_uart/src/lib.rs new file mode 100644 index 0000000000..069f079617 --- /dev/null +++ b/crates/dw_apb_uart/src/lib.rs @@ -0,0 +1,118 @@ +//! Definitions for snps,dw-apb-uart serial driver. +//! Uart snps,dw-apb-uart driver in Rust for BST A1000b FADA board. +#![no_std] + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite}, +}; + +register_structs! { + DW8250Regs { + /// Get or Put Register. + (0x00 => rbr: ReadWrite), + (0x04 => ier: ReadWrite), + (0x08 => fcr: ReadWrite), + (0x0c => lcr: ReadWrite), + (0x10 => mcr: ReadWrite), + (0x14 => lsr: ReadOnly), + (0x18 => msr: ReadOnly), + (0x1c => scr: ReadWrite), + (0x20 => lpdll: ReadWrite), + (0x24 => _reserved0), + /// Uart Status Register. + (0x7c => usr: ReadOnly), + (0x80 => _reserved1), + (0xc0 => dlf: ReadWrite), + (0xc4 => @END), + } +} + +/// dw-apb-uart serial driver: DW8250 +pub struct DW8250 { + base_vaddr: usize, +} + +impl DW8250 { + /// New a DW8250 + pub const fn new(base_vaddr: usize) -> Self { + Self { base_vaddr } + } + + const fn regs(&self) -> &DW8250Regs { + unsafe { &*(self.base_vaddr as *const _) } + } + + /// DW8250 initialize + pub fn init(&mut self) { + const UART_SRC_CLK: u32 = 25000000; + const BST_UART_DLF_LEN: u32 = 6; + const BAUDRATE: u32 = 115200; + //const BAUDRATE: u32 = 38400; + + let get_baud_divider = |baudrate| (UART_SRC_CLK << (BST_UART_DLF_LEN - 4)) / baudrate; + let divider = get_baud_divider(BAUDRATE); + + // Waiting to be no USR_BUSY. + while self.regs().usr.get() & 0b1 != 0 {} + + // bst_serial_hw_init_clk_rst + + /* Disable interrupts and Enable FIFOs */ + self.regs().ier.set(0); + self.regs().fcr.set(1); + + /* Disable flow ctrl */ + self.regs().mcr.set(0); + + /* Clear MCR_RTS */ + self.regs().mcr.set(self.regs().mcr.get() | (1 << 1)); + + /* Enable access DLL & DLH. Set LCR_DLAB */ + self.regs().lcr.set(self.regs().lcr.get() | (1 << 7)); + + /* Set baud rate. Set DLL, DLH, DLF */ + self.regs().rbr.set((divider >> BST_UART_DLF_LEN) & 0xff); + self.regs() + .ier + .set((divider >> (BST_UART_DLF_LEN + 8)) & 0xff); + self.regs().dlf.set(divider & ((1 << BST_UART_DLF_LEN) - 1)); + + /* Clear DLAB bit */ + self.regs().lcr.set(self.regs().lcr.get() & !(1 << 7)); + + /* Set data length to 8 bit, 1 stop bit, no parity. Set LCR_WLS1 | LCR_WLS0 */ + self.regs().lcr.set(self.regs().lcr.get() | 0b11); + } + + /// DW8250 serial output + pub fn putchar(&mut self, c: u8) { + // Check LSR_TEMT + // Wait for last character to go. + while self.regs().lsr.get() & (1 << 6) == 0 {} + self.regs().rbr.set(c as u32); + } + + /// DW8250 serial input + pub fn getchar(&mut self) -> Option { + // Check LSR_DR + // Wait for a character to arrive. + if self.regs().lsr.get() & 0b1 != 0 { + Some((self.regs().rbr.get() & 0xff) as u8) + } else { + None + } + } + + /// DW8250 serial interrupt enable or disable + pub fn set_ier(&mut self, enable: bool) { + if enable { + // Enable interrupts + self.regs().ier.set(1); + } else { + // Disable interrupts + self.regs().ier.set(0); + } + } +} diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml index 6b797f0237..f29ecde07d 100644 --- a/modules/axhal/Cargo.toml +++ b/modules/axhal/Cargo.toml @@ -52,6 +52,7 @@ aarch64-cpu = "9.3" tock-registers = "0.8" arm_gic = { path = "../../crates/arm_gic" } arm_pl011 = { path = "../../crates/arm_pl011" } +dw_apb_uart = { path = "../../crates/dw_apb_uart" } [build-dependencies] axconfig = { path = "../axconfig" } diff --git a/modules/axhal/src/lib.rs b/modules/axhal/src/lib.rs index 20636d2995..9fd27a1b1f 100644 --- a/modules/axhal/src/lib.rs +++ b/modules/axhal/src/lib.rs @@ -28,6 +28,7 @@ #![feature(asm_const)] #![feature(naked_functions)] #![feature(const_maybe_uninit_zeroed)] +#![feature(const_option)] #![feature(doc_auto_cfg)] #[allow(unused_imports)] diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs b/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs new file mode 100644 index 0000000000..844a3cbb3c --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs @@ -0,0 +1,44 @@ +//! snps,dw-apb-uart serial driver + +use crate::mem::phys_to_virt; +use dw_apb_uart::DW8250; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +const UART_BASE: PhysAddr = PhysAddr::from(axconfig::UART_PADDR); + +static UART: SpinNoIrq = SpinNoIrq::new(DW8250::new(phys_to_virt(UART_BASE).as_usize())); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\r' | b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + UART.lock().getchar() +} + +/// UART simply initialize +pub fn init_early() { + UART.lock().init(); +} + +/// Set UART IRQ Enable +#[cfg(feature = "irq")] +pub fn init_irq() { + UART.lock().set_ier(true); + crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, handle); +} + +/// UART IRQ Handler +pub fn handle() { + trace!("Uart IRQ Handler"); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs new file mode 100644 index 0000000000..e6faa5ef4e --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs @@ -0,0 +1,33 @@ +use crate::mem::{MemRegion, PhysAddr}; +use page_table_entry::{aarch64::A64PTE, GenericPTE, MappingFlags}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions()) +} + +pub(crate) unsafe fn init_boot_page_table( + boot_pt_l0: &mut [A64PTE; 512], + boot_pt_l1: &mut [A64PTE; 512], +) { + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + boot_pt_l0[0] = A64PTE::new_table(PhysAddr::from(boot_pt_l1.as_ptr() as usize)); + // 0x0000_0000_0000..0x0000_4000_0000, 1G block, device memory + boot_pt_l1[0] = A64PTE::new_page( + PhysAddr::from(0), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 1G block, device memory + boot_pt_l1[1] = A64PTE::new_page( + PhysAddr::from(0x40000000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 1G block, normal memory + boot_pt_l1[2] = A64PTE::new_page( + PhysAddr::from(0x80000000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs new file mode 100644 index 0000000000..8954132e19 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs @@ -0,0 +1,60 @@ +pub use crate::platform::aarch64_common::psci::system_off as terminate; + +use crate::mem::phys_to_virt; +use crate::time::{busy_wait, Duration}; +use core::ptr::{read_volatile, write_volatile}; + +/// Do QSPI reset +pub fn reset_qspi() { + // qspi exit 4-byte mode + // exit_4byte_qspi(); + + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + let value = read_volatile(ptr); + trace!("SAFETY CRM RESET CTRL = {:#x}", value); + write_volatile(ptr, value & !(0b11 << 15)); + busy_wait(Duration::from_millis(100)); + + write_volatile(ptr, value | (0b11 << 15)); + busy_wait(Duration::from_millis(100)); + } +} + +/// Do CPU reset +pub fn reset_cpu() { + reset_qspi(); + + //Data Width = 32 + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + write_volatile(ptr, read_volatile(ptr) & !0b1); + } + + loop {} +} + +/// reboot system +#[allow(dead_code)] +pub fn do_reset() { + axlog::ax_println!("resetting ...\n"); + + // wait 50 ms + busy_wait(Duration::from_millis(50)); + + // disable_interrupts(); + + reset_cpu(); + + // NOT REACHED + warn!("NOT REACHED Resetting"); +} + +/// bootmode define bit [27:26], from strap pin +#[allow(dead_code)] +pub fn get_bootmode() -> u32 { + unsafe { + let ptr = phys_to_virt((axconfig::A1000BASE_TOPCRM).into()).as_mut_ptr() as *mut u32; + (ptr.read_volatile() >> 26) & 0x7 + } +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs new file mode 100644 index 0000000000..8c7634bcf7 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs @@ -0,0 +1,62 @@ +mod dw_apb_uart; + +pub mod mem; +pub mod misc; + +#[cfg(feature = "smp")] +pub mod mp; + +#[cfg(feature = "irq")] +pub mod irq { + pub use crate::platform::aarch64_common::gic::*; +} + +pub mod console { + pub use super::dw_apb_uart::*; +} + +pub mod time { + pub use crate::platform::aarch64_common::generic_timer::*; +} + +extern "C" { + fn exception_vector_base(); + fn rust_main(cpu_id: usize, dtb: usize); + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize); +} + +pub(crate) unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + crate::mem::clear_bss(); + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::cpu::init_primary(cpu_id); + dw_apb_uart::init_early(); + super::aarch64_common::generic_timer::init_early(); + rust_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +pub(crate) unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::cpu::init_secondary(cpu_id); + rust_main_secondary(cpu_id); +} + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_primary(); + super::aarch64_common::generic_timer::init_percpu(); + #[cfg(feature = "irq")] + dw_apb_uart::init_irq(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_secondary(); + super::aarch64_common::generic_timer::init_percpu(); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs new file mode 100644 index 0000000000..489f342fa3 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs @@ -0,0 +1,23 @@ +use crate::mem::{virt_to_phys, PhysAddr, VirtAddr}; + +/// Hart number of bsta1000b board +pub const MAX_HARTS: usize = 8; +/// CPU HWID from cpu device tree nodes with "reg" property +pub const CPU_HWID: [usize; MAX_HARTS] = [0x00, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700]; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) { + if cpu_id >= MAX_HARTS { + error!("No support for bsta1000b core {}", cpu_id); + return; + } + extern "C" { + fn _start_secondary(); + } + let entry = virt_to_phys(VirtAddr::from(_start_secondary as usize)); + crate::platform::aarch64_common::psci::cpu_on( + CPU_HWID[cpu_id], + entry.as_usize(), + stack_top.as_usize(), + ); +} diff --git a/modules/axhal/src/platform/aarch64_common/gic.rs b/modules/axhal/src/platform/aarch64_common/gic.rs index 0d0aaa0a89..362a73ec78 100644 --- a/modules/axhal/src/platform/aarch64_common/gic.rs +++ b/modules/axhal/src/platform/aarch64_common/gic.rs @@ -1,5 +1,6 @@ use crate::{irq::IrqHandler, mem::phys_to_virt}; use arm_gic::gic_v2::{GicCpuInterface, GicDistributor}; +use arm_gic::{translate_irq, InterruptType}; use memory_addr::PhysAddr; use spinlock::SpinNoIrq; @@ -7,7 +8,10 @@ use spinlock::SpinNoIrq; pub const MAX_IRQ_COUNT: usize = 1024; /// The timer IRQ number. -pub const TIMER_IRQ_NUM: usize = 30; // physical timer, type=PPI, id=14 +pub const TIMER_IRQ_NUM: usize = translate_irq(14, InterruptType::PPI).unwrap(); + +/// The UART IRQ number. +pub const UART_IRQ_NUM: usize = translate_irq(axconfig::UART_IRQ, InterruptType::SPI).unwrap(); const GICD_BASE: PhysAddr = PhysAddr::from(axconfig::GICD_PADDR); const GICC_BASE: PhysAddr = PhysAddr::from(axconfig::GICC_PADDR); @@ -20,6 +24,7 @@ static GICC: GicCpuInterface = GicCpuInterface::new(phys_to_virt(GICC_BASE).as_m /// Enables or disables the given IRQ. pub fn set_enable(irq_num: usize, enabled: bool) { + trace!("GICD set enable: {} {}", irq_num, enabled); GICD.lock().set_enable(irq_num as _, enabled); } @@ -28,6 +33,7 @@ pub fn set_enable(irq_num: usize, enabled: bool) { /// It also enables the IRQ if the registration succeeds. It returns `false` if /// the registration failed. pub fn register_handler(irq_num: usize, handler: IrqHandler) -> bool { + trace!("register handler irq {}", irq_num); crate::irq::register_handler_common(irq_num, handler) } diff --git a/modules/axhal/src/platform/aarch64_common/mod.rs b/modules/axhal/src/platform/aarch64_common/mod.rs index d6630b122b..c585541fe8 100644 --- a/modules/axhal/src/platform/aarch64_common/mod.rs +++ b/modules/axhal/src/platform/aarch64_common/mod.rs @@ -1,8 +1,11 @@ mod boot; pub mod generic_timer; -pub mod pl011; +#[cfg(not(platform_family = "aarch64-raspi"))] pub mod psci; #[cfg(feature = "irq")] pub mod gic; + +#[cfg(not(platform_family = "aarch64-bsta1000b"))] +pub mod pl011; diff --git a/modules/axhal/src/platform/aarch64_common/pl011.rs b/modules/axhal/src/platform/aarch64_common/pl011.rs index d162ce5c22..e1c9083e79 100644 --- a/modules/axhal/src/platform/aarch64_common/pl011.rs +++ b/modules/axhal/src/platform/aarch64_common/pl011.rs @@ -36,7 +36,7 @@ pub fn init_early() { /// Set UART IRQ Enable pub fn init() { #[cfg(feature = "irq")] - crate::platform::irq::set_enable(axconfig::UART_IRQ_NUM, true); + crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); } /// UART IRQ Handler diff --git a/modules/axhal/src/platform/aarch64_common/psci.rs b/modules/axhal/src/platform/aarch64_common/psci.rs index 09369357a3..feca7ba6db 100644 --- a/modules/axhal/src/platform/aarch64_common/psci.rs +++ b/modules/axhal/src/platform/aarch64_common/psci.rs @@ -1,16 +1,73 @@ +//! ARM Power State Coordination Interface. + #![allow(dead_code)] -//! ARM Power State Coordination Interface. +pub const PSCI_0_2_FN_BASE: u32 = 0x84000000; +pub const PSCI_0_2_64BIT: u32 = 0x40000000; +pub const PSCI_0_2_FN_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + 1; +pub const PSCI_0_2_FN_CPU_OFF: u32 = PSCI_0_2_FN_BASE + 2; +pub const PSCI_0_2_FN_CPU_ON: u32 = PSCI_0_2_FN_BASE + 3; +pub const PSCI_0_2_FN_MIGRATE: u32 = PSCI_0_2_FN_BASE + 5; +pub const PSCI_0_2_FN_SYSTEM_OFF: u32 = PSCI_0_2_FN_BASE + 8; +pub const PSCI_0_2_FN_SYSTEM_RESET: u32 = PSCI_0_2_FN_BASE + 9; +pub const PSCI_0_2_FN64_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 1; +pub const PSCI_0_2_FN64_CPU_ON: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 3; +pub const PSCI_0_2_FN64_MIGRATE: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 5; -use core::arch::asm; +/// PSCI return values, inclusive of all PSCI versions. +#[derive(PartialEq, Debug)] +#[repr(i32)] +pub enum PsciError { + NotSupported = -1, + InvalidParams = -2, + Denied = -3, + AlreadyOn = -4, + OnPending = -5, + InternalFailure = -6, + NotPresent = -7, + Disabled = -8, + InvalidAddress = -9, +} -const PSCI_CPU_ON: u32 = 0x8400_0003; -const PSCI_SYSTEM_OFF: u32 = 0x8400_0008; +impl From for PsciError { + fn from(code: i32) -> PsciError { + use PsciError::*; + match code { + -1 => NotSupported, + -2 => InvalidParams, + -3 => Denied, + -4 => AlreadyOn, + -5 => OnPending, + -6 => InternalFailure, + -7 => NotPresent, + -8 => Disabled, + -9 => InvalidAddress, + _ => panic!("Unknown PSCI error code: {}", code), + } + } +} +/// arm,psci method: smc +/// when SMCCC_CONDUIT_SMC = 1 +fn arm_smccc_smc(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let mut ret; + unsafe { + core::arch::asm!( + "smc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +/// psci "hvc" method call fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { let ret; unsafe { - asm!( + core::arch::asm!( "hvc #0", inlateout("x0") func as usize => ret, in("x1") arg0, @@ -21,22 +78,52 @@ fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { ret } +fn psci_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> Result<(), PsciError> { + let ret = match axconfig::PSCI_METHOD { + "smc" => arm_smccc_smc(func, arg0, arg1, arg2), + "hvc" => psci_hvc_call(func, arg0, arg1, arg2), + _ => panic!("Unknown PSCI method: {}", axconfig::PSCI_METHOD), + }; + if ret == 0 { + Ok(()) + } else { + Err(PsciError::from(ret as i32)) + } +} + /// Shutdown the whole system, including all CPUs. pub fn system_off() -> ! { info!("Shutting down..."); - psci_hvc_call(PSCI_SYSTEM_OFF, 0, 0, 0); + psci_call(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0).ok(); warn!("It should shutdown!"); loop { crate::arch::halt(); } } -/// Starts a secondary CPU with the given ID. +/// Power up a core. This call is used to power up cores that either: +/// +/// * Have not yet been booted into the calling supervisory software. +/// * Have been previously powered down with a `cpu_off` call. /// -/// When the CPU is started, it will jump to the given entry and set the -/// corresponding register to the given argument. -pub fn cpu_on(id: usize, entry: usize, arg: usize) { - debug!("Starting core {}...", id); - assert_eq!(psci_hvc_call(PSCI_CPU_ON, id, entry, arg), 0); - debug!("Started core {}!", id); +/// `target_cpu` contains a copy of the affinity fields of the MPIDR register. +/// `entry_point` is the physical address of the secondary CPU's entry point. +/// `arg` will be passed to the `X0` register of the secondary CPU. +pub fn cpu_on(target_cpu: usize, entry_point: usize, arg: usize) { + info!("Starting CPU {:x} ON ...", target_cpu); + let res = psci_call(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_point, arg); + if let Err(e) = res { + error!("failed to boot CPU {:x} ({:?})", target_cpu, e); + } +} + +/// Power down the calling core. This call is intended for use in hotplug. A +/// core that is powered down by `cpu_off` can only be powered up again in +/// response to a `cpu_on`. +pub fn cpu_off() { + const PSCI_POWER_STATE_TYPE_STANDBY: u32 = 0; + const PSCI_POWER_STATE_TYPE_POWER_DOWN: u32 = 1; + const PSCI_0_2_POWER_STATE_TYPE_SHIFT: u32 = 16; + let state: u32 = PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT; + psci_call(PSCI_0_2_FN_CPU_OFF, state as usize, 0, 0).ok(); } diff --git a/modules/axhal/src/platform/aarch64_raspi/mod.rs b/modules/axhal/src/platform/aarch64_raspi/mod.rs index 2a452fa08e..c7c7264d4c 100644 --- a/modules/axhal/src/platform/aarch64_raspi/mod.rs +++ b/modules/axhal/src/platform/aarch64_raspi/mod.rs @@ -17,7 +17,12 @@ pub mod time { } pub mod misc { - pub use crate::platform::aarch64_common::psci::system_off as terminate; + pub fn terminate() -> ! { + info!("Shutting down..."); + loop { + crate::arch::halt(); + } + } } extern "C" { diff --git a/modules/axhal/src/platform/mod.rs b/modules/axhal/src/platform/mod.rs index 66f251a995..a39151c47f 100644 --- a/modules/axhal/src/platform/mod.rs +++ b/modules/axhal/src/platform/mod.rs @@ -19,6 +19,9 @@ cfg_if::cfg_if! { } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-raspi"))] { mod aarch64_raspi; pub use self::aarch64_raspi::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-bsta1000b"))] { + mod aarch64_bsta1000b; + pub use self::aarch64_bsta1000b::*; } else { mod dummy; pub use self::dummy::*; diff --git a/modules/axruntime/src/mp.rs b/modules/axruntime/src/mp.rs index 2797471683..768ef5188a 100644 --- a/modules/axruntime/src/mp.rs +++ b/modules/axruntime/src/mp.rs @@ -32,7 +32,7 @@ pub fn start_secondary_cpus(primary_cpu_id: usize) { #[no_mangle] pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { ENTERED_CPUS.fetch_add(1, Ordering::Relaxed); - info!("Secondary CPU {} started.", cpu_id); + info!("Secondary CPU {:x} started.", cpu_id); #[cfg(feature = "paging")] super::remap_kernel_memory().unwrap(); @@ -42,7 +42,7 @@ pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { #[cfg(feature = "multitask")] axtask::init_scheduler_secondary(); - info!("Secondary CPU {} init OK.", cpu_id); + info!("Secondary CPU {:x} init OK.", cpu_id); super::INITED_CPUS.fetch_add(1, Ordering::Relaxed); while !super::is_init_ok() { diff --git a/platforms/aarch64-bsta1000b.toml b/platforms/aarch64-bsta1000b.toml new file mode 100644 index 0000000000..097556a9f3 --- /dev/null +++ b/platforms/aarch64-bsta1000b.toml @@ -0,0 +1,54 @@ +# Architecture identifier. +arch = "aarch64" +# Platform identifier. +platform = "aarch64-bsta1000b" +# Platform family. +family = "aarch64-bsta1000b" + +# Base address of the whole physical memory. +phys-memory-base = "0x80000000" +# Size of the whole physical memory. +phys-memory-size = "0x70000000" +# Base physical address of the kernel image. +kernel-base-paddr = "0x81000000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_0000_8100_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_0000_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0x20008000", "0x1000"], # uart8250 UART0 + ["0x32000000", "0x8000"], # arm,gic-400 + ["0x32011000", "0x1000"], # CPU CSR + ["0x33002000", "0x1000"], # Top CRM + ["0x70035000", "0x1000"], # CRM reg + ["0x70038000", "0x1000"], # aon pinmux +] + +virtio-mmio-regions = [] + +# Base physical address of the PCIe ECAM space. +# pci-ecam-base = "0x40_1000_0000" +# End PCI bus number (`bus-range` property in device tree). +# pci-bus-end = "0xff" +# PCI device memory ranges (`ranges` property in device tree). +# pci-ranges = [] + +# UART Address +uart-paddr = "0x20008000" +# UART irq from device tree +uart-irq = "0xd5" +# GICD Address +gicd-paddr = "0x32001000" +# GICC Address +gicc-paddr = "0x32002000" + +# BST A1000B board registers +CPU_CSR_BASE = "0x32011000" +A1000BASE_TOPCRM = "0x33002000" +A1000BASE_SAFETYCRM = "0x70035000" +A1000BASE_AONCFG = "0x70038000" + +# PSCI +psci-method = "smc" diff --git a/platforms/aarch64-qemu-virt.toml b/platforms/aarch64-qemu-virt.toml index b01d9f5bf7..723bba7eb1 100644 --- a/platforms/aarch64-qemu-virt.toml +++ b/platforms/aarch64-qemu-virt.toml @@ -71,8 +71,11 @@ pci-ranges = [ ] # UART Address uart-paddr = "0x0900_0000" -uart-irq-num = "33" +uart-irq = "1" # GICC Address gicc-paddr = "0x0801_0000" gicd-paddr = "0x0800_0000" + +# PSCI +psci-method = "hvc" diff --git a/platforms/aarch64-raspi4.toml b/platforms/aarch64-raspi4.toml index cfe9d1bb9d..f204d1d1a0 100644 --- a/platforms/aarch64-raspi4.toml +++ b/platforms/aarch64-raspi4.toml @@ -24,7 +24,7 @@ mmio-regions = [ virtio-mmio-regions = [] # UART Address uart-paddr = "0xFE20_1000" -uart-irq-num = "153" +uart-irq = "0x79" # GIC Address gicc-paddr = "0xFF84_2000" diff --git a/scripts/make/bsta1000b-fada.mk b/scripts/make/bsta1000b-fada.mk new file mode 100644 index 0000000000..7a9efe4005 --- /dev/null +++ b/scripts/make/bsta1000b-fada.mk @@ -0,0 +1,4 @@ +fada: build + gzip -9 -cvf $(OUT_BIN) > arceos-fada.bin.gz + mkimage -f tools/bsta1000b/bsta1000b-fada-arceos.its arceos-fada.itb + @echo 'Built the FIT-uImage arceos-fada.itb' diff --git a/scripts/make/raspi4.mk b/scripts/make/raspi4.mk index 7b0b932fb2..66eea697b5 100644 --- a/scripts/make/raspi4.mk +++ b/scripts/make/raspi4.mk @@ -28,9 +28,6 @@ ifeq ($(BSP),rpi4) RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 endif -# Export for build.rs. -export LD_SCRIPT_PATH - EXEC_MINIPUSH = ruby tools/raspi4/common/serial/minipush.rb ##------------------------------------------------------------------------------ @@ -94,4 +91,4 @@ openocd: gdb: RUSTC_MISC_ARGS += -C debuginfo=2 gdb: $(KERNEL_ELF) $(call color_header, "Launching GDB") - @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) \ No newline at end of file + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) diff --git a/tools/bsta1000b/bsta1000b-fada-arceos.its b/tools/bsta1000b/bsta1000b-fada-arceos.its new file mode 100755 index 0000000000..8062b6354a --- /dev/null +++ b/tools/bsta1000b/bsta1000b-fada-arceos.its @@ -0,0 +1,50 @@ +/* + * U-Boot uImage source file with multiple kernels, ramdisks and FDT blobs + */ + +/dts-v1/; + +/ { + description = "Various kernels, ramdisks and FDT blobs"; + #address-cells = <1>; + + images { + kernel { + description = "ArceOS for BST A1000B"; + data = /incbin/("../../arceos-fada.bin.gz"); + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "gzip"; + load = <0x81000000>; + entry = <0x81000000>; + hash-1 { + algo = "md5"; + }; + hash-2 { + algo = "sha1"; + }; + }; + + fdt-fada { + description = "bsta1000b-fada fdt"; + data = /incbin/("./bsta1000b-fada.dtb"); + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + hash-1 { + algo = "crc32"; + }; + }; + }; + + configurations { + default = "config-fada"; + + config-fada { + description = "bsta1000b fada configuration"; + kernel = "kernel"; + fdt = "fdt-fada"; + }; + }; +}; diff --git a/tools/bsta1000b/bsta1000b-fada.dtb b/tools/bsta1000b/bsta1000b-fada.dtb new file mode 100644 index 0000000000000000000000000000000000000000..ff0ea9a42d899a118de720648a7c88ee8c8e2fcb GIT binary patch literal 59621 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

VmAGw~$QIAD@^Jh@hUs8CwnfeU+GDmqsobnoe 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 gV7~}*aU#@dSg}>2bB(1q5uE@ literal 0 HcmV?d00001