Skip to main content

axvirtio_common/pci/transport/
transition.rs

1use alloc::sync::Arc;
2
3use axdevice_base::DeviceResult;
4
5use super::{ActivityPermit, QueueNotifyOutcome};
6use crate::pci::{InterruptTransition, VirtioPciInterruptCoordinator};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub(super) enum InterruptPublicationKind {
10    Queue,
11    Configuration,
12}
13
14/// Queue notification result whose activity permit remains alive until the
15/// endpoint has published or deliberately suppressed the completion interrupt.
16pub struct QueueNotification {
17    pub(super) outcome: QueueNotifyOutcome,
18    pub(super) retry_required: bool,
19    pub(super) publication: InterruptPublicationRequest,
20}
21
22impl QueueNotification {
23    /// Returns the device-core result.
24    pub const fn outcome(&self) -> QueueNotifyOutcome {
25        self.outcome
26    }
27
28    /// Returns whether the endpoint must schedule another queue poll.
29    ///
30    /// This covers both a device-core deferral and a guest notification that
31    /// arrived while another execution context owned the queue.
32    pub const fn requires_poll(&self) -> bool {
33        self.retry_required || matches!(self.outcome, QueueNotifyOutcome::Deferred { .. })
34    }
35
36    /// Returns whether publishing this notification requires an endpoint IRQ
37    /// transition permit.
38    pub const fn requires_interrupt_publication(&self) -> bool {
39        self.publication.requires_irq_permit()
40    }
41
42    /// Returns the queue configuration generation covered by this terminal
43    /// notification, if processing was admitted.
44    pub const fn generation(&self) -> Option<VirtioQueueGeneration> {
45        self.publication.generation()
46    }
47
48    /// Explicitly ends the activity lifetime without publishing an ISR bit.
49    pub fn complete(self) {
50        self.publication.cancel();
51    }
52
53    /// Publishes the completion ISR and line transition after endpoint IRQ
54    /// admission, then releases queue activity.
55    pub fn publish<F>(self, publish_transition: F) -> DeviceResult
56    where
57        F: FnMut(InterruptTransition) -> DeviceResult,
58    {
59        self.publication.publish(publish_transition)
60    }
61
62    /// Consumes this notification and returns its pending ISR publication.
63    pub fn into_interrupt_publication(self) -> InterruptPublicationRequest {
64        self.publication
65    }
66}
67
68/// ISR publication retained until the endpoint has acquired its IRQ permit.
69pub struct InterruptPublicationRequest {
70    kind: Option<InterruptPublicationKind>,
71    activity: Option<ActivityPermit>,
72    interrupts: Arc<VirtioPciInterruptCoordinator>,
73}
74
75impl core::fmt::Debug for InterruptPublicationRequest {
76    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        formatter
78            .debug_struct("InterruptPublicationRequest")
79            .field("kind", &self.kind)
80            .field("has_activity", &self.activity.is_some())
81            .finish_non_exhaustive()
82    }
83}
84
85impl InterruptPublicationRequest {
86    pub(super) fn new(
87        interrupts: Arc<VirtioPciInterruptCoordinator>,
88        kind: Option<InterruptPublicationKind>,
89        activity: Option<ActivityPermit>,
90    ) -> Self {
91        Self {
92            kind,
93            activity,
94            interrupts,
95        }
96    }
97
98    /// Returns whether ISR publication must be admitted by the endpoint.
99    pub const fn requires_irq_permit(&self) -> bool {
100        self.kind.is_some()
101    }
102
103    /// Returns the queue generation protected by the activity permit.
104    pub const fn generation(&self) -> Option<VirtioQueueGeneration> {
105        match &self.activity {
106            Some(activity) => Some(activity.generation),
107            None => None,
108        }
109    }
110
111    /// Records the ISR bit and executes all resulting line transitions.
112    ///
113    /// The caller must hold the endpoint IRQ transition permit before calling
114    /// this method. A failed line operation leaves the ISR state retryable and
115    /// is returned to the guest-facing dispatcher.
116    pub fn publish<F>(mut self, mut publish_transition: F) -> DeviceResult
117    where
118        F: FnMut(InterruptTransition) -> DeviceResult,
119    {
120        let Some(kind) = self.kind.take() else {
121            self.activity.take();
122            return Ok(());
123        };
124        let mut transition = match kind {
125            InterruptPublicationKind::Queue => self.interrupts.record_queue_completion(true),
126            InterruptPublicationKind::Configuration => self.interrupts.record_config_change(),
127        };
128        loop {
129            if let Err(error) = publish_transition(transition) {
130                self.interrupts.complete_transition(transition, false);
131                self.activity.take();
132                return Err(error);
133            }
134            transition = self.interrupts.complete_transition(transition, true);
135            if transition == InterruptTransition::None {
136                self.activity.take();
137                return Ok(());
138            }
139        }
140    }
141
142    /// Cancels publication and releases queue activity without recording an ISR bit.
143    pub fn cancel(mut self) {
144        self.activity.take();
145    }
146}
147
148/// Immutable Command.INTx transition intent bound to one VirtIO queue
149/// generation.
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct InterruptTransitionIntent {
152    transition: InterruptTransition,
153    generation: VirtioQueueGeneration,
154}
155
156impl InterruptTransitionIntent {
157    pub(super) const fn new(
158        transition: InterruptTransition,
159        generation: VirtioQueueGeneration,
160    ) -> Self {
161        Self {
162            transition,
163            generation,
164        }
165    }
166
167    /// Returns the physical transition that may be published for this intent.
168    pub const fn transition(self) -> InterruptTransition {
169        self.transition
170    }
171
172    /// Returns the VirtIO queue generation captured with this intent.
173    pub const fn generation(self) -> VirtioQueueGeneration {
174        self.generation
175    }
176}
177
178/// Identity of one queue configuration lifetime.
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
180pub struct VirtioQueueGeneration(pub(super) u64);
181
182impl VirtioQueueGeneration {
183    /// Creates a generation token from a value captured by a transport.
184    pub const fn from_value(value: u64) -> Self {
185        Self(value)
186    }
187
188    /// Returns the numeric generation for diagnostics and tests.
189    pub const fn value(self) -> u64 {
190        self.0
191    }
192}
193
194/// Interrupt transition intent retained until its endpoint callback finishes.
195pub struct InterruptTransitionRequest {
196    transition: InterruptTransition,
197    activity: Option<ActivityPermit>,
198    interrupts: Arc<VirtioPciInterruptCoordinator>,
199}
200
201impl core::fmt::Debug for InterruptTransitionRequest {
202    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203        formatter
204            .debug_struct("InterruptTransitionRequest")
205            .field("transition", &self.transition)
206            .field("has_activity", &self.activity.is_some())
207            .finish_non_exhaustive()
208    }
209}
210
211impl InterruptTransitionRequest {
212    pub(super) fn new(
213        interrupts: Arc<VirtioPciInterruptCoordinator>,
214        transition: InterruptTransition,
215        activity: Option<ActivityPermit>,
216    ) -> Self {
217        Self {
218            transition,
219            activity,
220            interrupts,
221        }
222    }
223
224    pub(super) fn without_activity(
225        interrupts: Arc<VirtioPciInterruptCoordinator>,
226        transition: InterruptTransition,
227    ) -> Self {
228        Self::new(interrupts, transition, None)
229    }
230
231    /// Returns the physical transition that the endpoint must publish.
232    pub const fn transition(&self) -> InterruptTransition {
233        self.transition
234    }
235}
236
237impl Drop for InterruptTransitionRequest {
238    fn drop(&mut self) {
239        self.interrupts.cancel_transition(self.transition);
240        self.activity.take();
241    }
242}