Alioth Code Coverage

ioapic.rs62.50%

1// Copyright 2026 Google LLC
2//
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 at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// 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 and
13// limitations under the License.
14
15//! Emulated x86 IO APIC device.
16//! See: https://download.intel.com/design/chipsets/datashts/29056601.pdf chapter 3.2
17
18use parking_lot::Mutex;
19
20use crate::arch::intr::{DestinationMode, MsiAddrLo, MsiData, TriggerMode};
21use crate::arch::ioapic::{
22 IOAPIC_VER, IOAPICARB, IOAPICID, IOAPICVER, IOREDTBL_BASE, IOREDTBL_MAX, IOREGSEL, IOWIN,
23 NUM_PINS, RedirectEntry, RegId, RegVer,
24};
25use crate::arch::layout::{APIC_START, IOAPIC_END, IOAPIC_START};
26use crate::device::{self, MmioDev, Pause};
27use crate::hv::MsiSender;
28use crate::mem;
29use crate::mem::emulated::{Action, Mmio};
30
31#[derive(Debug, Default)]
32struct IoApicRegs {
33 id: RegId,
34 redirtbl: [RedirectEntry; NUM_PINS as usize],
35 select: u8,
36}
37
38#[derive(Debug)]
39pub struct IoApic<M: MsiSender> {
40 regs: Mutex<IoApicRegs>,
41 msi_sender: M,
42}
43
44impl<M: MsiSender> IoApic<M> {
45 pub fn new(msi_sender: M) -> Self {2x
46 Self {2x
47 regs: Mutex::new(IoApicRegs::default()),2x
48 msi_sender,2x
49 }2x
50 }2x
51
52 pub fn service_pin(&self, pin: u8) -> crate::hv::Result<()> {1x
53 let regs = self.regs.lock();1x
54 let Some(entry) = regs.redirtbl.get(pin as usize) else {1x
55 log::warn!("IOAPIC: invalid pin {pin}");
56 return Ok(());
57 };
58
59 if entry.masked() {1x
60 return Ok(());
61 }1x
62
63 if entry.dest_mode() == DestinationMode::LOGICAL.raw() {1x
64 log::warn!("IOAPIC: logical destination is not supported");
65 return Ok(());
66 }1x
67 if entry.trigger_mode() == TriggerMode::LEVEL.raw() {1x
68 log::warn!("IOAPIC: level-triggered interrupts are not supported");
69 return Ok(());
70 }1x
71
72 let mut addr_lo = MsiAddrLo(APIC_START as u32);1x
73 addr_lo.set_dest_id(entry.dest_id());1x
74 addr_lo.set_virt_dest_id_hi(entry.virt_dest_id_hi());1x
75
76 let data = MsiData::new(1x
77 entry.vector(),1x
78 entry.delivery_mode(),1x
79 false,
80 entry.trigger_mode(),1x
81 );
82
83 self.msi_sender.send(addr_lo.0 as u64, data.0)1x
84 }1x
85
86 fn read_reg(&self, regs: &IoApicRegs) -> u32 {2x
87 match regs.select {2x
88 IOAPICID | IOAPICARB => regs.id.0,
89 IOAPICVER => RegVer::new(IOAPIC_VER, NUM_PINS - 1).0,
90 select @ IOREDTBL_BASE..=IOREDTBL_MAX => {2x
91 let pin = ((select - IOREDTBL_BASE) >> 1) as usize;2x
92 let Some(entry) = regs.redirtbl.get(pin) else {2x
93 log::warn!("IOAPIC: read from unknown pin {pin:#x}");
94 return 0;
95 };
96 if select % 2 == 0 {2x
97 entry.0 as u321x
98 } else {
99 (entry.0 >> 32) as u321x
100 }
101 }
102 unknown => {
103 log::warn!("IOAPCI: read from unknown register {unknown:#x}");
104 0
105 }
106 }
107 }2x
108
109 fn write_reg(&self, regs: &mut IoApicRegs, val: u32) {4x
110 match regs.select {4x
111 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 => {4x
114 let pin = ((select - IOREDTBL_BASE) >> 1) as usize;4x
115 let Some(entry) = regs.redirtbl.get_mut(pin) else {4x
116 log::warn!("IOAPIC: write to unknown pin {pin:#x}");
117 return;
118 };
119 entry.0 = if select % 2 == 0 {4x
120 (entry.0 & 0xffffffff00000000) | (val as u64)2x
121 } else {
122 (entry.0 & 0x00000000ffffffff) | ((val as u64) << 32)2x
123 };
124 }
125 unknown => {
126 log::warn!("IOAPIC: write to unknown register {unknown:#x} with value {val:#x}");
127 }
128 }
129 }4x
130}
131
132impl<M: MsiSender> Mmio for IoApic<M> {
133 fn size(&self) -> u64 {
134 IOAPIC_END - IOAPIC_START
135 }
136
137 fn read(&self, offset: u64, size: u8) -> mem::Result<u64> {3x
138 if size != 4 {3x
139 log::warn!("IOAPIC: unaligned read: offset={offset:#x} size={size}");
140 return Ok(0);
141 }3x
142 let regs = self.regs.lock();3x
143 let val = match offset {3x
144 IOREGSEL => regs.select as u32,1x
145 IOWIN => self.read_reg(&regs),2x
146 _ => {
147 log::warn!("IOAPIC: read from unknown offset {offset:#x}");
148 0
149 }
150 };
151 Ok(val as u64)3x
152 }3x
153
154 fn write(&self, offset: u64, size: u8, val: u64) -> mem::Result<Action> {8x
155 if size != 4 {8x
156 log::warn!("IOAPIC: unaligned write: offset={offset:#x} size={size}");
157 return Ok(Action::None);
158 }8x
159 let mut regs = self.regs.lock();8x
160 match offset {8x
161 IOREGSEL => regs.select = val as u8,4x
162 IOWIN => self.write_reg(&mut regs, val as u32),4x
163 _ => {
164 log::warn!("IOAPIC: write to unknown offset {offset:#x} with value {val:#x}");
165 }
166 }
167 Ok(Action::None)8x
168 }8x
169}
170
171impl<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
181impl<M: MsiSender> MmioDev for IoApic<M> {}
182
183#[cfg(test)]
184#[path = "ioapic_test.rs"]
185mod tests;
186