From 87ffddbcf98abd610c0384b59975c2e23647d1d3 Mon Sep 17 00:00:00 2001 From: li041 Date: Wed, 24 Jun 2026 14:49:22 +0800 Subject: [PATCH 1/6] loongarch64: fix SMP root CPU assumptions --- src/arch/loongarch64/cpu.rs | 11 +++++++---- src/arch/loongarch64/trap.rs | 12 ++++++------ src/arch/loongarch64/zone.rs | 12 ++++++------ src/device/irqchip/ls7a2000/mod.rs | 15 +++++++++++---- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/arch/loongarch64/cpu.rs b/src/arch/loongarch64/cpu.rs index 73a256f84..7281572ac 100644 --- a/src/arch/loongarch64/cpu.rs +++ b/src/arch/loongarch64/cpu.rs @@ -71,15 +71,18 @@ impl ArchCpu { for i in 0..32 { self.ctx.x[i] = 0; } - // set all zone's GCSR.CPUID to 0 beacuse linux running on it will believe it's CPU0 - // - wheatfox 2025.5.20 - self.ctx.gcsr_cpuid = 0; + let physical_cpu = this_cpu_data().id; + self.ctx.gcsr_cpuid = this_cpu_data() + .zone + .as_ref() + .and_then(|zone| zone.read().phys_to_guest_cpu(physical_cpu)) + .unwrap_or(0); info!( "[[CPU virtualization]] CPU{} run@{:#x}", self.get_cpuid(), self.ctx.sepc ); - info!("loongarch64: @{:#x?}", self); + debug!("loongarch64: @{:#x?}", self); // step 1: enable guest mode // step 2: set guest entry to era // step 3: run ertn and enter guest mode diff --git a/src/arch/loongarch64/trap.rs b/src/arch/loongarch64/trap.rs index b2bc0ef4e..deb87025d 100644 --- a/src/arch/loongarch64/trap.rs +++ b/src/arch/loongarch64/trap.rs @@ -1299,9 +1299,9 @@ fn handle_interrupt(is: usize) { // Handle timer interrupts if is & TIMER_BIT != 0 { - warn!("Timer interrupt received"); + debug!("Timer interrupt received"); loongArch64::register::ticlr::clear_timer_interrupt(); - crate::device::irqchip::ls7a2000::clear_hwi_injected_irq(); + crate::device::irqchip::ls7a2000::clear_hwi_injected_irq_if_needed(); return; } @@ -1357,7 +1357,7 @@ fn emulate_cpucfg(ins: usize, ctx: &mut ZoneContext) { const MAX_CPUCFG_REGS: usize = 21; - info!( + debug!( "cpucfg emulation, target cpucfg index is {:#x}", cpucfg_target_idx ); @@ -1394,19 +1394,19 @@ fn emulate_csrx(ins: usize, ctx: &mut ZoneContext) { match ty { 0 => { // csrrd - info!("csrrd emulation for CSR {:#x}", csr); + debug!("csrrd emulation for CSR {:#x}", csr); ctx.x[rd] = 0; // just set it to 0 } 1 => { // csrwr - info!("csrwr emulation for CSR {:#x}", csr); + debug!("csrwr emulation for CSR {:#x}", csr); ctx.x[rd] = 0; // do nothing to GCSR, but we also need to set rd to 0 } _ => { // csrxchg - info!("csrxchg emulation for CSR {:#x}", csr); + debug!("csrxchg emulation for CSR {:#x}", csr); ctx.x[rd] = 0; // do nothing to GCSR, but we also need to set rd to 0 } diff --git a/src/arch/loongarch64/zone.rs b/src/arch/loongarch64/zone.rs index baa50d137..d9e1968db 100644 --- a/src/arch/loongarch64/zone.rs +++ b/src/arch/loongarch64/zone.rs @@ -26,7 +26,7 @@ use crate::{ mmio_generic_handler, mmio_perform_access, GuestPhysAddr, HostPhysAddr, MMIOAccess, MemFlags, MemoryRegion, MemorySet, }, - zone::Zone, + zone::{is_this_root_zone, Zone}, PHY_TO_DMW_UNCACHED, }; use alloc::boxed::Box; @@ -513,8 +513,8 @@ fn handle_extioi_mapping_mmio(mmio: &mut MMIOAccess, base_addr: usize, size: usi return Ok(()); } - // if this is nonroot, we ignore the mmio - if this_cpu_id() != 0 { + // Non-root zones see a virtual CPU0 even when running on another physical CPU. + if !is_this_root_zone() { info!("nonroot's write to extioi mapping regs, ignored"); return Ok(()); } @@ -649,21 +649,21 @@ pub fn loongarch_generic_mmio_handler(mmio: &mut MMIOAccess, arg: usize) -> HvRe } else if is_in_mmio_range!(mmio.address, EXTIOI_SR_CORE_BASE, EXTIOI_SR_CORE_SIZE) { ret = handle_extioi_status_mmio(mmio, EXTIOI_SR_CORE_BASE, EXTIOI_SR_CORE_SIZE); } else if is_in_mmio_range!(mmio.address, EXTIOI_ENABLE_BASE, EXTIOI_ENABLE_SIZE) { - if this_cpu_id() != 0 && mmio.is_write { + if !is_this_root_zone() && mmio.is_write { info!("nonroot's write to extioi enable regs, ignored"); return Ok(()); } else { ret = handle_generic_mmio(mmio, BASE_ADDR); } } else if is_in_mmio_range!(mmio.address, EXTIOI_BOUNCE_BASE, EXTIOI_BOUNCE_SIZE) { - if this_cpu_id() != 0 && mmio.is_write { + if !is_this_root_zone() && mmio.is_write { info!("nonroot's write to extioi bounce regs, ignored"); return Ok(()); } else { ret = handle_generic_mmio(mmio, BASE_ADDR); } } else if is_in_mmio_range!(mmio.address, EXTIOI_NODE_SEL_BASE, EXTIOI_NODE_SEL_SIZE) { - if this_cpu_id() != 0 && mmio.is_write { + if !is_this_root_zone() && mmio.is_write { info!("nonroot's write to extioi node sel regs, ignored"); return Ok(()); } else { diff --git a/src/device/irqchip/ls7a2000/mod.rs b/src/device/irqchip/ls7a2000/mod.rs index b34a766c5..edda109e4 100644 --- a/src/device/irqchip/ls7a2000/mod.rs +++ b/src/device/irqchip/ls7a2000/mod.rs @@ -33,10 +33,6 @@ use spin::Mutex; pub mod chip; pub fn primary_init_early() { - if this_cpu_id() != 0 { - info!("loongarch64: irqchip: primary_init_early: do nothing on secondary cpus"); - return; - } info!("loongarch64: irqchip: primary_init_early: checking iochip configs"); print_chip_info(); csr_disable_new_codec(); @@ -159,6 +155,17 @@ pub fn clear_hwi_injected_irq() { tcfg::set_en(false); // stop timer } +pub fn clear_hwi_injected_irq_if_needed() { + { + let status = GLOBAL_IRQ_INJECT_STATUS.lock(); + if status.cpu_status[this_cpu_id()].status != InjectionStatus::Injecting { + tcfg::set_en(false); + return; + } + } + clear_hwi_injected_irq(); +} + impl Zone { pub fn arch_irqchip_reset(&self) { // clear all SR regs From c3fa5e90dc976236973b513eb168c5e8dd763557 Mon Sep 17 00:00:00 2001 From: li041 Date: Wed, 29 Jul 2026 14:48:52 +0800 Subject: [PATCH 2/6] loongarch64: add SMP guest IPI support --- src/arch/aarch64/ipi.rs | 9 +- src/arch/loongarch64/ipi.rs | 241 ++++++-------------------- src/arch/loongarch64/trap.rs | 34 ++-- src/arch/loongarch64/zone.rs | 269 ++++++++++++++++++++++++++++- src/arch/riscv64/ipi.rs | 9 +- src/arch/x86_64/ipi.rs | 8 +- src/device/irqchip/ls7a2000/mod.rs | 25 ++- src/event.rs | 65 +++---- src/hypercall/mod.rs | 3 + 9 files changed, 395 insertions(+), 268 deletions(-) diff --git a/src/arch/aarch64/ipi.rs b/src/arch/aarch64/ipi.rs index 974a30136..1904e61a5 100644 --- a/src/arch/aarch64/ipi.rs +++ b/src/arch/aarch64/ipi.rs @@ -15,10 +15,15 @@ // use crate::device::irqchip::gic_send_event; + pub fn arch_send_event(cpu_id: u64, sgi_num: u64) { gic_send_event(cpu_id, sgi_num); } +pub fn arch_notify_event(cpu_id: u64, sgi_num: u64, _event_id: usize, _queue_was_empty: bool) { + arch_send_event(cpu_id, sgi_num); +} + pub fn arch_check_events(event: Option) { match event { _ => { @@ -29,7 +34,3 @@ pub fn arch_check_events(event: Option) { } } } - -pub fn arch_prepare_send_event(_cpu_id: usize, _ipi_int_id: usize, _event_id: usize) { - debug!("aarch64 arch_prepare_send_event: do nothing now.") -} diff --git a/src/arch/loongarch64/ipi.rs b/src/arch/loongarch64/ipi.rs index 1e1f0cfdb..4a1efcfd0 100644 --- a/src/arch/loongarch64/ipi.rs +++ b/src/arch/loongarch64/ipi.rs @@ -15,81 +15,61 @@ // Yulong Han // use crate::arch::cpu::this_cpu_id; -use crate::consts::IPI_EVENT_CLEAR_INJECT_IRQ; -use crate::device::common::MMIODerefWrapper; +use crate::consts::{IPI_EVENT_CLEAR_INJECT_IRQ, IPI_EVENT_SEND_IPI}; use core::arch::asm; use core::ptr::write_volatile; -use loongArch64::cpu; use loongArch64::register::ecfg::LineBasedInterrupt; use loongArch64::register::*; -use loongArch64::time; -use tock_registers::fields::FieldValue; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; -use tock_registers::register_bitfields; -use tock_registers::register_structs; -use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly}; pub fn arch_send_event(cpu_id: u64, sgi_num: u64) { debug!( "loongarch64: arch_send_event: sending event to cpu: {}, sgi_num: {}", cpu_id, sgi_num ); - // just call ipi_write_action - ipi_write_action(cpu_id as usize, sgi_num as usize); + // Route hvisor event doorbells through the IOCSR sender, whose payload + // carries the target CPU ID instead of indexing the fixed legacy table. + ipi_write_action_percore(cpu_id as usize, sgi_num as usize); } -register_bitfields! [ - u32, - pub IpiStatus [ IPISTATUS OFFSET(0) NUMBITS(32) ], - pub IpiEnable [ IPIENABLE OFFSET(0) NUMBITS(32) ], - pub IpiSet [ IPISET OFFSET(0) NUMBITS(32) ], - pub IpiClear [ IPICLEAR OFFSET(0) NUMBITS(32) ], -]; - -register_bitfields! [ - u64, - pub Mailbox0 [ MAILBOX0 OFFSET(0) NUMBITS(64) ], - pub Mailbox1 [ MAILBOX1 OFFSET(0) NUMBITS(64) ], - pub Mailbox2 [ MAILBOX2 OFFSET(0) NUMBITS(64) ], - pub Mailbox3 [ MAILBOX3 OFFSET(0) NUMBITS(64) ], -]; - -register_structs! { - #[allow(non_snake_case)] - pub IpiRegisters { - (0x00 => pub ipi_status: ReadOnly), - (0x04 => pub ipi_enable: ReadWrite), - (0x08 => pub ipi_set: WriteOnly), - (0x0c => pub ipi_clear: WriteOnly), - (0x10 => _reserved0: [u8; 0x10]), - (0x20 => pub mailbox0: ReadWrite), - (0x28 => pub mailbox1: ReadWrite), - (0x30 => pub mailbox2: ReadWrite), - (0x38 => pub mailbox3: ReadWrite), - (0x40 => @END), - } +pub fn arch_notify_event(cpu_id: u64, sgi_num: u64, event_id: usize, queue_was_empty: bool) { + debug!( + "loongarch64: notify event: cpu={}, ipi={}, event={}, queue_was_empty={}", + cpu_id, sgi_num, event_id, queue_was_empty + ); + if queue_was_empty { + arch_send_event(cpu_id, sgi_num); + } } const MMIO_BASE: usize = 0x8000_0000_1fe0_0000; -const IPI_MMIO_BASE: usize = MMIO_BASE; -const IPI_ANY_SEND_BASE: usize = MMIO_BASE + 0x1158; +const IOCSR_IPI_STATUS: usize = 0x1000; +const IOCSR_IPI_ENABLE: usize = 0x1004; +const IOCSR_IPI_CLEAR: usize = 0x100c; -// IPI registers, use this if you don't want to use the percore-IPI feature -pub static CORE0_IPI: MMIODerefWrapper = - unsafe { MMIODerefWrapper::new(IPI_MMIO_BASE + 0x1000) }; -pub static CORE1_IPI: MMIODerefWrapper = - unsafe { MMIODerefWrapper::new(IPI_MMIO_BASE + 0x1100) }; -pub static CORE2_IPI: MMIODerefWrapper = - unsafe { MMIODerefWrapper::new(IPI_MMIO_BASE + 0x1200) }; -pub static CORE3_IPI: MMIODerefWrapper = - unsafe { MMIODerefWrapper::new(IPI_MMIO_BASE + 0x1300) }; +#[inline] +fn iocsr_read32(reg: usize) -> u32 { + let value: usize; + unsafe { + asm!("iocsrrd.w {}, {}", out(reg) value, in(reg) reg); + } + value as u32 +} + +#[inline] +fn iocsr_write32(value: u32, reg: usize) { + unsafe { + asm!("iocsrwr.w {}, {}", in(reg) value as usize, in(reg) reg); + } +} // ipi actions pub const SMP_BOOT_CPU: usize = 0x1; pub const SMP_RESCHEDULE: usize = 0x2; pub const SMP_CALL_FUNCTION: usize = 0x4; // customized actions :), since there is no docs on this yet -pub const HVISOR_START_VCPU: usize = 0x8; +/// 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. +pub const HVISOR_EVENT_DOORBELL: usize = 0x8; fn iocsr_mbuf_send_box_lo(a: usize) -> usize { a << 1 @@ -182,115 +162,32 @@ pub fn ipi_write_action_percore(cpu_id: usize, _action: usize) { ); } -pub fn ipi_write_action(cpu_id: usize, _action: usize) { - // just write _action directly to the target cpu legacy IPI registers - // which is the IPI_SET register - debug!( - "ipi_write_action_legacy: sending action: {:#x} to cpu: {}", - _action, cpu_id - ); - let ipi: &MMIODerefWrapper = match cpu_id { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("ipi_write_action_legacy: invalid cpu_id: {}", cpu_id); - return; - } - }; - ipi.ipi_set.write(IpiSet::IPISET.val(_action as u32)); - debug!( - "ipi_write_action_legacy: finished sending action: {:#x} to cpu: {}", - _action, cpu_id - ); +pub fn enable_ipi() { + iocsr_write32(u32::MAX, IOCSR_IPI_ENABLE); + debug!("enable_ipi: IPI enabled for cpu {}", this_cpu_id()); } -pub fn mail_send(data: usize, cpu_id: usize, mailbox_id: usize) { - // just write data to the target cpu mailbox registers - // which is the mailbox0 register +pub fn clear_all_ipi() { + iocsr_write32(u32::MAX, IOCSR_IPI_CLEAR); debug!( - "mail_send: sending data: {:#x} to cpu: {}, mailbox_id: {}", - data, cpu_id, mailbox_id - ); - let ipi: &MMIODerefWrapper = match cpu_id { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("mail_send: invalid cpu_id: {}", cpu_id); - return; - } - }; - match mailbox_id { - 0 => ipi.mailbox0.write(Mailbox0::MAILBOX0.val(data as u64)), - 1 => ipi.mailbox1.write(Mailbox1::MAILBOX1.val(data as u64)), - 2 => ipi.mailbox2.write(Mailbox2::MAILBOX2.val(data as u64)), - 3 => ipi.mailbox3.write(Mailbox3::MAILBOX3.val(data as u64)), - _ => { - error!("mail_send: invalid mailbox_id: {}", mailbox_id); - return; - } - } - debug!( - "mail_send: finished sending data: {:#x} to cpu: {}, mailbox_id: {}", - data, cpu_id, mailbox_id + "clear_all_ipi: IPI status for cpu {}: {:#x}", + this_cpu_id(), + iocsr_read32(IOCSR_IPI_STATUS) ); } -pub fn enable_ipi(cpu_id: usize) { - let ipi: &MMIODerefWrapper = match cpu_id { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("enable_ipi: invalid cpu_id: {}", cpu_id); - return; - } - }; - ipi.ipi_enable.write(IpiEnable::IPIENABLE.val(0xffffffff)); - debug!("enable_ipi: IPI enabled for cpu {}", cpu_id); +pub fn clear_ipi_bits(mask: u32) { + iocsr_write32(mask, IOCSR_IPI_CLEAR); } -pub fn clear_all_ipi(cpu_id: usize) { - let ipi: &MMIODerefWrapper = match cpu_id { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("clear_all_ipi: invalid cpu_id: {}", cpu_id); - return; - } - }; - ipi.ipi_clear.write(IpiClear::IPICLEAR.val(0xffffffff)); - debug!( - "clear_all_ipi: IPI status for cpu {}: {:#x}", - cpu_id, - ipi.ipi_status.read(IpiStatus::IPISTATUS) - ); -} - -pub fn reset_ipi(cpu_id: usize) { +pub fn reset_ipi() { // clear all IPIs and enable all IPIs - clear_all_ipi(cpu_id); - enable_ipi(cpu_id); + clear_all_ipi(); + enable_ipi(); } -pub fn get_ipi_status(cpu_id: usize) -> u32 { - let ipi: &MMIODerefWrapper = match cpu_id { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("get_ipi_status: invalid cpu_id: {}", cpu_id); - return 0; - } - }; - ipi.ipi_status.read(IpiStatus::IPISTATUS) +pub fn get_ipi_status() -> u32 { + iocsr_read32(IOCSR_IPI_STATUS) } pub fn ecfg_ipi_enable() { @@ -315,35 +212,6 @@ pub fn ecfg_ipi_disable() { ); } -pub fn dump_ipi_registers() { - info!( - "dump_ipi_registers: dumping IPI registers for this cpu {}", - this_cpu_id() - ); - let ipi: &MMIODerefWrapper = match this_cpu_id() { - 0 => &CORE0_IPI, - 1 => &CORE1_IPI, - 2 => &CORE2_IPI, - 3 => &CORE3_IPI, - _ => { - error!("dump_ipi_registers: invalid cpu_id: {}", this_cpu_id()); - return; - } - }; - println!( - "ipi_status: {:#x}, ipi_enable: {:#x}", - ipi.ipi_status.read(IpiStatus::IPISTATUS), - ipi.ipi_enable.read(IpiEnable::IPIENABLE), - ); - println!( - "mailbox0: {:#x}, mailbox1: {:#x}, mailbox2: {:#x}, mailbox3: {:#x}", - ipi.mailbox0.read(Mailbox0::MAILBOX0), - ipi.mailbox1.read(Mailbox1::MAILBOX1), - ipi.mailbox2.read(Mailbox2::MAILBOX2), - ipi.mailbox3.read(Mailbox3::MAILBOX3) - ); -} - pub fn arch_check_events(event: Option) { match event { Some(IPI_EVENT_CLEAR_INJECT_IRQ) => { @@ -351,17 +219,12 @@ pub fn arch_check_events(event: Option) { use crate::device::irqchip::ls7a2000::clear_hwi_injected_irq; clear_hwi_injected_irq(); } + Some(IPI_EVENT_SEND_IPI) => { + use crate::device::irqchip::ls7a2000::inject_irq; + inject_irq(12, false); + } _ => { panic!("arch_check_events: unhandled event: {:?}", event); } } } - -pub fn arch_prepare_send_event(cpu_id: usize, ipi_int_id: usize, event_id: usize) { - use crate::event::fetch_event; - while !fetch_event(cpu_id).is_none() {} - debug!( - "loongarch64:: send_event: cpu_id: {}, ipi_int_id: {}, event_id: {}", - cpu_id, ipi_int_id, event_id - ); -} diff --git a/src/arch/loongarch64/trap.rs b/src/arch/loongarch64/trap.rs index deb87025d..a98fe2cec 100644 --- a/src/arch/loongarch64/trap.rs +++ b/src/arch/loongarch64/trap.rs @@ -19,11 +19,11 @@ use super::register::*; use super::zone::ZoneContext; use crate::arch::cpu::this_cpu_id; use crate::arch::ipi::*; -use crate::consts::{IPI_EVENT_CLEAR_INJECT_IRQ, MAX_CPU_NUM}; +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::{check_events, dump_cpu_events, dump_events}; +use crate::event::{dump_events, handle_next_event}; use crate::hypercall::{SGI_IPI_ID, *}; use crate::memory::{addr, mmio_handle_access, MMIOAccess}; use crate::zone::Zone; @@ -1274,26 +1274,30 @@ fn handle_interrupt(is: usize) { // Handle IPI interrupts if is & IPI_BIT != 0 { let cpu_id = this_cpu_id(); - let ipi_status = get_ipi_status(cpu_id); + let ipi_status = get_ipi_status(); debug!( "CPU {} received IPI interrupt, status = {:#x}", cpu_id, ipi_status ); - match ipi_status { - status if status == SGI_IPI_ID as _ => { - let events = dump_cpu_events(cpu_id); - debug!("CPU {} events: {:?}", cpu_id, events); - while check_events() {} - } - status if status == 0x8 => { - debug!("CPU {} received unhandled IPI status {:#x}", cpu_id, status); - } - status => { - warn!("CPU {} received unknown IPI status {:#x}", cpu_id, status); + let hvisor_mask = SGI_IPI_ID as u32; + if ipi_status & hvisor_mask != 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); } } - reset_ipi(cpu_id); + + let unhandled = ipi_status & !hvisor_mask; + if unhandled != 0 { + error!( + "CPU {} has unhandled physical IPI status {:#x}; preserving those bits", + cpu_id, unhandled + ); + } return; } diff --git a/src/arch/loongarch64/zone.rs b/src/arch/loongarch64/zone.rs index d9e1968db..68627782c 100644 --- a/src/arch/loongarch64/zone.rs +++ b/src/arch/loongarch64/zone.rs @@ -16,11 +16,17 @@ // use crate::device::irqchip::ls7a2000::chip::get_extioi_sr; use crate::{ - arch::{cpu::this_cpu_id, trap::GLOBAL_TRAP_CONTEXT_HELPER_PER_CPU, Stage2PageTable}, + arch::{ + cpu::this_cpu_id, ipi::SMP_BOOT_CPU, trap::GLOBAL_TRAP_CONTEXT_HELPER_PER_CPU, + Stage2PageTable, + }, config::*, - consts::PAGE_SIZE, + consts::{IPI_EVENT_SEND_IPI, MAX_CPU_NUM, PAGE_SIZE}, + cpu_data::{get_cpu_data, this_cpu_data, VcpuState}, device::virtio_trampoline::mmio_virtio_handler, error::{HvError, HvResult}, + event::{send_event, IPI_EVENT_WAKEUP}, + hypercall::SGI_IPI_ID, memory::{ addr::{align_down, align_up}, mmio_generic_handler, mmio_perform_access, GuestPhysAddr, HostPhysAddr, MMIOAccess, @@ -33,7 +39,7 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; -use core::sync::atomic::{fence, AtomicU64, Ordering}; +use core::sync::atomic::{fence, AtomicU32, AtomicU64, Ordering}; use core::{arch::asm, ptr::write_volatile}; use spin::lazy::Lazy; use spin::Mutex; @@ -457,6 +463,12 @@ const LOG_INTERVAL: u64 = 100000; const BASE_ADDR: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_0000); const UART0_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_01e0); const UART0_SIZE: usize = 0x8; +const IPI_REG_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_1000); +const IPI_REG_SIZE: usize = 0x400; +const IPI_SEND_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_1040); +const IPI_SEND_SIZE: usize = 0x4; +const IPI_MAIL_SEND_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_1048); +const IPI_MAIL_SEND_SIZE: usize = 0x8; const LIOINTC_MAP_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_1400); // 1400-141f const LIOINTC_MAP_SIZE: usize = 0x20; const ANYSEND_BASE: usize = PHY_TO_DMW_UNCACHED!(0x1fe0_1158); @@ -484,6 +496,26 @@ macro_rules! is_in_mmio_range { }; } +const IPI_STATUS: usize = 0x00; +const IPI_ENABLE: usize = 0x04; +const IPI_SET: usize = 0x08; +const IPI_CLEAR: usize = 0x0c; +const IPI_MAILBOX_BASE: usize = 0x20; +const IPI_MAILBOX_SIZE: usize = 0x20; + +static VIRTUAL_IPI_STATUS: [AtomicU32; MAX_CPU_NUM] = { + const C: AtomicU32 = AtomicU32::new(0); + [C; MAX_CPU_NUM] +}; +static VIRTUAL_IPI_ENABLE: [AtomicU32; MAX_CPU_NUM] = { + const C: AtomicU32 = AtomicU32::new(0xffff_ffff); + [C; MAX_CPU_NUM] +}; +static VIRTUAL_IPI_MAILBOX: [AtomicU64; MAX_CPU_NUM * 4] = { + const C: AtomicU64 = AtomicU64::new(0); + [C; MAX_CPU_NUM * 4] +}; + fn handle_uart_mmio(mmio: &mut MMIOAccess, base_addr: usize) -> HvResult { mmio_perform_access(base_addr, mmio); Ok(()) @@ -515,7 +547,7 @@ fn handle_extioi_mapping_mmio(mmio: &mut MMIOAccess, base_addr: usize, size: usi // Non-root zones see a virtual CPU0 even when running on another physical CPU. if !is_this_root_zone() { - info!("nonroot's write to extioi mapping regs, ignored"); + debug!("nonroot's write to extioi mapping regs, ignored"); return Ok(()); } @@ -587,6 +619,221 @@ fn handle_generic_mmio(mmio: &mut MMIOAccess, base_addr: usize) -> HvResult { Ok(()) } +fn this_guest_cpu_id() -> usize { + this_cpu_data() + .zone + .as_ref() + .and_then(|zone| zone.read().phys_to_guest_cpu(this_cpu_id())) + .unwrap_or(0) +} + +fn guest_cpu_to_physical_cpu(guest_cpu: usize) -> Option { + this_cpu_data() + .zone + .as_ref() + .and_then(|zone| zone.read().guest_to_phys_cpu(guest_cpu)) +} + +fn virtual_ipi_send(target_guest_cpu: usize, ipi_bits: u32) -> HvResult { + let Some(target_cpu) = guest_cpu_to_physical_cpu(target_guest_cpu) else { + warn!( + "loongarch64: guest IPI target vCPU {} out of range", + target_guest_cpu + ); + return Ok(()); + }; + + if ipi_bits & SMP_BOOT_CPU as u32 != 0 { + let entry = VIRTUAL_IPI_MAILBOX[target_cpu * 4].load(Ordering::Acquire) as usize; + let target_data = get_cpu_data(target_cpu); + let _lock = target_data.ctrl_lock.lock(); + if target_data.vcpu_state.is_stopped() { + debug!( + "loongarch64: guest boots vCPU{}(pCPU{}) through IPI, entry={:#x}", + target_guest_cpu, target_cpu, entry + ); + target_data.cpu_on_entry = entry; + target_data.vcpu_state.store(VcpuState::Ready); + send_event(target_cpu, SGI_IPI_ID as usize, IPI_EVENT_WAKEUP); + } else { + warn!( + "loongarch64: guest tried to boot running vCPU{}(pCPU{})", + target_guest_cpu, target_cpu + ); + } + } + + let pending = ipi_bits & !(SMP_BOOT_CPU as u32); + if pending != 0 { + debug!( + "loongarch64: guest IPI send cpu{} -> cpu{}, bits={:#x}", + this_guest_cpu_id(), + target_guest_cpu, + pending + ); + VIRTUAL_IPI_STATUS[target_cpu].fetch_or(pending, Ordering::AcqRel); + if target_cpu == this_cpu_id() { + crate::device::irqchip::ls7a2000::inject_irq(12, false); + } else { + send_event(target_cpu, SGI_IPI_ID as usize, IPI_EVENT_SEND_IPI); + } + } + + Ok(()) +} + +fn handle_ipi_any_send_mmio(mmio: &mut MMIOAccess) -> HvResult { + if !mmio.is_write { + mmio.value = 0; + return Ok(()); + } + if mmio.size != 4 { + warn!("loongarch64: unsupported guest IPI send size {}", mmio.size); + return Ok(()); + } + + let value = mmio.value as u32; + let ipi_id = value & 0x1f; + let target_guest_cpu = ((value >> 16) & 0x3ff) as usize; + virtual_ipi_send(target_guest_cpu, 1u32 << ipi_id) +} + +fn handle_ipi_mail_send_mmio(mmio: &mut MMIOAccess) -> HvResult { + if !mmio.is_write { + mmio.value = 0; + return Ok(()); + } + if mmio.size != 8 { + warn!( + "loongarch64: unsupported guest mailbox send size {}", + mmio.size + ); + return Ok(()); + } + + let value = mmio.value as u64; + let target_guest_cpu = ((value >> 16) & 0x3ff) as usize; + let box_half = ((value >> 2) & 0x7) as usize; + let mailbox = box_half / 2; + let is_high = (box_half & 1) != 0; + let data = (value >> 32) as u32; + + let Some(target_cpu) = guest_cpu_to_physical_cpu(target_guest_cpu) else { + warn!( + "loongarch64: invalid guest mailbox send target_vcpu={}, mailbox={}", + target_guest_cpu, mailbox + ); + return Ok(()); + }; + + if mailbox >= 4 { + warn!( + "loongarch64: invalid guest mailbox send target_vcpu={}, mailbox={}", + target_guest_cpu, mailbox + ); + return Ok(()); + } + + let slot = &VIRTUAL_IPI_MAILBOX[target_cpu * 4 + mailbox]; + if is_high { + let old = slot.load(Ordering::Acquire); + let new = (old & 0x0000_0000_ffff_ffff) | ((data as u64) << 32); + slot.store(new, Ordering::Release); + } else { + let old = slot.load(Ordering::Acquire); + let new = (old & 0xffff_ffff_0000_0000) | data as u64; + slot.store(new, Ordering::Release); + } + + Ok(()) +} + +fn handle_ipi_mmio(mmio: &mut MMIOAccess) -> HvResult { + let cpu_block = (mmio.address - offset(IPI_REG_BASE)) / 0x100; + let reg = (mmio.address - offset(IPI_REG_BASE)) % 0x100; + let guest_cpu = if cpu_block == 0 { + this_guest_cpu_id() + } else { + cpu_block + }; + let Some(cpu) = guest_cpu_to_physical_cpu(guest_cpu) else { + warn!( + "loongarch64: guest IPI register access for invalid vCPU {}", + guest_cpu + ); + if !mmio.is_write { + mmio.value = 0; + } + return Ok(()); + }; + + match reg { + IPI_STATUS => { + if !mmio.is_write { + mmio.value = VIRTUAL_IPI_STATUS[cpu].load(Ordering::Acquire) as usize; + if mmio.value != 0 { + debug!( + "loongarch64: guest cpu{} reads IPI_STATUS cpu{} = {:#x}", + this_guest_cpu_id(), + guest_cpu, + mmio.value + ); + } + } + } + IPI_ENABLE => { + if mmio.is_write { + VIRTUAL_IPI_ENABLE[cpu].store(mmio.value as u32, Ordering::Release); + } else { + mmio.value = VIRTUAL_IPI_ENABLE[cpu].load(Ordering::Acquire) as usize; + } + } + IPI_SET => { + if mmio.is_write { + virtual_ipi_send(guest_cpu, mmio.value as u32)?; + } else { + mmio.value = 0; + } + } + IPI_CLEAR => { + if mmio.is_write { + let old = VIRTUAL_IPI_STATUS[cpu].load(Ordering::Acquire); + VIRTUAL_IPI_STATUS[cpu].fetch_and(!(mmio.value as u32), Ordering::AcqRel); + if cpu == this_cpu_id() && (old & mmio.value as u32) != 0 { + crate::device::irqchip::ls7a2000::clear_injected_irq(12); + } + if old != 0 || mmio.value != 0 { + debug!( + "loongarch64: guest cpu{} clears IPI_STATUS cpu{}, mask={:#x}, old={:#x}", + this_guest_cpu_id(), + guest_cpu, + mmio.value, + old + ); + } + } else { + mmio.value = 0; + } + } + IPI_MAILBOX_BASE..=0x3f if reg < IPI_MAILBOX_BASE + IPI_MAILBOX_SIZE => { + let mailbox = (reg - IPI_MAILBOX_BASE) / 8; + if mmio.is_write { + VIRTUAL_IPI_MAILBOX[cpu * 4 + mailbox].store(mmio.value as u64, Ordering::Release); + } else { + mmio.value = + VIRTUAL_IPI_MAILBOX[cpu * 4 + mailbox].load(Ordering::Acquire) as usize; + } + } + _ => { + if !mmio.is_write { + mmio.value = 0; + } + } + } + + Ok(()) +} + fn handle_mmio_stats(mmio: &mut MMIOAccess) { let key = MMIOAccessKey { offset: mmio.address, @@ -644,27 +891,33 @@ pub fn loongarch_generic_mmio_handler(mmio: &mut MMIOAccess, arg: usize) -> HvRe let ret; - if is_in_mmio_range!(mmio.address, EXTIOI_MAP_CORE_BASE, EXTIOI_MAP_CORE_SIZE) { + if is_in_mmio_range!(mmio.address, IPI_SEND_BASE, IPI_SEND_SIZE) { + ret = handle_ipi_any_send_mmio(mmio); + } else if is_in_mmio_range!(mmio.address, IPI_MAIL_SEND_BASE, IPI_MAIL_SEND_SIZE) { + ret = handle_ipi_mail_send_mmio(mmio); + } else if is_in_mmio_range!(mmio.address, IPI_REG_BASE, IPI_REG_SIZE) { + ret = handle_ipi_mmio(mmio); + } else if is_in_mmio_range!(mmio.address, EXTIOI_MAP_CORE_BASE, EXTIOI_MAP_CORE_SIZE) { ret = handle_extioi_mapping_mmio(mmio, EXTIOI_MAP_CORE_BASE, EXTIOI_MAP_CORE_SIZE); } else if is_in_mmio_range!(mmio.address, EXTIOI_SR_CORE_BASE, EXTIOI_SR_CORE_SIZE) { ret = handle_extioi_status_mmio(mmio, EXTIOI_SR_CORE_BASE, EXTIOI_SR_CORE_SIZE); } else if is_in_mmio_range!(mmio.address, EXTIOI_ENABLE_BASE, EXTIOI_ENABLE_SIZE) { if !is_this_root_zone() && mmio.is_write { - info!("nonroot's write to extioi enable regs, ignored"); + debug!("nonroot's write to extioi enable regs, ignored"); return Ok(()); } else { ret = handle_generic_mmio(mmio, BASE_ADDR); } } else if is_in_mmio_range!(mmio.address, EXTIOI_BOUNCE_BASE, EXTIOI_BOUNCE_SIZE) { if !is_this_root_zone() && mmio.is_write { - info!("nonroot's write to extioi bounce regs, ignored"); + debug!("nonroot's write to extioi bounce regs, ignored"); return Ok(()); } else { ret = handle_generic_mmio(mmio, BASE_ADDR); } } else if is_in_mmio_range!(mmio.address, EXTIOI_NODE_SEL_BASE, EXTIOI_NODE_SEL_SIZE) { if !is_this_root_zone() && mmio.is_write { - info!("nonroot's write to extioi node sel regs, ignored"); + debug!("nonroot's write to extioi node sel regs, ignored"); return Ok(()); } else { ret = handle_generic_mmio(mmio, BASE_ADDR); diff --git a/src/arch/riscv64/ipi.rs b/src/arch/riscv64/ipi.rs index 242023cad..fca1e2491 100644 --- a/src/arch/riscv64/ipi.rs +++ b/src/arch/riscv64/ipi.rs @@ -19,7 +19,6 @@ use crate::consts::IPI_EVENT_SEND_IPI; use crate::consts::IPI_EVENT_UPDATE_HART_LINE; use crate::platform::BOARD_HARTID_MAP; -// arch_send_event pub fn arch_send_event(cpu_id: u64, _sgi_num: u64) { let hart_id = BOARD_HARTID_MAP[cpu_id as usize]; debug!("arch_send_event: cpu_id: {}", hart_id); @@ -35,6 +34,10 @@ pub fn arch_send_event(cpu_id: u64, _sgi_num: u64) { } } +pub fn arch_notify_event(cpu_id: u64, sgi_num: u64, _event_id: usize, _queue_was_empty: bool) { + arch_send_event(cpu_id, sgi_num); +} + /// Handle send_ipi event. pub fn arch_ipi_handler() { unsafe { @@ -58,7 +61,3 @@ pub fn arch_check_events(event: Option) { } } } - -pub fn arch_prepare_send_event(_cpu_id: usize, _ipi_int_id: usize, _event_id: usize) { - debug!("risc-v arch_prepare_send_event: do nothing now.") -} diff --git a/src/arch/x86_64/ipi.rs b/src/arch/x86_64/ipi.rs index 7de5c2cc1..67a1588d1 100644 --- a/src/arch/x86_64/ipi.rs +++ b/src/arch/x86_64/ipi.rs @@ -144,6 +144,10 @@ pub fn arch_send_event(dest: u64, _: u64) { }; } +pub fn arch_notify_event(dest: u64, vector: u64, _event_id: usize, _queue_was_empty: bool) { + arch_send_event(dest, vector); +} + pub fn handle_virt_ipi() { // this may never return! loop { @@ -164,7 +168,3 @@ pub fn arch_check_events(event: Option) { } } } - -pub fn arch_prepare_send_event(cpu_id: usize, ipi_int_id: usize, event_id: usize) { - debug!("x86_64 arch_prepare_send_event: do nothing now.") -} diff --git a/src/device/irqchip/ls7a2000/mod.rs b/src/device/irqchip/ls7a2000/mod.rs index edda109e4..d37e432c7 100644 --- a/src/device/irqchip/ls7a2000/mod.rs +++ b/src/device/irqchip/ls7a2000/mod.rs @@ -85,8 +85,8 @@ pub fn clock_cpucfg_dump() { pub fn percpu_init() { info!("loongarch64: irqchip: percpu_init: running percpu_init"); - clear_all_ipi(this_cpu_id()); - enable_ipi(this_cpu_id()); + clear_all_ipi(); + enable_ipi(); ecfg_ipi_enable(); clock_cpucfg_dump(); // timer_test_tick(); @@ -155,6 +155,27 @@ pub fn clear_hwi_injected_irq() { tcfg::set_en(false); // stop timer } +pub fn clear_injected_irq(_irq: usize) { + debug!("loongarch64: clear_injected_irq: _irq: {}", _irq); + if _irq > INT_IPI { + error!("loongarch64: clear_injected_irq: _irq > {}, not valid", INT_IPI); + return; + } + let bit = 1 << _irq; + if _irq >= INT_HWI0 && _irq <= INT_HWI7 { + use crate::arch::register::gintc; + gintc::set_hwis(gintc::read().hwis() & !(bit >> INT_HWI0)); + } else { + let mut gcsr_estat = read_gcsr_estat(); + gcsr_estat &= !bit; + write_gcsr_estat(gcsr_estat); + } + + let mut status = GLOBAL_IRQ_INJECT_STATUS.lock(); + status.cpu_status[this_cpu_id()].status = InjectionStatus::Idle; + tcfg::set_en(false); +} + pub fn clear_hwi_injected_irq_if_needed() { { let status = GLOBAL_IRQ_INJECT_STATUS.lock(); diff --git a/src/event.rs b/src/event.rs index 7b147e161..700eeed61 100644 --- a/src/event.rs +++ b/src/event.rs @@ -16,7 +16,7 @@ #![allow(unused)] use crate::{ arch::cpu::this_cpu_id, - arch::ipi::{arch_check_events, arch_prepare_send_event, arch_send_event}, + arch::ipi::{arch_check_events, arch_notify_event}, consts::{ IPI_EVENT_CLEAR_INJECT_IRQ, IPI_EVENT_DWC_MSI_INJECT, IPI_EVENT_SEND_IPI, IPI_EVENT_UPDATE_HART_LINE, IPI_EVENT_VCPU_SUSPEND, MAX_CPU_NUM, @@ -50,17 +50,20 @@ fn get_percpu_events(cpu: usize) -> &'static Mutex> { unsafe { PERCPU_EVENTS.remote_ref_raw(cpu) } } -fn add_event(cpu: usize, event_id: usize) -> Option<()> { +/// Enqueue an event and report whether the target queue was previously empty. +/// Architectures can use the transition to coalesce their notification mechanism. +fn add_event(cpu: usize, event_id: usize) -> Option { if cpu >= MAX_CPU_NUM { return None; } let mut e = get_percpu_events(cpu).lock(); + let was_empty = e.is_empty(); if event_id == IPI_EVENT_SHUTDOWN { // If the event is shutdown, we need to clear all previous events, because shutdown will make cpu idle and won't process any events. e.clear(); } e.push_back(event_id); - Some(()) + Some(was_empty) } pub fn fetch_event(cpu: usize) -> Option { @@ -93,9 +96,8 @@ pub fn clear_events(cpu: usize) { get_percpu_events(cpu).lock().clear(); } -pub fn check_events() -> bool { +fn handle_event(event: Option) -> bool { let cpu_data = this_cpu_data(); - let event = fetch_event(cpu_data.id); match event { Some(IPI_EVENT_WAKEUP) => { cpu_data.arch_cpu.run(); @@ -149,47 +151,28 @@ pub fn check_events() -> bool { vcpu_suspend(); true } - // #[cfg(target_arch = "loongarch64")] - // Some(IPI_EVENT_CLEAR_INJECT_IRQ) => { - // use crate::device::irqchip; - // irqchip::ls7a2000::clear_hwi_injected_irq(); - // true - // } - // #[cfg(all(target_arch = "riscv64", plic))] - // Some(IPI_EVENT_UPDATE_HART_LINE) => { - // use crate::device::irqchip; - // info!("cpu {} update hart line", cpu_data.id); - // irqchip::plic::update_hart_line(); - // true - // } - // #[cfg(target_arch = "riscv64")] - // Some(IPI_EVENT_SEND_IPI) => { - // // This event is different from events above, it is used to inject software interrupt. - // // While events above will inject external interrupt. - // use crate::arch::ipi::arch_ipi_handler; - // arch_ipi_handler(); - // true - // } _ => false, } } +pub fn check_events() -> bool { + handle_event(fetch_event(this_cpu_data().id)) +} + +/// Handle one queued event, returning whether an event was present. +pub fn handle_next_event() -> bool { + let event = fetch_event(this_cpu_data().id); + if event.is_none() { + return false; + } + handle_event(event); + true +} + pub fn send_event(cpu_id: usize, ipi_int_id: usize, event_id: usize) { - // #[cfg(target_arch = "loongarch64")] - // { - // // block until the previous event is processed, which means - // // the target queue is empty - // while !fetch_event(cpu_id).is_none() {} - // debug!( - // "loongarch64:: send_event: cpu_id: {}, ipi_int_id: {}, event_id: {}", - // cpu_id, ipi_int_id, event_id - // ); - // } - /// Some arch need do something before send event. - /// Currently, we are not passing parameters, and we will modify the function signature later as needed. - arch_prepare_send_event(cpu_id, ipi_int_id, event_id); - add_event(cpu_id, event_id); - arch_send_event(cpu_id as _, ipi_int_id as _); + if let Some(queue_was_empty) = add_event(cpu_id, event_id) { + arch_notify_event(cpu_id as _, ipi_int_id as _, event_id, queue_was_empty); + } } /// Send event to a cpu set (except self). diff --git a/src/hypercall/mod.rs b/src/hypercall/mod.rs index 0db51cb40..1e4bdada9 100644 --- a/src/hypercall/mod.rs +++ b/src/hypercall/mod.rs @@ -56,6 +56,9 @@ numeric_enum! { HvGetEcamBase = 9, } } +#[cfg(target_arch = "loongarch64")] +pub const SGI_IPI_ID: u64 = crate::arch::ipi::HVISOR_EVENT_DOORBELL as u64; +#[cfg(not(target_arch = "loongarch64"))] pub const SGI_IPI_ID: u64 = 7; pub type HyperCallResult = HvResult; From 9282cb3e3f70772477cf5ec5e032007dcf4b6df4 Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 17:35:56 +0800 Subject: [PATCH 3/6] loongarch64: preserve virtual guest IPI state Merge guest IPI action bits, publish blocking sends before return, and keep the virtual IPI line asserted until all pending actions are cleared. --- src/arch/loongarch64/ipi.rs | 3 +- src/arch/loongarch64/zone.rs | 84 +++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/arch/loongarch64/ipi.rs b/src/arch/loongarch64/ipi.rs index 4a1efcfd0..dc3dba4a8 100644 --- a/src/arch/loongarch64/ipi.rs +++ b/src/arch/loongarch64/ipi.rs @@ -220,8 +220,7 @@ pub fn arch_check_events(event: Option) { clear_hwi_injected_irq(); } Some(IPI_EVENT_SEND_IPI) => { - use crate::device::irqchip::ls7a2000::inject_irq; - inject_irq(12, false); + crate::arch::zone::sync_virtual_ipi_line(); } _ => { panic!("arch_check_events: unhandled event: {:?}", event); diff --git a/src/arch/loongarch64/zone.rs b/src/arch/loongarch64/zone.rs index 68627782c..5db3981d6 100644 --- a/src/arch/loongarch64/zone.rs +++ b/src/arch/loongarch64/zone.rs @@ -515,6 +515,19 @@ static VIRTUAL_IPI_MAILBOX: [AtomicU64; MAX_CPU_NUM * 4] = { const C: AtomicU64 = AtomicU64::new(0); [C; MAX_CPU_NUM * 4] }; +const IOCSR_IPI_SEND_BLOCKING: u32 = 1 << 31; + +pub fn reset_virtual_ipi_state(cpu: usize) { + if cpu >= MAX_CPU_NUM { + return; + } + + VIRTUAL_IPI_STATUS[cpu].store(0, Ordering::Release); + VIRTUAL_IPI_ENABLE[cpu].store(0xffff_ffff, Ordering::Release); + for mailbox in 0..4 { + VIRTUAL_IPI_MAILBOX[cpu * 4 + mailbox].store(0, Ordering::Release); + } +} fn handle_uart_mmio(mmio: &mut MMIOAccess, base_addr: usize) -> HvResult { mmio_perform_access(base_addr, mmio); @@ -634,7 +647,26 @@ fn guest_cpu_to_physical_cpu(guest_cpu: usize) -> Option { .and_then(|zone| zone.read().guest_to_phys_cpu(guest_cpu)) } -fn virtual_ipi_send(target_guest_cpu: usize, ipi_bits: u32) -> HvResult { +pub fn sync_virtual_ipi_line() { + let cpu = this_cpu_id(); + let status = VIRTUAL_IPI_STATUS[cpu].load(Ordering::Acquire); + + if status != 0 { + crate::device::irqchip::ls7a2000::inject_irq(12, false); + } else { + crate::device::irqchip::ls7a2000::clear_injected_irq(12); + } +} + +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); + } +} + +fn virtual_ipi_send(target_guest_cpu: usize, ipi_bits: u32, blocking: bool) -> HvResult { let Some(target_cpu) = guest_cpu_to_physical_cpu(target_guest_cpu) else { warn!( "loongarch64: guest IPI target vCPU {} out of range", @@ -664,19 +696,26 @@ fn virtual_ipi_send(target_guest_cpu: usize, ipi_bits: u32) -> HvResult { } let pending = ipi_bits & !(SMP_BOOT_CPU as u32); - if pending != 0 { - debug!( - "loongarch64: guest IPI send cpu{} -> cpu{}, bits={:#x}", - this_guest_cpu_id(), - target_guest_cpu, - pending - ); - VIRTUAL_IPI_STATUS[target_cpu].fetch_or(pending, Ordering::AcqRel); - if target_cpu == this_cpu_id() { - crate::device::irqchip::ls7a2000::inject_irq(12, false); - } else { - send_event(target_cpu, SGI_IPI_ID as usize, IPI_EVENT_SEND_IPI); + if pending == 0 { + if blocking { + fence(Ordering::SeqCst); } + return Ok(()); + } + + // Match the in-kernel LoongArch IPI model: commit the status update before + // returning from the send write, merge repeated actions, and only notify on + // the zero-to-nonzero transition. Bit 31 does not wait for guest IPI_CLEAR. + let old = VIRTUAL_IPI_STATUS[target_cpu].fetch_or(pending, Ordering::AcqRel); + let new = old | pending; + if old == 0 && new != 0 { + sync_or_notify_virtual_ipi_line(target_cpu); + } + + if blocking { + // The emulated send is synchronous: the target status and event queue + // are globally visible before the trapped IOCSR write completes. + fence(Ordering::SeqCst); } Ok(()) @@ -695,7 +734,8 @@ fn handle_ipi_any_send_mmio(mmio: &mut MMIOAccess) -> HvResult { let value = mmio.value as u32; let ipi_id = value & 0x1f; let target_guest_cpu = ((value >> 16) & 0x3ff) as usize; - virtual_ipi_send(target_guest_cpu, 1u32 << ipi_id) + let blocking = value & IOCSR_IPI_SEND_BLOCKING != 0; + virtual_ipi_send(target_guest_cpu, 1u32 << ipi_id, blocking) } fn handle_ipi_mail_send_mmio(mmio: &mut MMIOAccess) -> HvResult { @@ -790,17 +830,18 @@ fn handle_ipi_mmio(mmio: &mut MMIOAccess) -> HvResult { } IPI_SET => { if mmio.is_write { - virtual_ipi_send(guest_cpu, mmio.value as u32)?; + virtual_ipi_send(guest_cpu, mmio.value as u32, false)?; } else { mmio.value = 0; } } IPI_CLEAR => { if mmio.is_write { - let old = VIRTUAL_IPI_STATUS[cpu].load(Ordering::Acquire); - VIRTUAL_IPI_STATUS[cpu].fetch_and(!(mmio.value as u32), Ordering::AcqRel); - if cpu == this_cpu_id() && (old & mmio.value as u32) != 0 { - crate::device::irqchip::ls7a2000::clear_injected_irq(12); + let mask = mmio.value as u32; + let old = VIRTUAL_IPI_STATUS[cpu].fetch_and(!mask, Ordering::AcqRel); + let new = old & !mask; + if old != 0 && new == 0 { + sync_or_notify_virtual_ipi_line(cpu); } if old != 0 || mmio.value != 0 { debug!( @@ -953,7 +994,10 @@ impl Zone { Ok(()) } - pub fn arch_zone_reset(&mut self, _config: &HvZoneConfig) -> HvResult { + pub fn arch_zone_reset(&mut self, config: &HvZoneConfig) -> HvResult { + for cpu in config.cpus().iter() { + reset_virtual_ipi_state(*cpu as usize); + } Ok(()) } } From 0287e9ebd4a87ee0672936992a4db553176e0890 Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 17:40:32 +0800 Subject: [PATCH 4/6] loongarch64: track guest HWI lines by level Maintain asserted HWI bits per physical CPU, update only GINTC.VIP, preserve PIP and HC, and remove timer-driven global injection cleanup. --- src/arch/loongarch64/hypercall.rs | 8 -- src/arch/loongarch64/ipi.rs | 4 +- src/arch/loongarch64/register/gintc.rs | 36 +++++-- src/arch/loongarch64/trap.rs | 1 - src/device/irqchip/ls7a2000/mod.rs | 142 ++++++++++++------------- src/device/irqchip/mod.rs | 10 ++ src/device/virtio_trampoline.rs | 9 +- src/event.rs | 11 +- src/hypercall/mod.rs | 55 ++++++---- 9 files changed, 162 insertions(+), 114 deletions(-) diff --git a/src/arch/loongarch64/hypercall.rs b/src/arch/loongarch64/hypercall.rs index 53e46e88d..8f372ecbb 100644 --- a/src/arch/loongarch64/hypercall.rs +++ b/src/arch/loongarch64/hypercall.rs @@ -17,7 +17,6 @@ use crate::arch::cpu::this_cpu_id; use crate::config::HvZoneConfig; use crate::config::CONFIG_MAGIC_VERSION; -use crate::device::virtio_trampoline::MAX_DEVS; use crate::hypercall::HyperCall; use crate::hypercall::HyperCallResult; impl<'a> HyperCall<'a> { @@ -30,13 +29,6 @@ impl<'a> HyperCall<'a> { HyperCallResult::Ok(0) } - pub fn wait_for_interrupt(&mut self, irq_list: &mut [u64; MAX_DEVS + 1]) { - use crate::device::irqchip::ls7a2000::*; - let status = GLOBAL_IRQ_INJECT_STATUS.lock(); - drop(status); - irq_list[0] = 0; // CAUTION: this is a workaround for loongarch64 - } - pub fn hv_zone_config_check(&self, magic_version: *mut u64) -> HyperCallResult { let magic_version_raw = magic_version as u64; let magic_version_hva = magic_version_raw | crate::arch::mm::LOONGARCH64_CACHED_DMW_PREFIX; diff --git a/src/arch/loongarch64/ipi.rs b/src/arch/loongarch64/ipi.rs index dc3dba4a8..54dab3653 100644 --- a/src/arch/loongarch64/ipi.rs +++ b/src/arch/loongarch64/ipi.rs @@ -215,9 +215,7 @@ pub fn ecfg_ipi_disable() { pub fn arch_check_events(event: Option) { match event { Some(IPI_EVENT_CLEAR_INJECT_IRQ) => { - // clear the injected IPI interrupt - use crate::device::irqchip::ls7a2000::clear_hwi_injected_irq; - clear_hwi_injected_irq(); + warn!("legacy CLEAR_INJECT_IRQ event ignored; use the per-IRQ line API"); } Some(IPI_EVENT_SEND_IPI) => { crate::arch::zone::sync_virtual_ipi_line(); diff --git a/src/arch/loongarch64/register/gintc.rs b/src/arch/loongarch64/register/gintc.rs index 26e3ece2c..3cf968d49 100644 --- a/src/arch/loongarch64/register/gintc.rs +++ b/src/arch/loongarch64/register/gintc.rs @@ -20,25 +20,49 @@ impl_define_csr!(Gintc, "GINTC"); impl_read_csr!(0x52, Gintc); impl Gintc { - pub fn hwis(&self) -> usize { + pub fn vip(&self) -> usize { self.bits.get_bits(0..=7) } - pub fn hwip(&self) -> usize { + pub fn pip(&self) -> usize { self.bits.get_bits(8..=15) } - pub fn hwic(&self) -> usize { + pub fn hc(&self) -> usize { self.bits.get_bits(16..=23) } + + pub fn hwis(&self) -> usize { + self.vip() + } + + pub fn hwip(&self) -> usize { + self.pip() + } + + pub fn hwic(&self) -> usize { + self.hc() + } +} + +pub fn write_vip(vip: usize) { + set_csr_loong_bits!(0x52, 0..=7, vip); +} + +pub fn write_pip(pip: usize) { + set_csr_loong_bits!(0x52, 8..=15, pip); +} + +pub fn write_hc(hc: usize) { + set_csr_loong_bits!(0x52, 16..=23, hc); } pub fn set_hwis(hwis: usize) { - set_csr_loong_bits!(0x52, 0..=7, hwis); + write_vip(hwis); } pub fn set_hwip(hwip: usize) { - set_csr_loong_bits!(0x52, 8..=15, hwip); + write_pip(hwip); } pub fn set_hwic(hwic: usize) { - set_csr_loong_bits!(0x52, 16..=23, hwic); + write_hc(hwic); } diff --git a/src/arch/loongarch64/trap.rs b/src/arch/loongarch64/trap.rs index a98fe2cec..5087c8faf 100644 --- a/src/arch/loongarch64/trap.rs +++ b/src/arch/loongarch64/trap.rs @@ -1305,7 +1305,6 @@ fn handle_interrupt(is: usize) { if is & TIMER_BIT != 0 { debug!("Timer interrupt received"); loongArch64::register::ticlr::clear_timer_interrupt(); - crate::device::irqchip::ls7a2000::clear_hwi_injected_irq_if_needed(); return; } diff --git a/src/device/irqchip/ls7a2000/mod.rs b/src/device/irqchip/ls7a2000/mod.rs index d37e432c7..cb9f05346 100644 --- a/src/device/irqchip/ls7a2000/mod.rs +++ b/src/device/irqchip/ls7a2000/mod.rs @@ -24,11 +24,12 @@ use crate::{ register::{read_gcsr_estat, write_gcsr_estat}, }, consts::MAX_CPU_NUM, + event::{send_event, IPI_EVENT_VIRTIO_INJECT_IRQ}, + hypercall::SGI_IPI_ID, zone::Zone, }; use chip::*; -use loongArch64::register::tcfg; -use spin::Mutex; +use core::sync::atomic::{AtomicU32, Ordering}; pub mod chip; @@ -106,6 +107,65 @@ const INT_PERF: usize = 10; const INT_TIMER: usize = 11; const INT_IPI: usize = 12; +static GUEST_HWI_ASSERTED: [AtomicU32; MAX_CPU_NUM] = { + const C: AtomicU32 = AtomicU32::new(0); + [C; MAX_CPU_NUM] +}; + +pub fn set_guest_irq_line(cpu: usize, irq: usize, asserted: bool) -> bool { + if cpu >= MAX_CPU_NUM || !(INT_HWI0..=INT_HWI7).contains(&irq) { + error!( + "loongarch64: invalid guest IRQ line: cpu={} irq={} asserted={}", + cpu, irq, asserted + ); + return false; + } + + let mask = 1u32 << (irq - INT_HWI0); + let state = &GUEST_HWI_ASSERTED[cpu]; + let old = if asserted { + state.fetch_or(mask, Ordering::AcqRel) + } else { + state.fetch_and(!mask, Ordering::AcqRel) + }; + let new = if asserted { old | mask } else { old & !mask }; + if old == new { + return true; + } + + if cpu == this_cpu_id() { + sync_guest_irqs(); + } else { + send_event(cpu, SGI_IPI_ID as usize, IPI_EVENT_VIRTIO_INJECT_IRQ); + } + true +} + +pub fn clear_guest_irq_lines(cpu: usize) { + if cpu >= MAX_CPU_NUM { + return; + } + let old = GUEST_HWI_ASSERTED[cpu].swap(0, Ordering::AcqRel); + if old == 0 { + return; + } + if cpu == this_cpu_id() { + sync_guest_irqs(); + } else { + send_event(cpu, SGI_IPI_ID as usize, IPI_EVENT_VIRTIO_INJECT_IRQ); + } +} + +pub fn sync_guest_irqs() { + let cpu = this_cpu_id(); + let desired_vip = GUEST_HWI_ASSERTED[cpu].load(Ordering::Acquire) as usize & 0xff; + use crate::arch::register::gintc; + let old_vip = gintc::read().vip(); + if old_vip != desired_vip { + gintc::write_vip(desired_vip); + } +} + /// inject irq to THIS cpu pub fn inject_irq(_irq: usize, is_hardware: bool) { debug!( @@ -118,73 +178,32 @@ pub fn inject_irq(_irq: usize, is_hardware: bool) { } let bit = 1 << _irq; if _irq >= INT_HWI0 && _irq <= INT_HWI7 { - // use gintc to inject - use crate::arch::register::gintc; - gintc::set_hwis(bit >> INT_HWI0); + set_guest_irq_line(this_cpu_id(), _irq, true); } else { // use gcsr to inject, just set the bit let mut gcsr_estat = read_gcsr_estat(); gcsr_estat |= bit; write_gcsr_estat(gcsr_estat); } - let mut status = GLOBAL_IRQ_INJECT_STATUS.lock(); - status.cpu_status[this_cpu_id()].status = InjectionStatus::Injecting; - - tcfg::set_en(true); // start timer to avoid endless timer injection - // please only enable this for debugging because it may cause overheads for realtime nonroots -} - -/// clear the injecting irq ctrl bit on THIS cpu -pub fn clear_hwi_injected_irq() { - use crate::arch::register::gintc; - gintc::set_hwis(0); - // gintc::set_hwip(0); - // gintc::set_hwic(0xff); - let mut gintc_raw = 0usize; - use core::arch::asm; - unsafe { - asm!("csrrd {0}, 0x52", out(reg) gintc_raw); - } - debug!( - "loongarch64: clear_hwi_injected_irq: current gintc: {:#x}", - gintc_raw - ); - let mut status = GLOBAL_IRQ_INJECT_STATUS.lock(); - status.cpu_status[this_cpu_id()].status = InjectionStatus::Idle; - - tcfg::set_en(false); // stop timer } pub fn clear_injected_irq(_irq: usize) { debug!("loongarch64: clear_injected_irq: _irq: {}", _irq); if _irq > INT_IPI { - error!("loongarch64: clear_injected_irq: _irq > {}, not valid", INT_IPI); + error!( + "loongarch64: clear_injected_irq: _irq > {}, not valid", + INT_IPI + ); return; } let bit = 1 << _irq; if _irq >= INT_HWI0 && _irq <= INT_HWI7 { - use crate::arch::register::gintc; - gintc::set_hwis(gintc::read().hwis() & !(bit >> INT_HWI0)); + set_guest_irq_line(this_cpu_id(), _irq, false); } else { let mut gcsr_estat = read_gcsr_estat(); gcsr_estat &= !bit; write_gcsr_estat(gcsr_estat); } - - let mut status = GLOBAL_IRQ_INJECT_STATUS.lock(); - status.cpu_status[this_cpu_id()].status = InjectionStatus::Idle; - tcfg::set_en(false); -} - -pub fn clear_hwi_injected_irq_if_needed() { - { - let status = GLOBAL_IRQ_INJECT_STATUS.lock(); - if status.cpu_status[this_cpu_id()].status != InjectionStatus::Injecting { - tcfg::set_en(false); - return; - } - } - clear_hwi_injected_irq(); } impl Zone { @@ -198,28 +217,3 @@ impl Zone { ); } } - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum InjectionStatus { - Injecting, - Idle, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct PercpuInjectionStatus { - pub status: InjectionStatus, - pub irqs: [u32; 32], -} - -#[derive(Debug)] -pub struct GlobalInjectionStatus { - pub cpu_status: [PercpuInjectionStatus; MAX_CPU_NUM], -} - -pub static GLOBAL_IRQ_INJECT_STATUS: Mutex = - Mutex::new(GlobalInjectionStatus { - cpu_status: [PercpuInjectionStatus { - status: InjectionStatus::Idle, - irqs: [0; 32], - }; MAX_CPU_NUM], - }); diff --git a/src/device/irqchip/mod.rs b/src/device/irqchip/mod.rs index c32b0e3cb..baa707747 100644 --- a/src/device/irqchip/mod.rs +++ b/src/device/irqchip/mod.rs @@ -17,6 +17,16 @@ use crate::arch::zone::HvArchZoneConfig; use crate::config::HvZoneConfig; use crate::zone::Zone; +#[cfg(target_arch = "loongarch64")] +pub fn handle_virtio_irq_event() { + ls7a2000::sync_guest_irqs(); +} + +#[cfg(not(target_arch = "loongarch64"))] +pub fn handle_virtio_irq_event() { + crate::device::virtio_trampoline::handle_virtio_irq(); +} + #[cfg(all(irq_gicv2, target_arch = "aarch64"))] pub mod gicv2; #[cfg(all(irq_gicv2, target_arch = "aarch64"))] diff --git a/src/device/virtio_trampoline.rs b/src/device/virtio_trampoline.rs index 4d4af6bbf..c4d77dfc5 100644 --- a/src/device/virtio_trampoline.rs +++ b/src/device/virtio_trampoline.rs @@ -20,6 +20,8 @@ #![deny(unused_mut)] #![deny(unused)] +#[cfg(not(target_arch = "loongarch64"))] +use crate::device::irqchip::inject_irq; #[cfg(not(target_arch = "loongarch64"))] use crate::{ arch::cpu::get_target_cpu, @@ -27,9 +29,10 @@ use crate::{ hypercall::SGI_IPI_ID, }; use crate::{ - arch::cpu::this_cpu_id, consts::MAX_WAIT_TIMES, device::irqchip::inject_irq, error::HvResult, - memory::MMIOAccess, zone::this_zone_id, + arch::cpu::this_cpu_id, consts::MAX_WAIT_TIMES, error::HvResult, memory::MMIOAccess, + zone::this_zone_id, }; +#[cfg(not(target_arch = "loongarch64"))] use alloc::collections::BTreeMap; use core::{ fmt::{Debug, Formatter, Result}, @@ -43,6 +46,7 @@ use tock_registers::{ }; /// Save the irqs the virtio-device wants to inject. The format is >, and the first elem of List is the valid len of it. +#[cfg(not(target_arch = "loongarch64"))] pub static VIRTIO_IRQS: Mutex> = Mutex::new(BTreeMap::new()); // Controller of the shared memory the root linux's virtio device and hvisor shares. pub static VIRTIO_PCI_BRIDGE: Mutex = Mutex::new(VirtioPCIBridge::dummy()); @@ -157,6 +161,7 @@ pub fn check_need_wakeup_and_send_ipi(is_send_ipi: &mut bool) { /// When virtio req type is notify, root zone will send sgi to non root, \ /// and non root will call this function. +#[cfg(not(target_arch = "loongarch64"))] pub fn handle_virtio_irq() { let mut map = VIRTIO_IRQS.lock(); let irq_list = map.get_mut(&this_cpu_id()).unwrap(); diff --git a/src/event.rs b/src/event.rs index 700eeed61..6c3947e37 100644 --- a/src/event.rs +++ b/src/event.rs @@ -22,7 +22,7 @@ use crate::{ IPI_EVENT_UPDATE_HART_LINE, IPI_EVENT_VCPU_SUSPEND, MAX_CPU_NUM, }, cpu_data::{this_cpu_data, vcpu_suspend, CpuSet}, - device::{irqchip::inject_irq, virtio_trampoline::handle_virtio_irq}, + device::irqchip::{handle_virtio_irq_event, inject_irq}, platform::IRQ_WAKEUP_VIRTIO_DEVICE, }; #[cfg(virtio_pci)] @@ -104,11 +104,18 @@ fn handle_event(event: Option) -> bool { false } Some(IPI_EVENT_SHUTDOWN) => { + #[cfg(target_arch = "loongarch64")] + { + // Shutdown coalescing may have discarded the queued IRQ-sync + // event. Apply the cleared software bitmap before idling so the + // next zone cannot inherit stale GINTC.VIP bits. + crate::device::irqchip::ls7a2000::sync_guest_irqs(); + } cpu_data.arch_cpu.idle(); false } Some(IPI_EVENT_VIRTIO_INJECT_IRQ) => { - handle_virtio_irq(); + handle_virtio_irq_event(); true } Some(IPI_EVENT_WAKEUP_VIRTIO_DEVICE) => { diff --git a/src/hypercall/mod.rs b/src/hypercall/mod.rs index 1e4bdada9..323356ede 100644 --- a/src/hypercall/mod.rs +++ b/src/hypercall/mod.rs @@ -21,9 +21,10 @@ use crate::config::{HvZoneBootMode, HvZoneConfig}; use crate::consts::{INVALID_ADDRESS, MAX_CPU_NUM, MAX_WAIT_TIMES, PAGE_SIZE}; use crate::cpu_data::{get_cpu_data, PerCpu, VcpuState}; use crate::device::virtio_trampoline::{ - VirtioPCIHypercallOp, MAX_DEVS, VIRTIO_BRIDGE, VIRTIO_IRQS, VIRTIO_PCI_BRIDGE, - VIRTIO_PCI_HYPERCALL_VERSION, + VirtioPCIHypercallOp, VIRTIO_BRIDGE, VIRTIO_PCI_BRIDGE, VIRTIO_PCI_HYPERCALL_VERSION, }; +#[cfg(not(target_arch = "loongarch64"))] +use crate::device::virtio_trampoline::{MAX_DEVS, VIRTIO_IRQS}; use crate::error::HvResult; use crate::pci::pci_config::GLOBAL_PCIE_LIST; use crate::zone::{ @@ -31,10 +32,9 @@ use crate::zone::{ set_zone_boot_mode, zone_create, ZoneInfo, }; -use crate::event::{ - send_event, IPI_EVENT_SHUTDOWN, IPI_EVENT_VIRTIO_INJECT_IRQ, IPI_EVENT_VIRTIO_PCI_DONE, - IPI_EVENT_WAKEUP, -}; +#[cfg(not(target_arch = "loongarch64"))] +use crate::event::IPI_EVENT_VIRTIO_INJECT_IRQ; +use crate::event::{send_event, IPI_EVENT_SHUTDOWN, IPI_EVENT_VIRTIO_PCI_DONE, IPI_EVENT_WAKEUP}; use core::convert::TryFrom; use numeric_enum_macro::numeric_enum; @@ -155,6 +155,7 @@ impl<'a> HyperCall<'a> { ); } let mut res_agent = VIRTIO_BRIDGE.res_agent(); + #[cfg(not(target_arch = "loongarch64"))] let mut map_irq = VIRTIO_IRQS.lock(); while !res_agent.is_empty() { let (_res_front, irq_id, target_zone) = res_agent.peek_front(); @@ -166,21 +167,35 @@ impl<'a> HyperCall<'a> { } }; - let irq_list = map_irq.entry(target_cpu).or_insert([0; MAX_DEVS + 1]); - - self.wait_for_interrupt(irq_list); - if !irq_list[1..=irq_list[0] as usize].contains(&irq_id) { - let len = irq_list[0] as usize; - assert!(len + 1 < MAX_DEVS); - irq_list[len + 1] = irq_id; - irq_list[0] += 1; - send_event( - target_cpu as _, - SGI_IPI_ID as _, - IPI_EVENT_VIRTIO_INJECT_IRQ, + #[cfg(target_arch = "loongarch64")] + { + crate::device::irqchip::ls7a2000::set_guest_irq_line( + target_cpu, + irq_id as usize, + true, ); + res_agent.advance_front(); + continue; + } + + #[cfg(not(target_arch = "loongarch64"))] + { + let irq_list = map_irq.entry(target_cpu).or_insert([0; MAX_DEVS + 1]); + self.wait_for_interrupt(irq_list); + if !irq_list[1..=irq_list[0] as usize].contains(&irq_id) { + let len = irq_list[0] as usize; + assert!(len + 1 < MAX_DEVS); + irq_list[len + 1] = irq_id; + irq_list[0] += 1; + send_event( + target_cpu as _, + SGI_IPI_ID as _, + IPI_EVENT_VIRTIO_INJECT_IRQ, + ); + } } + #[cfg(not(target_arch = "loongarch64"))] res_agent.advance_front(); } drop(res_agent); @@ -279,6 +294,7 @@ impl<'a> HyperCall<'a> { return hv_result_err!(EINVAL); } // avoid virtio daemon send sgi to the shutdowning zone + #[cfg(not(target_arch = "loongarch64"))] let mut map_irq = VIRTIO_IRQS.lock(); let zone = match find_zone(zone_id as _) { @@ -295,8 +311,11 @@ impl<'a> HyperCall<'a> { zone_w.cpu_set().iter().for_each(|cpu_id| { let _lock = get_cpu_data(cpu_id).ctrl_lock.lock(); get_cpu_data(cpu_id).cpu_on_entry = INVALID_ADDRESS; + #[cfg(target_arch = "loongarch64")] + crate::device::irqchip::ls7a2000::clear_guest_irq_lines(cpu_id); send_event(cpu_id, SGI_IPI_ID as _, IPI_EVENT_SHUTDOWN); // set the virtio irq list's len to 0 + #[cfg(not(target_arch = "loongarch64"))] if let Some(irq_list) = map_irq.get_mut(&cpu_id) { irq_list[0] = 0; } From 5f7e395865c1cddf79bc45bd5131606702407ed8 Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 17:41:23 +0800 Subject: [PATCH 5/6] hypercall: deassert Virtio IRQs by zone and line Pass the target zone and IRQ through HvClearInjectIrq so backends can deassert one guest interrupt line without clearing unrelated pending interrupts. --- src/hypercall/mod.rs | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/hypercall/mod.rs b/src/hypercall/mod.rs index 323356ede..36587d3d8 100644 --- a/src/hypercall/mod.rs +++ b/src/hypercall/mod.rs @@ -18,7 +18,7 @@ use crate::arch::cpu::get_target_cpu; use crate::config::{HvZoneBootMode, HvZoneConfig}; -use crate::consts::{INVALID_ADDRESS, MAX_CPU_NUM, MAX_WAIT_TIMES, PAGE_SIZE}; +use crate::consts::{INVALID_ADDRESS, MAX_WAIT_TIMES, PAGE_SIZE}; use crate::cpu_data::{get_cpu_data, PerCpu, VcpuState}; use crate::device::virtio_trampoline::{ VirtioPCIHypercallOp, VIRTIO_BRIDGE, VIRTIO_PCI_BRIDGE, VIRTIO_PCI_HYPERCALL_VERSION, @@ -48,7 +48,7 @@ numeric_enum! { HvZoneStart = 2, HvZoneShutdown = 3, HvZoneList = 4, - HvClearInjectIrq = 20, + HvClearInjectIrq = 11, HvIvcInfo = 5, HvConfigCheck = 6, HvVirtioPCI = 7, @@ -95,15 +95,7 @@ impl<'a> HyperCall<'a> { HyperCallCode::HvZoneShutdown => self.hv_zone_shutdown(arg0), HyperCallCode::HvZoneList => self.hv_zone_list(&mut *(arg0 as *mut ZoneInfo), arg1), HyperCallCode::HvClearInjectIrq => { - use crate::consts::IPI_EVENT_CLEAR_INJECT_IRQ; - for i in 1..MAX_CPU_NUM { - // if target cpu status is not running, we skip it - if !get_cpu_data(i).vcpu_state.is_running() { - continue; - } - send_event(i, SGI_IPI_ID as _, IPI_EVENT_CLEAR_INJECT_IRQ); - } - HyperCallResult::Ok(0) + self.hv_virtio_deassert_irq(arg0 as usize, arg1 as usize) } HyperCallCode::HvIvcInfo => self.hv_ivc_info(arg0), HyperCallCode::HvConfigCheck => self.hv_zone_config_check(arg0 as *mut u64), @@ -238,6 +230,39 @@ impl<'a> HyperCall<'a> { HyperCallResult::Ok(0) } + fn hv_virtio_deassert_irq(&mut self, target_zone: usize, irq_id: usize) -> HyperCallResult { + if !is_this_root_zone() { + return hv_result_err!( + EPERM, + "Virtio deassert operation over non-root zones: unsupported!" + ); + } + if find_zone(target_zone).is_none() { + return hv_result_err!(EINVAL, format!("zone {} does not exist", target_zone)); + } + + let target_cpu = get_target_cpu(irq_id, target_zone); + #[cfg(target_arch = "loongarch64")] + { + if !crate::device::irqchip::ls7a2000::set_guest_irq_line(target_cpu, irq_id, false) { + return hv_result_err!( + EINVAL, + format!( + "invalid LoongArch guest HWI {} for zone {}", + irq_id, target_zone + ) + ); + } + return HyperCallResult::Ok(0); + } + + #[cfg(not(target_arch = "loongarch64"))] + hv_result_err!( + ENOSYS, + "per-IRQ Virtio deassert is not implemented on this architecture" + ) + } + pub fn hv_zone_start(&mut self, config: &HvZoneConfig, config_size: u64) -> HyperCallResult { let config_ipa = config as *const HvZoneConfig as u64; let config_pa = self.hv_get_real_pa(config_ipa); @@ -276,7 +301,6 @@ impl<'a> HyperCall<'a> { error!("hv_zone_start: cpu {} already on", boot_cpu); return hv_result_err!(EBUSY); }; - self.check_cpu_id(); add_zone(zone); drop(_lock); HyperCallResult::Ok(0) From c784eafae34b99dbb9a4097ead54ddba5e40442a Mon Sep 17 00:00:00 2001 From: li041 Date: Tue, 11 Aug 2026 22:10:08 +0800 Subject: [PATCH 6/6] hypercall: remove obsolete CPU ID checks --- src/arch/aarch64/hypercall.rs | 6 ------ src/arch/loongarch64/hypercall.rs | 6 ------ src/arch/riscv64/hypercall.rs | 6 ------ src/arch/x86_64/hypercall.rs | 6 ------ 4 files changed, 24 deletions(-) diff --git a/src/arch/aarch64/hypercall.rs b/src/arch/aarch64/hypercall.rs index 0ed5c3e67..2b641ab36 100644 --- a/src/arch/aarch64/hypercall.rs +++ b/src/arch/aarch64/hypercall.rs @@ -14,7 +14,6 @@ // Authors: // ForeverYolo <2572131118@qq.com> -use crate::arch::cpu::this_cpu_id; use crate::arch::ivc::{IvcInfo, IVC_INFOS}; use crate::config::CONFIG_MAGIC_VERSION; use crate::cpu_data::this_zone; @@ -76,11 +75,6 @@ impl<'a> HyperCall<'a> { return list_addr; } - pub fn check_cpu_id(&self) { - let cpuid = this_cpu_id(); - trace!("CPU ID: {} Start Zone", cpuid); - } - pub fn hv_virtio_get_irq(&self, _virtio_irq: *mut u32) -> HyperCallResult { trace!("hv_virtio_get_irq is not need for AArch64"); HyperCallResult::Ok(0) diff --git a/src/arch/loongarch64/hypercall.rs b/src/arch/loongarch64/hypercall.rs index 8f372ecbb..ad3ae2530 100644 --- a/src/arch/loongarch64/hypercall.rs +++ b/src/arch/loongarch64/hypercall.rs @@ -14,7 +14,6 @@ // Authors: // ForeverYolo <2572131118@qq.com> -use crate::arch::cpu::this_cpu_id; use crate::config::HvZoneConfig; use crate::config::CONFIG_MAGIC_VERSION; use crate::hypercall::HyperCall; @@ -54,11 +53,6 @@ impl<'a> HyperCall<'a> { return list_addr; } - pub fn check_cpu_id(&self) { - let cpuid = this_cpu_id(); - assert_eq!(cpuid, 0); - } - pub fn hv_virtio_get_irq(&self, virtio_irq: *mut u32) -> HyperCallResult { trace!("hv_virtio_get_irq is not need for LoongArch64"); HyperCallResult::Ok(0) diff --git a/src/arch/riscv64/hypercall.rs b/src/arch/riscv64/hypercall.rs index dac4a45cd..4228211c3 100644 --- a/src/arch/riscv64/hypercall.rs +++ b/src/arch/riscv64/hypercall.rs @@ -14,7 +14,6 @@ // Authors: // ForeverYolo <2572131118@qq.com> -use crate::arch::cpu::this_cpu_id; use crate::config::CONFIG_MAGIC_VERSION; use crate::device::virtio_trampoline::MAX_DEVS; use crate::hypercall::HyperCall; @@ -56,11 +55,6 @@ impl<'a> HyperCall<'a> { return list_addr; } - pub fn check_cpu_id(&self) { - let cpuid = this_cpu_id(); - trace!("CPU ID: {} Start Zone", cpuid); - } - pub fn hv_virtio_get_irq(&self, _virtio_irq: *mut u32) -> HyperCallResult { trace!("hv_virtio_get_irq is not need for RISC-V"); HyperCallResult::Ok(0) diff --git a/src/arch/x86_64/hypercall.rs b/src/arch/x86_64/hypercall.rs index ff0cdfa14..8a8953002 100644 --- a/src/arch/x86_64/hypercall.rs +++ b/src/arch/x86_64/hypercall.rs @@ -15,7 +15,6 @@ // Solicey use crate::{ - arch::cpu::this_cpu_id, config::CONFIG_MAGIC_VERSION, cpu_data::this_zone, device::virtio_trampoline::MAX_DEVS, @@ -76,11 +75,6 @@ impl<'a> HyperCall<'a> { HyperCallResult::Ok(0) } - pub fn check_cpu_id(&self) { - let cpuid = this_cpu_id(); - trace!("CPU ID: {} Start Zone", cpuid); - } - pub fn hv_virtio_get_irq(&self, virtio_irq: *mut u32) -> HyperCallResult { let virtio_irq = unsafe { this_zone()