Add RISC-V IPI

This commit is contained in:
Zhang Junyang 2025-08-25 20:49:28 +08:00 committed by Tate, Hongliang Tian
parent 73c0f34947
commit 7d21144da6
10 changed files with 143 additions and 36 deletions

View File

@ -22,6 +22,8 @@ impl HwCpuId {
/// The caller must ensure that the interrupt number is valid and that
/// the corresponding handler is configured correctly on the remote CPU.
/// Furthermore, invoking the interrupt handler must also be safe.
pub(crate) unsafe fn send_ipi(_hw_cpu_id: HwCpuId, _irq_num: u8, _guard: &dyn PinCurrentCpu) {
pub(crate) unsafe fn send_ipi(_hw_cpu_id: HwCpuId, _guard: &dyn PinCurrentCpu) {
// To suppress unused function lint errors. We should be using it here.
let _ = crate::smp::do_inter_processor_call;
unimplemented!()
}

View File

@ -41,10 +41,24 @@ pub(in crate::arch) unsafe fn init(io_mem_builder: &IoMemAllocatorBuilder) {
IRQ_CHIP.call_once(|| IrqChip {
plics: SpinLock::new(plics.into_boxed_slice()),
});
// SAFETY: Accessing the `sie` CSR to enable the external interrupt is
// safe here because this function is only called during PLIC
// initialization, and we ensure that only the external interrupt bit is
// set without affecting other interrupt sources.
// SAFETY: Accessing the `sie` CSR to enable the external interrupt is safe
// here because this function is only called during PLIC initialization,
// and we ensure that only the external interrupt bit is set without
// affecting other interrupt sources.
unsafe { riscv::register::sie::set_sext() };
}
/// Initializes application-processor-specific PLIC state.
///
/// # Safety
///
/// This function is safe to call on the following conditions:
/// 1. It is called once and at most once on this AP.
/// 2. It is called before any other public functions of this module is called
/// on this AP.
pub(in crate::arch) unsafe fn init_current_hart() {
// SAFETY: Accessing the `sie` CSR to enable the external interrupt is safe
// here due to the same reasons mentioned in `init`.
unsafe { riscv::register::sie::set_sext() };
}

View File

@ -2,7 +2,9 @@
//! Inter-processor interrupts.
use crate::cpu::PinCurrentCpu;
use spin::Once;
use crate::{cpu::PinCurrentCpu, irq::IrqLine};
/// Hardware-specific, architecture-dependent CPU ID.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -15,6 +17,37 @@ impl HwCpuId {
}
}
pub(in crate::arch) static IPI_IRQ: Once<IrqLine> = Once::new();
/// Initializes the global IPI-related state and local state on BSP.
///
/// # Safety
///
/// This function can only be called before any other IPI-related function is
/// called.
pub(in crate::arch) unsafe fn init() {
let mut irq = IrqLine::alloc().unwrap();
// SAFETY: This will be called upon an inter-processor interrupt.
irq.on_active(|f| unsafe { crate::smp::do_inter_processor_call(f) });
IPI_IRQ.call_once(|| irq);
// SAFETY: Enabling the software interrupts is safe here because this
// function cannot be called when others can perform IPI-related
// operations. And it has no side-effects.
unsafe { riscv::register::sie::set_ssoft() };
}
/// Initializes the IPI-related state on this CPU.
///
/// # Safety
///
/// This function can only be called before any other harts can IPI this hart.
pub(in crate::arch) unsafe fn init_current_hart() {
// SAFETY: Enabling the software interrupts is safe here due to the same
// reasons mentioned in `init`.
unsafe { riscv::register::sie::set_ssoft() };
}
/// Sends a general inter-processor interrupt (IPI) to the specified CPU.
///
/// # Safety
@ -22,6 +55,24 @@ impl HwCpuId {
/// The caller must ensure that the interrupt number is valid and that
/// the corresponding handler is configured correctly on the remote CPU.
/// Furthermore, invoking the interrupt handler must also be safe.
pub(crate) unsafe fn send_ipi(_hw_cpu_id: HwCpuId, _irq_num: u8, _guard: &dyn PinCurrentCpu) {
unimplemented!()
#[expect(unused_variables)]
pub(crate) unsafe fn send_ipi(hw_cpu_id: HwCpuId, guard: &dyn PinCurrentCpu) {
const XLEN: usize = core::mem::size_of::<usize>() * 8;
const XLEN_MASK: usize = XLEN - 1;
let hart_id = hw_cpu_id.0 as usize;
let hart_mask_base = hart_id & !XLEN_MASK;
let hart_mask = 1 << (hart_id & XLEN_MASK);
let ret = sbi_rt::send_ipi(sbi_rt::HartMask::from_mask_base(hart_mask, hart_mask_base));
if ret.error == 0 {
log::debug!("Successfully sent IPI to hart {}", hw_cpu_id.0);
} else {
log::error!(
"Failed to send IPI to hart {}: error code {}",
hw_cpu_id.0,
ret.error
);
}
}

View File

@ -3,7 +3,7 @@
//! Interrupts.
pub(super) mod chip;
mod ipi;
pub(super) mod ipi;
mod ops;
mod remapping;
@ -55,7 +55,11 @@ impl HwIrqLine {
*interrupt_source_on_chip,
);
}
InterruptSource::Software => unimplemented!(),
InterruptSource::Software => {
// SAFETY: We have already handled the IPI. So clearing the
// software interrupt pending bit is safe.
unsafe { riscv::register::sip::clear_ssoft() };
}
}
}
}

