From 0cf954801d2ddc9d377dd96c7f911574f93f5f84 Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Fri, 28 Jun 2024 09:42:46 +0000 Subject: [PATCH] Support sending IPI using APIC Co-authored-by: Chuandong Li --- ostd/src/arch/x86/kernel/apic/mod.rs | 194 +++++++++++++++++++++++- ostd/src/arch/x86/kernel/apic/x2apic.rs | 18 ++- ostd/src/arch/x86/kernel/apic/xapic.rs | 18 +++ 3 files changed, 227 insertions(+), 3 deletions(-) diff --git a/ostd/src/arch/x86/kernel/apic/mod.rs b/ostd/src/arch/x86/kernel/apic/mod.rs index e0a5108a..32ef2037 100644 --- a/ostd/src/arch/x86/kernel/apic/mod.rs +++ b/ostd/src/arch/x86/kernel/apic/mod.rs @@ -4,6 +4,7 @@ use alloc::sync::Arc; +use bit_field::BitField; use log::info; use spin::Once; @@ -14,6 +15,7 @@ pub mod x2apic; pub mod xapic; pub static APIC_INSTANCE: Once>> = Once::new(); +static APIC_TYPE: Once = Once::new(); pub trait Apic: ApicTimer + Sync + Send { fn id(&self) -> u32; @@ -22,6 +24,9 @@ pub trait Apic: ApicTimer + Sync + Send { /// End of Interrupt, this function will inform APIC that this interrupt has been processed. fn eoi(&mut self); + + /// Send a general inter-processor interrupt. + unsafe fn send_ipi(&mut self, icr: Icr); } pub trait ApicTimer: Sync + Send { @@ -43,6 +48,191 @@ pub trait ApicTimer: Sync + Send { fn set_timer_div_config(&mut self, div_config: DivideConfig); } +enum ApicType { + XApic, + X2Apic, +} + +/// The inter-processor interrupt control register. +/// +/// ICR is a 64-bit local APIC register that allows software running on the +/// porcessor to specify and send IPIs to other porcessors in the system. +/// To send an IPI, software must set up the ICR to indicate the type of IPI +/// message to be sent and the destination processor or processors. (All fields +/// of the ICR are read-write by software with the exception of the delivery +/// status field, which is read-only.) +/// +/// The act of writing to the low doubleword of the ICR causes the IPI to be +/// sent. Therefore, in xapic mode, high doubleword of the ICR needs to be written +/// first and then the low doubleword to ensure the correct interrupt is sent. +/// +/// The ICR consists of the following fields: +/// - **Bit 0-7** Vector :The vector number of the interrupt being sent. +/// - **Bit 8-10** Delivery Mode :Specifies the type of IPI to be sent. +/// - **Bit 11** Destination Mode :Selects either physical or logical destination mode. +/// - **Bit 12** Delivery Status(RO) :Indicates the IPI delivery status. +/// - **Bit 13** Reserved +/// - **Bit 14** Level :Only set 1 for the INIT level de-assert delivery mode. +/// - **Bit 15** Trigger Mode :Selects level or edge trigger mode. +/// - **Bit 16-17** Reserved +/// - **Bit 18-19** Destination Shorthand :Indicates destination set. +/// - **Bit 20-55** Reserved +/// - **Bit 56-63** Destination Field :Specifies the target processor or processors. +pub struct Icr(u64); + +impl Icr { + #[allow(clippy::too_many_arguments)] + pub fn new( + destination: ApicId, + destination_shorthand: DestinationShorthand, + trigger_mode: TriggerMode, + level: Level, + delivery_status: DeliveryStatus, + destination_mode: DestinationMode, + delivery_mode: DeliveryMode, + vector: u8, + ) -> Self { + let dest = match destination { + ApicId::XApic(d) => (d as u64) << 56, + ApicId::X2Apic(d) => (d as u64) << 32, + }; + Icr(dest + | (destination_shorthand as u64) << 18 + | (trigger_mode as u64) << 15 + | (level as u64) << 14 + | (delivery_status as u64) << 12 + | (destination_mode as u64) << 11 + | (delivery_mode as u64) << 8 + | (vector as u64)) + } + + /// Returns the lower 32 bits of the ICR. + pub fn lower(&self) -> u32 { + self.0 as u32 + } + + /// Returns the higher 32 bits of the ICR. + pub fn upper(&self) -> u32 { + (self.0 >> 32) as u32 + } +} + +/// The core identifier. ApicId can be divided into Physical ApicId and Logical ApicId. +/// The Physical ApicId is the value read from the LAPIC ID Register, while the Logical ApicId has different +/// encoding modes in XApic and X2Apic. +pub enum ApicId { + XApic(u8), + X2Apic(u32), +} + +impl ApicId { + /// Returns the logical x2apic ID. + /// + /// In x2APIC mode, the 32-bit logical x2APIC ID, which can be read from + /// LDR, is derived from the 32-bit local x2APIC ID: + /// Logical x2APIC ID = [(x2APIC ID[19:4] << 16) | (1 << x2APIC ID[3:0])] + pub fn x2apic_logical_id(&self) -> u32 { + self.x2apic_logical_cluster_id() << 16 | 1 << self.x2apic_logical_field_id() + } + + /// Returns the logical x2apic cluster ID. + /// + /// Logical cluster ID = x2APIC ID[19:4] + pub fn x2apic_logical_cluster_id(&self) -> u32 { + let apic_id = match *self { + ApicId::XApic(id) => id as u32, + ApicId::X2Apic(id) => id, + }; + apic_id.get_bits(4..=19) + } + + /// Returns the logical x2apic field ID. + /// + /// Specifically, the 16-bit logical ID sub-field is derived by the lowest + /// 4 bits of the x2APIC ID, i.e., + /// Logical field ID = x2APIC ID[3:0]. + pub fn x2apic_logical_field_id(&self) -> u32 { + let apic_id = match *self { + ApicId::XApic(id) => id as u32, + ApicId::X2Apic(id) => id, + }; + apic_id.get_bits(0..=3) + } +} + +impl From for ApicId { + fn from(value: u32) -> Self { + match APIC_TYPE.get().unwrap() { + ApicType::XApic => ApicId::XApic(value as u8), + ApicType::X2Apic => ApicId::X2Apic(value), + } + } +} + +/// Indicates whether a shorthand notation is used to specify the destination of +/// the interrupt and, if so, which shorthand is used. Destination shorthands are +/// used in place of the 8-bit destination field, and can be sent by software +/// using a single write to the low doubleword of the ICR. +/// +/// Shorthands are defined for the following cases: software self interrupt, IPIs +/// to all processors in the system including the sender, IPIs to all processors +/// in the system excluding the sender. +#[repr(u64)] +pub enum DestinationShorthand { + NoShorthand = 0b00, + MySelf = 0b01, + AllIncludingSelf = 0b10, + AllExcludingSelf = 0b11, +} + +#[repr(u64)] +pub enum TriggerMode { + Egde = 0, + Level = 1, +} + +#[repr(u64)] +pub enum Level { + Deassert = 0, + Assert = 1, +} + +/// Indicates the IPI delivery status (read only), as follows: +/// **0 (Idle)** Indicates that this local APIC has completed sending any previous IPIs. +/// **1 (Send Pending)** Indicates that this local APIC has not completed sending the last IPI. +#[repr(u64)] +pub enum DeliveryStatus { + Idle = 0, + SendPending = 1, +} + +#[repr(u64)] +pub enum DestinationMode { + Physical = 0, + Logical = 1, +} + +#[repr(u64)] +pub enum DeliveryMode { + /// Delivers the interrupt specified in the vector field to the target processor or processors. + Fixed = 0b000, + /// Same as fixed mode, except that the interrupt is delivered to the processor executing at + /// the lowest priority among the set of processors specified in the destination field. The + /// ability for a processor to send a lowest priority IPI is model specific and should be + /// avoided by BIOS and operating system software. + LowestPriority = 0b001, + /// Non-Maskable Interrupt + Smi = 0b010, + _Reserved = 0b011, + /// System Management Interrupt + Nmi = 0b100, + /// Delivers an INIT request to the target processor or processors, which causes them to + /// perform an initialization. + Init = 0b101, + /// Start-up Interrupt + StrartUp = 0b110, +} + #[derive(Debug)] pub enum ApicInitError { /// No x2APIC or xAPIC found. @@ -74,6 +264,7 @@ pub fn init() -> Result<(), ApicInitError> { (version >> 16) & 0xff ); APIC_INSTANCE.call_once(|| Arc::new(SpinLock::new(x2apic))); + APIC_TYPE.call_once(|| ApicType::X2Apic); Ok(()) } else if let Some(mut xapic) = xapic::XApic::new() { xapic.enable(); @@ -85,9 +276,10 @@ pub fn init() -> Result<(), ApicInitError> { (version >> 16) & 0xff ); APIC_INSTANCE.call_once(|| Arc::new(SpinLock::new(xapic))); + APIC_TYPE.call_once(|| ApicType::XApic); Ok(()) } else { - log::warn!("Not found x2APIC or xAPIC"); + log::warn!("Neither x2APIC nor xAPIC found!"); Err(ApicInitError::NoApic) } } diff --git a/ostd/src/arch/x86/kernel/apic/x2apic.rs b/ostd/src/arch/x86/kernel/apic/x2apic.rs index 77eeb3e3..d9189d1c 100644 --- a/ostd/src/arch/x86/kernel/apic/x2apic.rs +++ b/ostd/src/arch/x86/kernel/apic/x2apic.rs @@ -2,8 +2,8 @@ use x86::msr::{ rdmsr, wrmsr, IA32_APIC_BASE, IA32_X2APIC_APICID, IA32_X2APIC_CUR_COUNT, IA32_X2APIC_DIV_CONF, - IA32_X2APIC_EOI, IA32_X2APIC_INIT_COUNT, IA32_X2APIC_LVT_TIMER, IA32_X2APIC_SIVR, - IA32_X2APIC_VERSION, + IA32_X2APIC_EOI, IA32_X2APIC_ESR, IA32_X2APIC_ICR, IA32_X2APIC_INIT_COUNT, + IA32_X2APIC_LVT_TIMER, IA32_X2APIC_SIVR, IA32_X2APIC_VERSION, }; use super::ApicTimer; @@ -66,6 +66,20 @@ impl super::Apic for X2Apic { wrmsr(IA32_X2APIC_EOI, 0); } } + + unsafe fn send_ipi(&mut self, icr: super::Icr) { + wrmsr(IA32_X2APIC_ESR, 0); + wrmsr(IA32_X2APIC_ICR, icr.0); + loop { + let icr = rdmsr(IA32_X2APIC_ICR); + if (icr >> 12 & 0x1) == 0 { + break; + } + if rdmsr(IA32_X2APIC_ESR) > 0 { + break; + } + } + } } impl ApicTimer for X2Apic { diff --git a/ostd/src/arch/x86/kernel/apic/xapic.rs b/ostd/src/arch/x86/kernel/apic/xapic.rs index 4fb3d978..59175a21 100644 --- a/ostd/src/arch/x86/kernel/apic/xapic.rs +++ b/ostd/src/arch/x86/kernel/apic/xapic.rs @@ -74,6 +74,24 @@ impl super::Apic for XApic { fn eoi(&mut self) { self.write(xapic::XAPIC_EOI, 0); } + + unsafe fn send_ipi(&mut self, icr: super::Icr) { + self.write(xapic::XAPIC_ESR, 0); + // The upper 32 bits of ICR must be written into XAPIC_ICR1 first, + // because writing into XAPIC_ICR0 will trigger the action of + // interrupt sending. + self.write(xapic::XAPIC_ICR1, icr.upper()); + self.write(xapic::XAPIC_ICR0, icr.lower()); + loop { + let icr = self.read(xapic::XAPIC_ICR0); + if (icr >> 12 & 0x1) == 0 { + break; + } + if self.read(xapic::XAPIC_ESR) > 0 { + break; + } + } + } } impl ApicTimer for XApic {