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