View File

@ -45,6 +45,9 @@ pub(crate) unsafe fn late_init_on_bsp() {
// operations having been performed.
unsafe { irq::chip::init(&io_mem_builder) };
// SAFETY: This is called before any IPI-related operation is performed.
unsafe { irq::ipi::init() };
// SAFETY: We're on the BSP and we're ready to boot all APs.
unsafe { crate::boot::smp::boot_all_aps() };
@ -59,8 +62,26 @@ pub(crate) unsafe fn late_init_on_bsp() {
unsafe { crate::io::init(io_mem_builder) };
}
/// Initializes application-processor-specific state.
///
/// On RISC-V, Application Processors (APs) are harts that are not the
/// bootstrapping hart.
///
/// # Safety
///
/// This function must be called only once on each application processor.
/// And it should be called after the BSP's call to [`late_init_on_bsp`]
/// and before any other architecture-specific code in this module is called on
/// this AP.
pub(crate) unsafe fn init_on_ap() {
unimplemented!();
// SAFETY: The safety is upheld by the caller.
unsafe { trap::init_on_cpu() };
// SAFETY: The safety is upheld by the caller.
unsafe { irq::chip::init_current_hart() };
// SAFETY: This is called before any IPI-related operation is performed.
unsafe { irq::ipi::init_current_hart() };
}
/// Returns the frequency of TSC. The unit is Hz.

View File

@ -127,7 +127,14 @@ pub(super) fn call_irq_callback_functions_by_scause(
call_irq_callback_functions(trap_frame, &hw_irq_line, priv_level);
}
}
Interrupt::SupervisorSoft => todo!(),
Interrupt::SupervisorSoft => {
let ipi_irq_num = super::irq::ipi::IPI_IRQ.get().unwrap().num();
call_irq_callback_functions(
trap_frame,
&HwIrqLine::new(ipi_irq_num, InterruptSource::Software),
priv_level,
);
}
Interrupt::Unknown => {
panic!(
"Cannot handle unknown supervisor interrupt, scause: {:#x}, trapframe: {:#x?}.",

View File

@ -2,7 +2,9 @@
//! Inter-processor interrupts.
use crate::cpu::PinCurrentCpu;
use spin::Once;
use crate::{cpu::PinCurrentCpu, irq::IrqLine, smp::do_inter_processor_call};
/// Hardware-specific, architecture-dependent CPU ID.
///
@ -19,6 +21,16 @@ impl HwCpuId {
}
}
static IPI_IRQ: Once<IrqLine> = Once::new();
/// Initializes global IPI state.
pub(in crate::arch) fn init() {
let mut irq = IrqLine::alloc().unwrap();
// SAFETY: This will be called upon an inter-processor interrupt.
irq.on_active(|f| unsafe { do_inter_processor_call(f) });
IPI_IRQ.call_once(|| irq);
}
/// Sends a general inter-processor interrupt (IPI) to the specified CPU.
///
/// # Safety
@ -26,9 +38,11 @@ impl HwCpuId {
/// The caller must ensure that the interrupt number is valid and that
/// the corresponding handler is configured correctly on the remote CPU.
/// Furthermore, invoking the interrupt handler must also be safe.
pub(crate) unsafe fn send_ipi(hw_cpu_id: HwCpuId, irq_num: u8, guard: &dyn PinCurrentCpu) {
pub(crate) unsafe fn send_ipi(hw_cpu_id: HwCpuId, guard: &dyn PinCurrentCpu) {
use crate::arch::kernel::apic::{self, Icr};
let irq_num = IPI_IRQ.get().unwrap().num();
let icr = Icr::new(
apic::ApicId::from(hw_cpu_id.0),
apic::DestinationShorthand::NoShorthand,
@ -41,7 +55,7 @@ pub(crate) unsafe fn send_ipi(hw_cpu_id: HwCpuId, irq_num: u8, guard: &dyn PinCu
);
let apic = apic::get_or_init(guard);
// SAFETY: The ICR is valid to generate the request IPI. Generating the request IPI is safe
// as guaranteed by the caller.
// SAFETY: The ICR is valid to generate the request IPI. Generating the
// request IPI is safe.
unsafe { apic.send_ipi(icr) };
}

View File

@ -3,7 +3,7 @@
//! Interrupts.
pub(super) mod chip;
mod ipi;
pub(super) mod ipi;
mod ops;
mod remapping;

View File

@ -60,6 +60,7 @@ pub(crate) unsafe fn late_init_on_bsp() {
kernel::apic::init(&io_mem_builder).expect("APIC doesn't exist");
irq::chip::init(&io_mem_builder);
irq::ipi::init();
kernel::tsc::init_tsc_freq();
timer::init_on_bsp();

View File

@ -10,13 +10,9 @@ use alloc::{boxed::Box, collections::VecDeque};
use spin::Once;
use crate::{
arch::{
irq::{send_ipi, HwCpuId},
trap::TrapFrame,
},
arch::{irq::HwCpuId, trap::TrapFrame},
cpu::{CpuSet, PinCurrentCpu},
cpu_local,
irq::{self, IrqLine},
cpu_local, irq,
sync::SpinLock,
util::id_set::Id,
};
@ -40,7 +36,6 @@ pub fn inter_processor_call(targets: &CpuSet, f: fn()) {
let this_cpu_id = irq_guard.current_cpu();
let ipi_data = IPI_GLOBAL_DATA.get().unwrap();
let irq_num = ipi_data.irq.num();
let mut call_on_self = false;
for cpu_id in targets.iter() {
@ -54,15 +49,12 @@ pub fn inter_processor_call(targets: &CpuSet, f: fn()) {
if cpu_id == this_cpu_id {
continue;
}
let hw_cpu_id = ipi_data.hw_cpu_ids[cpu_id.as_usize()];
// SAFETY: The value of `irq_num` corresponds to a valid IRQ line and
// triggering it will not cause any safety issues.
unsafe {
send_ipi(
ipi_data.hw_cpu_ids[cpu_id.as_usize()],
irq_num,
&irq_guard as _,
)
};
crate::arch::irq::send_ipi(hw_cpu_id, &irq_guard as _);
}
}
if call_on_self {
// Execute the function synchronously.
@ -71,7 +63,6 @@ pub fn inter_processor_call(targets: &CpuSet, f: fn()) {
}
struct IpiGlobalData {
irq: IrqLine,
hw_cpu_ids: Box<[HwCpuId]>,
}
@ -81,7 +72,12 @@ cpu_local! {
static CALL_QUEUES: SpinLock<VecDeque<fn()>> = SpinLock::new(VecDeque::new());
}
fn do_inter_processor_call(_trapframe: &TrapFrame) {
/// Handles inter-processor calls.
///
/// # Safety
///
/// It should only be called upon the inter processor interrupt.
pub(crate) unsafe fn do_inter_processor_call(_trapframe: &TrapFrame) {
// No races because we are in IRQs.
let this_cpu_id = crate::cpu::CpuId::current_racy();
@ -98,11 +94,8 @@ fn do_inter_processor_call(_trapframe: &TrapFrame) {
pub(super) fn init() {
IPI_GLOBAL_DATA.call_once(|| {
let mut irq = IrqLine::alloc().unwrap();
irq.on_active(do_inter_processor_call);
let hw_cpu_ids = crate::boot::smp::construct_hw_cpu_id_mapping();
IpiGlobalData { irq, hw_cpu_ids }
IpiGlobalData { hw_cpu_ids }
});
}