Support sending IPI using APIC

Co-authored-by: Chuandong Li <lichuand@pku.edu.cn>
This commit is contained in:
Zhang Junyang 2024-06-28 09:42:46 +00:00 committed by Tate, Hongliang Tian
parent fef8eebadc
commit 0cf954801d
3 changed files with 227 additions and 3 deletions

View File

@ -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<Arc<SpinLock<dyn Apic + 'static>>> = Once::new();
static APIC_TYPE: Once<ApicType> = 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<u32> 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)
}
}

View File

@ -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 {

View File

@ -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 {