ioapic.rs62.50%
1
// Copyright 2026 Google LLC2
//3
// Licensed under the Apache License, Version 2.0 (the "License");4
// you may not use this file except in compliance with the License.5
// You may obtain a copy of the License at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// distributed under the License is distributed on an "AS IS" BASIS,11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12
// See the License for the specific language governing permissions and13
// limitations under the License.14
15
//! Emulated x86 IO APIC device.16
//! See: https://download.intel.com/design/chipsets/datashts/29056601.pdf chapter 3.217
18
use parking_lot::Mutex;19
20
use crate::arch::x86_64::intr::{DestinationMode, MsiAddrLo, MsiData, TriggerMode};21
use crate::arch::x86_64::ioapic::{22
IOAPIC_VER, IOAPICARB, IOAPICID, IOAPICVER, IOREDTBL_BASE, IOREDTBL_MAX, IOREGSEL, IOWIN,23
NUM_PINS, RedirectEntry, RegId, RegVer,24
};25
use crate::arch::x86_64::layout::{APIC_START, IOAPIC_END, IOAPIC_START};26
use crate::device::{self, MmioDev, Pause};27
use crate::hv::MsiSender;28
use crate::mem;29
use crate::mem::emulated::{Action, Mmio};30
31
#[derive(Debug, Default)]32
struct IoApicRegs {33
id: RegId,34
redirtbl: [RedirectEntry; NUM_PINS as usize],35
select: u8,36
}37
38
#[derive(Debug)]39
pub struct IoApic<M: MsiSender> {40
regs: Mutex<IoApicRegs>,41
msi_sender: M,42
}43
44
impl<M: MsiSender> IoApic<M> {45
pub fn new(msi_sender: M) -> Self {24x46
Self {24x47
regs: Mutex::new(IoApicRegs::default()),24x48
msi_sender,24x49
}24x50
}24x51
52
pub fn service_pin(&self, pin: u8) -> crate::hv::Result<()> {12x53
let regs = self.regs.lock();12x54
let Some(entry) = regs.redirtbl.get(pin as usize) else {12x55
log::warn!("IOAPIC: invalid pin {pin}");56
return Ok(());57
};58
59
if entry.masked() {12x60
return Ok(());61
}12x62
63
if entry.dest_mode() == DestinationMode::LOGICAL.raw() {12x64
log::warn!("IOAPIC: logical destination is not supported");65
return Ok(());66
}12x67
if entry.trigger_mode() == TriggerMode::LEVEL.raw() {12x68
log::warn!("IOAPIC: level-triggered interrupts are not supported");69
return Ok(());70
}12x71
72
let mut addr_lo = MsiAddrLo(APIC_START as u32);12x73
addr_lo.set_dest_id(entry.dest_id());12x74
addr_lo.set_virt_dest_id_hi(entry.virt_dest_id_hi());12x75
76
let data = MsiData::new(12x77
entry.vector(),12x78
entry.delivery_mode(),12x79
false,80
entry.trigger_mode(),12x81
);82
83
self.msi_sender.send(addr_lo.0 as u64, data.0)12x84
}12x85
86
fn read_reg(&self, regs: &IoApicRegs) -> u32 {6x87
match regs.select {6x88
IOAPICID | IOAPICARB => regs.id.0,89
IOAPICVER => RegVer::new(IOAPIC_VER, NUM_PINS - 1).0,90
select @ IOREDTBL_BASE..=IOREDTBL_MAX => {6x91
let pin = ((select - IOREDTBL_BASE) >> 1) as usize;6x92
let Some(entry) = regs.redirtbl.get(pin) else {6x93
log::warn!("IOAPIC: read from unknown pin {pin:#x}");94
return 0;95
};96
if select % 2 == 0 {6x97
entry.0 as u323x98
} else {99
(entry.0 >> 32) as u323x100
}101
}102
unknown => {103
log::warn!("IOAPCI: read from unknown register {unknown:#x}");104
0105
}106
}107
}6x108
109
fn write_reg(&self, regs: &mut IoApicRegs, val: u32) {30x110
match regs.select {30x111
IOAPICID => regs.id.set_id(RegId(val).id()),112
IOAPICVER | IOAPICARB => log::warn!("IOAPIC: IOAPICVER and IOAPICARB are read-only"),113
select @ IOREDTBL_BASE..=IOREDTBL_MAX => {30x114
let pin = ((select - IOREDTBL_BASE) >> 1) as usize;30x115
let Some(entry) = regs.redirtbl.get_mut(pin) else {30x116
log::warn!("IOAPIC: write to unknown pin {pin:#x}");117
return;118
};119
entry.0 = if select % 2 == 0 {30x120
(entry.0 & 0xffffffff00000000) | (val as u64)15x121
} else {122
(entry.0 & 0x00000000ffffffff) | ((val as u64) << 32)15x123
};124
}125
unknown => {126
log::warn!("IOAPIC: write to unknown register {unknown:#x} with value {val:#x}");127
}128
}129
}30x130
}131
132
impl<M: MsiSender> Mmio for IoApic<M> {133
fn size(&self) -> u64 {134
IOAPIC_END - IOAPIC_START135
}136
137
fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {9x138
if size != 4 {9x139
log::warn!("IOAPIC: unaligned read: offset={offset:#x} size={size}");140
return Ok(0);141
}9x142
let regs = self.regs.lock();9x143
let val = match offset {9x144
IOREGSEL => regs.select as u32,3x145
IOWIN => self.read_reg(®s),6x146
_ => {147
log::warn!("IOAPIC: read from unknown offset {offset:#x}");148
0149
}150
};151
Ok(val as u64)9x152
}9x153
154
fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {60x155
if size != 4 {60x156
log::warn!("IOAPIC: unaligned write: offset={offset:#x} size={size}");157
return Ok(Action::None);158
}60x159
let mut regs = self.regs.lock();60x160
match offset {60x161
IOREGSEL => regs.select = val as u8,30x162
IOWIN => self.write_reg(&mut regs, val as u32),30x163
_ => {164
log::warn!("IOAPIC: write to unknown offset {offset:#x} with value {val:#x}");165
}166
}167
Ok(Action::None)60x168
}60x169
}170
171
impl<M: MsiSender> Pause for IoApic<M> {172
fn pause(&self) -> device::Result<()> {173
Ok(())174
}175
176
fn resume(&self) -> device::Result<()> {177
Ok(())178
}179
}180
181
impl<M: MsiSender> MmioDev for IoApic<M> {}182
183
#[cfg(test)]184
#[path = "ioapic_test.rs"]185
pub mod tests;186