Skip to main content

axdevice/
x86.rs

1//! AxVM-facing adapters for OS-neutral x86 virtual interrupt-controller devices.
2
3use alloc::{boxed::Box, string::String};
4use core::{any::Any, marker::PhantomData};
5
6use axdevice_base::{AccessWidth, BusAccess, BusKind, BusResponse, Device, DeviceError, Resource};
7use x86_vlapic::{
8    EmulatedIoApic, EmulatedPit, EmulatedSerialPort, IoApicEoi, IoApicInterrupt, X86AccessWidth,
9    X86GuestPhysAddr, X86GuestPhysAddrRange, X86Port, X86PortRange, X86VlapicHostOps,
10};
11
12/// Type-specific IOAPIC capability used by the x86 interrupt runtime.
13pub trait X86IoApicDeviceOps: Send + Sync {
14    /// Return the guest interrupt vector programmed for a GSI.
15    fn vector_for_gsi(&self, gsi: usize) -> Option<u8>;
16
17    /// Assert an IOAPIC GSI and return an interrupt to inject if one is unmasked.
18    fn assert_gsi(&self, gsi: usize) -> Option<IoApicInterrupt>;
19
20    /// Broadcast a local APIC EOI to the IOAPIC.
21    fn end_of_interrupt(&self, vector: u8) -> Option<IoApicEoi>;
22}
23
24/// Type-specific PIT capability used by the x86 interrupt runtime.
25pub trait X86PitDeviceOps: Send + Sync {
26    /// Consume a pending PIT IRQ0 tick if the deadline is due.
27    fn consume_irq0_if_due(&self, now_ns: u64) -> bool;
28}
29
30/// Type-specific COM1 capability used by the x86 interrupt runtime.
31pub trait X86SerialDeviceOps: Send + Sync {
32    /// Poll host input and return whether COM1 should assert an IRQ.
33    fn poll_irq(&self) -> bool;
34}
35
36/// Unified-device adapter for [`EmulatedIoApic`].
37pub struct X86IoApicDevice {
38    inner: EmulatedIoApic,
39    name: String,
40    resources: Box<[Resource]>,
41}
42
43impl X86IoApicDevice {
44    /// Creates an IOAPIC adapter with the given guest MMIO range.
45    pub fn new(base: X86GuestPhysAddr, size: Option<usize>) -> Self {
46        let inner = EmulatedIoApic::new(base, size);
47        let resources = mmio_resources(inner.address_range());
48        Self {
49            inner,
50            name: String::from("x86-ioapic"),
51            resources,
52        }
53    }
54
55    /// Returns the wrapped OS-neutral IOAPIC core.
56    pub const fn inner(&self) -> &EmulatedIoApic {
57        &self.inner
58    }
59}
60
61impl X86IoApicDeviceOps for X86IoApicDevice {
62    fn vector_for_gsi(&self, gsi: usize) -> Option<u8> {
63        self.inner.vector_for_gsi(gsi)
64    }
65
66    fn assert_gsi(&self, gsi: usize) -> Option<IoApicInterrupt> {
67        self.inner.assert_gsi(gsi)
68    }
69
70    fn end_of_interrupt(&self, vector: u8) -> Option<IoApicEoi> {
71        self.inner.end_of_interrupt(vector)
72    }
73}
74
75impl Device for X86IoApicDevice {
76    fn name(&self) -> &str {
77        &self.name
78    }
79
80    fn resources(&self) -> &[Resource] {
81        &self.resources
82    }
83
84    fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
85        if access.kind != BusKind::Mmio {
86            return Err(DeviceError::OutOfRange { addr: access.addr });
87        }
88        let addr = X86GuestPhysAddr::from_usize(access.addr as usize);
89        let width = x86_access_width(access.width);
90        if access.is_read {
91            self.inner
92                .handle_read(addr, width)
93                .map(|value| BusResponse::Read {
94                    value: value as u64,
95                })
96                .map_err(|_| DeviceError::Internal)
97        } else {
98            self.inner
99                .handle_write(addr, width, access.data as usize)
100                .map(|_| BusResponse::Write)
101                .map_err(|_| DeviceError::Internal)
102        }
103    }
104
105    fn as_any(&self) -> &dyn Any {
106        self
107    }
108}
109
110/// Unified-device adapter for [`EmulatedPit`].
111pub struct X86PitDevice<H: X86VlapicHostOps> {
112    inner: EmulatedPit<H>,
113    name: String,
114    resources: Box<[Resource]>,
115    _host: PhantomData<fn() -> H>,
116}
117
118impl<H: X86VlapicHostOps> X86PitDevice<H> {
119    /// Creates a PIT adapter.
120    pub fn new() -> Self {
121        let inner = EmulatedPit::<H>::new();
122        let resources = port_resources(inner.address_range());
123        Self {
124            inner,
125            name: String::from("x86-pit"),
126            resources,
127            _host: PhantomData,
128        }
129    }
130
131    /// Returns the wrapped OS-neutral PIT core.
132    pub const fn inner(&self) -> &EmulatedPit<H> {
133        &self.inner
134    }
135}
136
137impl<H: X86VlapicHostOps> Default for X86PitDevice<H> {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl<H: X86VlapicHostOps> X86PitDeviceOps for X86PitDevice<H> {
144    fn consume_irq0_if_due(&self, now_ns: u64) -> bool {
145        self.inner.consume_irq0_if_due(now_ns)
146    }
147}
148
149impl<H: X86VlapicHostOps + 'static> Device for X86PitDevice<H> {
150    fn name(&self) -> &str {
151        &self.name
152    }
153
154    fn resources(&self) -> &[Resource] {
155        &self.resources
156    }
157
158    fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
159        if access.kind != BusKind::Port {
160            return Err(DeviceError::OutOfRange { addr: access.addr });
161        }
162        let port = X86Port::new(
163            u16::try_from(access.addr)
164                .map_err(|_| DeviceError::OutOfRange { addr: access.addr })?,
165        );
166        let width = x86_access_width(access.width);
167        if access.is_read {
168            self.inner
169                .handle_read(port, width)
170                .map(|value| BusResponse::Read {
171                    value: value as u64,
172                })
173                .map_err(|_| DeviceError::Internal)
174        } else {
175            self.inner
176                .handle_write(port, width, access.data as usize)
177                .map(|_| BusResponse::Write)
178                .map_err(|_| DeviceError::Internal)
179        }
180    }
181
182    fn as_any(&self) -> &dyn Any {
183        self
184    }
185}
186
187/// Unified-device adapter for [`EmulatedSerialPort`].
188pub struct X86SerialPortDevice<H: X86VlapicHostOps> {
189    inner: EmulatedSerialPort<H>,
190    name: String,
191    resources: Box<[Resource]>,
192    _host: PhantomData<fn() -> H>,
193}
194
195impl<H: X86VlapicHostOps> X86SerialPortDevice<H> {
196    /// Creates a COM1 adapter.
197    pub fn new() -> Self {
198        let inner = EmulatedSerialPort::<H>::new();
199        let resources = port_resources(inner.address_range());
200        Self {
201            inner,
202            name: String::from("x86-serial-com1"),
203            resources,
204            _host: PhantomData,
205        }
206    }
207
208    /// Returns the wrapped OS-neutral COM1 core.
209    pub const fn inner(&self) -> &EmulatedSerialPort<H> {
210        &self.inner
211    }
212}
213
214impl<H: X86VlapicHostOps> Default for X86SerialPortDevice<H> {
215    fn default() -> Self {
216        Self::new()
217    }
218}
219
220impl<H: X86VlapicHostOps> X86SerialDeviceOps for X86SerialPortDevice<H> {
221    fn poll_irq(&self) -> bool {
222        self.inner.poll_irq()
223    }
224}
225
226impl<H: X86VlapicHostOps + 'static> Device for X86SerialPortDevice<H> {
227    fn name(&self) -> &str {
228        &self.name
229    }
230
231    fn resources(&self) -> &[Resource] {
232        &self.resources
233    }
234
235    fn handle(&self, access: &BusAccess) -> Result<BusResponse, DeviceError> {
236        if access.kind != BusKind::Port {
237            return Err(DeviceError::OutOfRange { addr: access.addr });
238        }
239        let port = X86Port::new(
240            u16::try_from(access.addr)
241                .map_err(|_| DeviceError::OutOfRange { addr: access.addr })?,
242        );
243        let width = x86_access_width(access.width);
244        if access.is_read {
245            self.inner
246                .handle_read(port, width)
247                .map(|value| BusResponse::Read {
248                    value: value as u64,
249                })
250                .map_err(|_| DeviceError::Internal)
251        } else {
252            self.inner
253                .handle_write(port, width, access.data as usize)
254                .map(|_| BusResponse::Write)
255                .map_err(|_| DeviceError::Internal)
256        }
257    }
258
259    fn as_any(&self) -> &dyn Any {
260        self
261    }
262}
263
264fn x86_access_width(width: AccessWidth) -> X86AccessWidth {
265    match width {
266        AccessWidth::Byte => X86AccessWidth::Byte,
267        AccessWidth::Word => X86AccessWidth::Word,
268        AccessWidth::Dword => X86AccessWidth::Dword,
269        AccessWidth::Qword => X86AccessWidth::Qword,
270    }
271}
272
273fn mmio_resources(range: X86GuestPhysAddrRange) -> Box<[Resource]> {
274    let base = range.start.as_usize() as u64;
275    let size = range.end.as_usize().saturating_sub(range.start.as_usize()) as u64;
276    alloc::vec![Resource::MmioRange { base, size }].into_boxed_slice()
277}
278
279fn port_resources(range: X86PortRange) -> Box<[Resource]> {
280    let base = range.start.number();
281    let size = range
282        .end
283        .number()
284        .saturating_sub(range.start.number())
285        .saturating_add(1);
286    alloc::vec![Resource::PortRange { base, size }].into_boxed_slice()
287}