diff --git a/src/arch/loongarch64/ipi.rs b/src/arch/loongarch64/ipi.rs index 54dab3653..35720de03 100644 --- a/src/arch/loongarch64/ipi.rs +++ b/src/arch/loongarch64/ipi.rs @@ -68,8 +68,17 @@ pub const SMP_RESCHEDULE: usize = 0x2; pub const SMP_CALL_FUNCTION: usize = 0x4; // customized actions :), since there is no docs on this yet /// Dedicated physical IPI bit used only as the hvisor event-queue doorbell. -/// Linux SMP actions use bits 0..=2, so sharing those bits can drop guest IPI work. +/// Keep this separate from the guest virtual-IPI doorbell below. pub const HVISOR_EVENT_DOORBELL: usize = 0x8; +/// Dedicated physical IPI bit used only to notify a target CPU that its +/// guest virtual-IPI status changed. This must not enter the generic event +/// queue path. +pub const HVISOR_VIPI_DOORBELL: usize = 0x10; + +#[inline] +pub fn arch_send_virtual_ipi(cpu_id: usize) { + arch_send_event(cpu_id as u64, HVISOR_VIPI_DOORBELL as u64); +} fn iocsr_mbuf_send_box_lo(a: usize) -> usize { a << 1 diff --git a/src/arch/loongarch64/trap.rs b/src/arch/loongarch64/trap.rs index 5087c8faf..5e0591ab5 100644 --- a/src/arch/loongarch64/trap.rs +++ b/src/arch/loongarch64/trap.rs @@ -23,7 +23,7 @@ use crate::consts::MAX_CPU_NUM; use crate::cpu_data::this_cpu_data; use crate::device::irqchip::inject_irq; use crate::device::irqchip::ls7a2000::chip::*; -use crate::event::{dump_events, handle_next_event}; +use crate::event::{dump_events, handle_next_event, has_pending_events}; use crate::hypercall::{SGI_IPI_ID, *}; use crate::memory::{addr, mmio_handle_access, MMIOAccess}; use crate::zone::Zone; @@ -1271,6 +1271,14 @@ const HWI7: usize = 1 << 9; /// handle loongarch64 interrupts here fn handle_interrupt(is: usize) { + let timer_pending = is & TIMER_BIT != 0; + if timer_pending { + // Clear the timer before processing IPI work. A combined IPI+timer + // exception must not let the IPI path starve the timer indefinitely. + loongArch64::register::ticlr::clear_timer_interrupt(); + debug!("Timer interrupt received"); + } + // Handle IPI interrupts if is & IPI_BIT != 0 { let cpu_id = this_cpu_id(); @@ -1280,31 +1288,42 @@ fn handle_interrupt(is: usize) { cpu_id, ipi_status ); - let hvisor_mask = SGI_IPI_ID as u32; - if ipi_status & hvisor_mask != 0 { + let event_doorbell = SGI_IPI_ID as u32; + let virtual_ipi_doorbell = HVISOR_VIPI_DOORBELL as u32; + + // Virtual IPI status is posted state, not a generic event. Its + // dedicated physical doorbell must never cause the event FIFO to be + // inspected or drained. + if ipi_status & virtual_ipi_doorbell != 0 { + clear_ipi_bits(virtual_ipi_doorbell); + if crate::arch::loongarch64::zone::virtual_ipi_pending(cpu_id) { + crate::arch::loongarch64::zone::sync_virtual_ipi_line(); + } + } + + if ipi_status & event_doorbell != 0 { // Clear before each fetch. If the fetch observes an empty queue while a // producer enqueues concurrently, its doorbell remains pending and // re-fires. Clearing after the fetch could lose that coalesced wakeup. - clear_ipi_bits(hvisor_mask); - while handle_next_event() { - clear_ipi_bits(hvisor_mask); + clear_ipi_bits(event_doorbell); + if has_pending_events(cpu_id) { + while handle_next_event() { + clear_ipi_bits(event_doorbell); + } } } - let unhandled = ipi_status & !hvisor_mask; + let handled_mask = event_doorbell | virtual_ipi_doorbell; + let unhandled = ipi_status & !handled_mask; if unhandled != 0 { error!( "CPU {} has unhandled physical IPI status {:#x}; preserving those bits", cpu_id, unhandled ); } - return; } - // Handle timer interrupts - if is & TIMER_BIT != 0 { - debug!("Timer interrupt received"); - loongArch64::register::ticlr::clear_timer_interrupt(); + if is & (IPI_BIT | TIMER_BIT) != 0 { return; } diff --git a/src/arch/loongarch64/zone.rs b/src/arch/loongarch64/zone.rs index 5db3981d6..e692f672a 100644 --- a/src/arch/loongarch64/zone.rs +++ b/src/arch/loongarch64/zone.rs @@ -21,7 +21,7 @@ use crate::{ Stage2PageTable, }, config::*, - consts::{IPI_EVENT_SEND_IPI, MAX_CPU_NUM, PAGE_SIZE}, + consts::{MAX_CPU_NUM, PAGE_SIZE}, cpu_data::{get_cpu_data, this_cpu_data, VcpuState}, device::virtio_trampoline::mmio_virtio_handler, error::{HvError, HvResult}, @@ -658,11 +658,18 @@ pub fn sync_virtual_ipi_line() { } } +pub fn virtual_ipi_pending(cpu: usize) -> bool { + cpu < MAX_CPU_NUM && VIRTUAL_IPI_STATUS[cpu].load(Ordering::Acquire) != 0 +} + fn sync_or_notify_virtual_ipi_line(cpu: usize) { if cpu == this_cpu_id() { sync_virtual_ipi_line(); } else { - send_event(cpu, SGI_IPI_ID as usize, IPI_EVENT_SEND_IPI); + // The virtual IPI status is already the pending state. Use the + // dedicated physical doorbell directly instead of allocating a + // one-item generic event just to call sync_virtual_ipi_line(). + crate::arch::ipi::arch_send_virtual_ipi(cpu); } } diff --git a/src/event.rs b/src/event.rs index 6c3947e37..ba94a6168 100644 --- a/src/event.rs +++ b/src/event.rs @@ -73,6 +73,13 @@ pub fn fetch_event(cpu: usize) -> Option { get_percpu_events(cpu).lock().pop_front() } +pub fn has_pending_events(cpu: usize) -> bool { + if cpu >= MAX_CPU_NUM { + return false; + } + !get_percpu_events(cpu).lock().is_empty() +} + pub fn dump_events() { for cpu in 0..MAX_CPU_NUM { let events = get_percpu_events(cpu).lock();