axvirtio_common/pci/transport/
mod.rs1use alloc::{format, sync::Arc};
10
11use ax_sync::SpinLock;
12use axdevice_base::{AccessWidth, DeviceError, DeviceResult};
13
14use crate::{
15 GuestMemory, NoGuestMemoryAccessor, VirtioDeviceID, VirtioError, VirtioQueue, map_virtio_error,
16 pci::{InterruptTransition, VirtioPciInterruptCoordinator},
17};
18
19mod access;
20mod queue;
21mod reset;
22mod state;
23mod transition;
24
25pub use state::ActivityPermit;
26use state::{QueueActivity, QueueState, TransportState};
27use transition::InterruptPublicationKind;
28pub use transition::{
29 InterruptPublicationRequest, InterruptTransitionIntent, InterruptTransitionRequest,
30 QueueNotification, VirtioQueueGeneration,
31};
32
33pub(super) const COMMON_CONFIG_SIZE: u64 = 0x38;
34pub(super) const NOTIFY_CONFIG_OFFSET: u64 = 0x100;
35pub(super) const ISR_CONFIG_OFFSET: u64 = 0x200;
36pub(super) const DEVICE_CONFIG_OFFSET: u64 = 0x300;
37pub(super) const RESET_DRAIN_SPIN_LIMIT: usize = 1 << 20;
38
39pub(super) const DEVICE_FEATURE_SELECT: u64 = 0x00;
40pub(super) const DEVICE_FEATURE: u64 = 0x04;
41pub(super) const DRIVER_FEATURE_SELECT: u64 = 0x08;
42pub(super) const DRIVER_FEATURE: u64 = 0x0c;
43pub(super) const MSIX_CONFIG: u64 = 0x10;
44pub(super) const NUM_QUEUES: u64 = 0x12;
45pub(super) const DEVICE_STATUS: u64 = 0x14;
46pub(super) const CONFIG_GENERATION: u64 = 0x15;
47pub(super) const QUEUE_SELECT: u64 = 0x16;
48pub(super) const QUEUE_SIZE: u64 = 0x18;
49pub(super) const QUEUE_MSIX_VECTOR: u64 = 0x1a;
50pub(super) const QUEUE_ENABLE: u64 = 0x1c;
51pub(super) const QUEUE_NOTIFY_OFF: u64 = 0x1e;
52pub(super) const QUEUE_DESC: u64 = 0x20;
53pub(super) const QUEUE_DRIVER: u64 = 0x28;
54pub(super) const QUEUE_DEVICE: u64 = 0x30;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum QueueNotifyOutcome {
59 Idle,
61 Completed {
63 notify: bool,
65 },
66 Deferred {
68 notify: bool,
70 },
71}
72
73pub trait VirtioDeviceCore: Send + Sync {
80 fn device_type(&self) -> VirtioDeviceID;
82
83 fn device_features(&self) -> u64;
85
86 fn queue_num_max(&self) -> u16 {
88 1
89 }
90
91 fn queue_size_max(&self) -> u16;
93
94 fn device_config_size(&self) -> u32;
96
97 fn read_device_config(&self, offset: u64, width: AccessWidth) -> DeviceResult<u64>;
99
100 fn write_device_config(&self, offset: u64, width: AccessWidth, value: u64) -> DeviceResult;
102
103 fn notify_queue(
105 &self,
106 queue: &mut VirtioQueue<NoGuestMemoryAccessor>,
107 memory: &mut dyn GuestMemory,
108 ) -> DeviceResult<QueueNotifyOutcome>;
109
110 fn requires_deferred_processing(&self) -> bool {
112 false
113 }
114
115 fn reset(&self) -> DeviceResult {
117 Ok(())
118 }
119}
120
121pub enum VirtioPciWriteOutcome {
123 None,
125 QueueNotified(QueueNotification),
127 Reset {
129 interrupt: InterruptTransition,
131 },
132 Fault {
134 error: DeviceError,
136 publication: InterruptPublicationRequest,
138 },
139}
140
141pub struct VirtioPciTransport<D: VirtioDeviceCore> {
143 core: D,
144 state: SpinLock<TransportState>,
145 interrupts: Arc<VirtioPciInterruptCoordinator>,
146 activity: Arc<QueueActivity>,
147 device_config_size: u32,
148 #[cfg(test)]
149 notify_admission_hook: SpinLock<Option<Arc<dyn Fn() + Send + Sync>>>,
150 #[cfg(test)]
151 reset_before_core_hook: SpinLock<Option<Arc<dyn Fn() + Send + Sync>>>,
152}
153
154impl<D: VirtioDeviceCore> VirtioPciTransport<D> {
155 pub fn try_new(core: D) -> DeviceResult<Self> {
164 let queue_num_max = core.queue_num_max();
165 let queue_size_max = core.queue_size_max();
166 if queue_num_max != 1 {
167 return Err(DeviceError::InvalidInput {
168 operation: "create VirtIO PCI transport",
169 detail: "commit 4 supports exactly one queue".into(),
170 });
171 }
172 if !queue_size_max.is_power_of_two() {
173 return Err(DeviceError::InvalidInput {
174 operation: "create VirtIO PCI transport",
175 detail: "queue size must be a power of two".into(),
176 });
177 }
178 if core.requires_deferred_processing() {
179 return Err(DeviceError::Unsupported {
180 operation: "create VirtIO PCI transport",
181 detail: "deferred queue processing is not supported by the synchronous PCI adapter"
182 .into(),
183 });
184 }
185 Ok(Self {
186 device_config_size: core.device_config_size(),
187 state: SpinLock::new(TransportState::new(queue_num_max, queue_size_max)),
188 interrupts: Arc::new(VirtioPciInterruptCoordinator::new()),
189 activity: Arc::new(QueueActivity::new()),
190 core,
191 #[cfg(test)]
192 notify_admission_hook: SpinLock::new(None),
193 #[cfg(test)]
194 reset_before_core_hook: SpinLock::new(None),
195 })
196 }
197
198 pub fn core(&self) -> &D {
200 &self.core
201 }
202
203 pub fn device_type(&self) -> VirtioDeviceID {
205 self.core.device_type()
206 }
207
208 pub fn device_features(&self) -> u64 {
210 self.core.device_features()
211 }
212
213 pub fn status(&self) -> u8 {
215 self.state.lock().status
216 }
217
218 pub fn driver_features(&self) -> u64 {
220 self.state.lock().driver_features
221 }
222
223 pub fn queue_generation(&self) -> VirtioQueueGeneration {
225 VirtioQueueGeneration(self.state.lock().queue_generation)
226 }
227
228 pub fn interrupt_pending(&self) -> bool {
230 self.interrupts.pending()
231 }
232
233 #[cfg(test)]
235 pub(crate) fn record_interrupt(&self, configuration_change: bool) -> InterruptTransition {
236 if configuration_change {
237 self.interrupts.record_config_change()
238 } else {
239 self.interrupts.record_queue_completion(true)
240 }
241 }
242
243 #[cfg(test)]
248 pub(crate) fn set_notify_admission_hook<F>(&self, hook: F)
249 where
250 F: Fn() + Send + Sync + 'static,
251 {
252 *self.notify_admission_hook.lock() = Some(Arc::new(hook));
253 }
254
255 #[cfg(test)]
256 pub(super) fn run_notify_admission_hook(&self) {
257 let hook = self.notify_admission_hook.lock().clone();
258 if let Some(hook) = hook {
259 hook();
260 }
261 }
262
263 #[cfg(test)]
267 pub(crate) fn set_reset_before_core_hook<F>(&self, hook: F)
268 where
269 F: Fn() + Send + Sync + 'static,
270 {
271 *self.reset_before_core_hook.lock() = Some(Arc::new(hook));
272 }
273
274 #[cfg(test)]
275 pub(super) fn run_reset_before_core_hook(&self) {
276 let hook = self.reset_before_core_hook.lock().clone();
277 if let Some(hook) = hook {
278 hook();
279 }
280 }
281
282 pub fn update_interrupt_disabled_logical(&self, disabled: bool) -> InterruptTransitionIntent {
290 let generation = self.queue_generation();
294 let transition = self.interrupts.set_disabled(disabled);
295 InterruptTransitionIntent::new(transition, generation)
296 }
297
298 pub fn admit_interrupt_transition(
306 &self,
307 intent: InterruptTransitionIntent,
308 ) -> DeviceResult<Option<InterruptTransitionRequest>> {
309 let Some(activity) = self.activity.acquire(intent.generation()) else {
310 self.interrupts.cancel_transition(intent.transition());
315 return Ok(None);
316 };
317
318 if self.queue_generation() != intent.generation() {
324 self.interrupts
325 .suppress_stale_transition(intent.transition());
326 drop(activity);
327 return Ok(None);
328 }
329
330 Ok(Some(InterruptTransitionRequest::new(
331 Arc::clone(&self.interrupts),
332 intent.transition(),
333 Some(activity),
334 )))
335 }
336
337 pub fn set_interrupt_disabled(
339 &self,
340 disabled: bool,
341 ) -> DeviceResult<InterruptTransitionRequest> {
342 let intent = self.update_interrupt_disabled_logical(disabled);
343 self.admit_interrupt_transition(intent)?
344 .ok_or(DeviceError::InvalidState {
345 operation: "update VirtIO PCI interrupt state",
346 detail: "transport reset is in progress or the transition is stale".into(),
347 })
348 }
349
350 pub fn complete_interrupt_transition(
353 &self,
354 transition: InterruptTransition,
355 success: bool,
356 ) -> InterruptTransition {
357 self.interrupts.complete_transition(transition, success)
358 }
359
360 pub fn suppress_stale_interrupt_transition(&self, transition: InterruptTransition) {
366 self.interrupts.suppress_stale_transition(transition);
367 }
368
369 pub fn resynchronize_interrupt(&self) -> InterruptTransition {
371 self.interrupts.resynchronize()
372 }
373
374 fn acquire_control_activity(&self) -> DeviceResult<ActivityPermit> {
375 self.activity
376 .acquire(self.queue_generation())
377 .ok_or(DeviceError::InvalidState {
378 operation: "access VirtIO PCI transport control state",
379 detail: "transport reset is in progress".into(),
380 })
381 }
382}
383
384fn require_width(actual: AccessWidth, expected: AccessWidth) -> DeviceResult {
385 if actual == expected {
386 Ok(())
387 } else {
388 Err(DeviceError::InvalidWidth { expected, actual })
389 }
390}
391
392fn access_in_region(offset: u64, width: AccessWidth, start: u64, length: u64) -> bool {
393 offset >= start
394 && offset
395 .checked_add(width.size() as u64)
396 .is_some_and(|end| end <= start + length)
397}
398
399fn feature_word(features: u64, selector: u32) -> DeviceResult<u64> {
400 if selector > 1 {
401 Ok(0)
402 } else {
403 Ok((features >> (selector * 32)) & u32::MAX as u64)
404 }
405}
406
407fn invalid_queue(index: u16) -> DeviceError {
408 DeviceError::InvalidInput {
409 operation: "virtio-pci queue",
410 detail: format!("queue index {index} is not exposed"),
411 }
412}
413
414fn map_pci_error(error: VirtioError) -> DeviceError {
415 map_virtio_error(error, "virtio-pci queue")
416}
417
418fn reject_processing_queue(queue: &QueueState) -> DeviceResult {
419 if queue.processing {
420 Err(DeviceError::ResourceBusy {
421 operation: "configure VirtIO queue",
422 resource: "queue processing lease".into(),
423 })
424 } else {
425 Ok(())
426 }
427}
428
429#[cfg(test)]
430mod tests;