vmcore/
line_interrupt.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Infrastructure to support line interrupts.
5
6#![warn(missing_docs)]
7
8use inspect::Inspect;
9use parking_lot::Mutex;
10use std::borrow::Cow;
11use std::collections::BTreeMap;
12use std::collections::btree_map::Entry;
13use std::fmt::Debug;
14use std::fmt::Display;
15use std::ops::RangeInclusive;
16use std::sync::Arc;
17use thiserror::Error;
18
19/// An error occurred while creating a new line interrupt.
20#[derive(Debug, Error)]
21pub enum NewLineError {
22    /// The line interrupt has been shared too many times.
23    #[error("irq {0} has been shared too many times")]
24    TooMany(u32),
25}
26
27/// Unless you're implementing an interrupt controller (e.g: the PIC, IOAPIC),
28/// you shouldn't be using this trait!
29///
30/// **NOTE: Individual devices should not use this trait directly!**
31///
32/// Devices are expected to use [`LineInterrupt`], which decouples the details
33/// of IRQ numbers and assignment from concrete device implementations.
34///
35/// The alternative, where devices get handed an interface that allows them to
36/// assert arbitrary IRQ lines, can lead to multiple devices inadvertently
37/// trampling on one another's IRQ lines if you're not careful.
38pub trait LineSetTarget: Send + Sync {
39    /// Set an interrupt line state.
40    fn set_irq(&self, vector: u32, high: bool);
41}
42
43#[derive(Debug)]
44struct Line {
45    debug_label: Cow<'static, str>,
46    is_high: bool,
47}
48
49impl Line {
50    fn new(debug_label: Cow<'static, str>) -> Self {
51        Self {
52            debug_label,
53            is_high: false,
54        }
55    }
56}
57
58struct Target {
59    debug_label: Arc<str>,
60    inner: Arc<dyn LineSetTarget>,
61    vector: u32,
62}
63
64impl Debug for Target {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("Target")
67            .field("debug_label", &self.debug_label)
68            .field("vector", &self.vector)
69            .finish()
70    }
71}
72
73#[derive(Debug)]
74struct LineInterruptInner {
75    targets: Vec<Target>,
76    lines: BTreeMap<u8, Line>,
77    fresh_line_key: u8,
78    vector: u32,
79}
80
81impl Display for LineInterruptInner {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "targets[")?;
84        for (i, target) in self.targets.iter().enumerate() {
85            if i != 0 {
86                write!(f, ",")?;
87            }
88            write!(f, "{}({})", target.debug_label, target.vector)?;
89        }
90        write!(f, "],lines[")?;
91        for (i, (_, line)) in self.lines.iter().enumerate() {
92            if i != 0 {
93                write!(f, ",")?;
94            }
95            write!(f, "{}({})", line.debug_label, line.is_high)?;
96        }
97        write!(f, "]")?;
98        Ok(())
99    }
100}
101
102impl Inspect for LineInterruptInner {
103    fn inspect(&self, req: inspect::Request<'_>) {
104        req.respond()
105            .child("targets", |req| {
106                let mut resp = req.respond();
107                for target in self.targets.iter() {
108                    resp.field(target.debug_label.as_ref(), target.vector);
109                }
110            })
111            .child("lines", |req| {
112                let mut resp = req.respond();
113                for (_, line) in self.lines.iter() {
114                    resp.field(&line.debug_label, line.is_high);
115                }
116            });
117    }
118}
119
120impl LineInterruptInner {
121    fn new(debug_label: Cow<'static, str>, vector: u32, targets: Vec<Target>) -> Self {
122        Self {
123            targets,
124            lines: [(0, Line::new(debug_label))].into_iter().collect(),
125            fresh_line_key: 1,
126            vector,
127        }
128    }
129
130    fn add_line(&mut self, debug_label: Cow<'static, str>) -> Option<u8> {
131        const MAX_SHARED: usize = 16; // arbitrary choice
132        if self.lines.len() >= MAX_SHARED {
133            return None;
134        }
135
136        let line_key = self.fresh_line_key;
137        // this loop would only go off in the unlikely case that a single
138        // LineInterrupt is being constantly shared + dropped in a loop, and
139        // is bounded to MAX_SHARED iterations.
140        self.fresh_line_key = self.fresh_line_key.wrapping_add(1);
141        while self.lines.contains_key(&self.fresh_line_key) {
142            self.fresh_line_key = self.fresh_line_key.wrapping_add(1);
143        }
144
145        let existing = self.lines.insert(line_key, Line::new(debug_label));
146        assert!(existing.is_none());
147
148        Some(line_key)
149    }
150}
151
152/// A line interrupt, representing a (virtually) physical wire between a device
153/// and an interrupt controller.
154//
155// DEVNOTE: while it's tempting to provide an `impl Clone` for this type which
156// returns a new LineInterrupt that gets OR'd with the existing LineInterrupt,
157// doing so would violate the principle of least surprise, as the `Clone` trait
158// in Rust isn't supposed to change the semantics of the underlying type. This
159// could become a problem if, say, the LineInterrupt was stored in a `Vec` which
160// was absentmindedly cloned.
161pub struct LineInterrupt {
162    inner: Arc<Mutex<LineInterruptInner>>,
163    line_key: u8,
164}
165
166impl Debug for LineInterrupt {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        f.debug_struct("LineInterrupt")
169            .field("line_key", &self.line_key)
170            .field("inner", &*self.inner.lock())
171            .finish()
172    }
173}
174
175impl Display for LineInterrupt {
176    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177        Display::fmt(&self.inner.lock(), f)
178    }
179}
180
181impl Inspect for LineInterrupt {
182    fn inspect(&self, req: inspect::Request<'_>) {
183        let inner = self.inner.lock();
184        let line = inner
185            .lines
186            .get(&self.line_key)
187            .expect("line_key is always valid");
188
189        req.respond()
190            .field("debug_label", line.debug_label.as_ref())
191            .field("is_high", line.is_high)
192            .child("targets", |req| {
193                let mut resp = req.respond();
194                for target in inner.targets.iter() {
195                    resp.field(target.debug_label.as_ref(), target.vector);
196                }
197            });
198    }
199}
200
201impl LineInterrupt {
202    /// Creates a line that is not attached to any line set or target.
203    ///
204    /// This is useful for testing purposes.
205    pub fn detached() -> Self {
206        Self {
207            inner: Arc::new(Mutex::new(LineInterruptInner {
208                targets: Vec::new(),
209                lines: [(0, Line::new("detached".into()))].into_iter().collect(),
210                fresh_line_key: 1,
211                vector: 0,
212            })),
213            line_key: 0,
214        }
215    }
216
217    /// Creates a new line interrupt associated with provided target.
218    ///
219    /// This is a shorthand helper method for:
220    ///
221    /// ```rust
222    /// # use vmcore::line_interrupt::*;
223    /// # let f = || -> Result<LineInterrupt, NewLineError> {
224    /// # let (label, target, vector) = ("", todo!(), 0);
225    /// let set = LineSet::new();
226    /// set.add_target(0..=0, vector, "target", target);
227    /// set.new_line(0, label)
228    /// # };
229    /// ```
230    pub fn new_with_target(
231        debug_label: impl Into<Cow<'static, str>>,
232        target: Arc<dyn LineSetTarget>,
233        vector: u32,
234    ) -> LineInterrupt {
235        let set = LineSet::new();
236        let debug_label = debug_label.into();
237        set.add_target(0..=0, vector, debug_label.as_ref(), target);
238        set.new_line(0, debug_label).unwrap()
239    }
240
241    /// Creates a new line interrupt sharing the same vector.
242    pub fn new_shared(
243        &self,
244        debug_label: impl Into<Cow<'static, str>>,
245    ) -> Result<Self, NewLineError> {
246        let mut inner = self.inner.lock();
247        let line_key = inner
248            .add_line(debug_label.into())
249            .ok_or(NewLineError::TooMany(inner.vector))?;
250
251        Ok(Self {
252            inner: self.inner.clone(),
253            line_key,
254        })
255    }
256
257    /// Sets the line level high or low.
258    pub fn set_level(&self, high: bool) {
259        let mut inner = self.inner.lock();
260        inner
261            .lines
262            .get_mut(&self.line_key)
263            .expect("line_key is always valid")
264            .is_high = high;
265
266        let is_high = inner.lines.iter().any(|(_, line)| line.is_high);
267
268        if is_high && inner.targets.is_empty() {
269            tracelimit::warn_ratelimited!(%inner, "LineInterrupt not hooked up to any targets!");
270        }
271
272        for target in inner.targets.iter() {
273            target.inner.set_irq(target.vector, is_high);
274        }
275    }
276}
277
278impl Drop for LineInterrupt {
279    fn drop(&mut self) {
280        // make sure to deassert the line if this was the last shared line that
281        // was being asserted.
282        self.set_level(false);
283
284        let mut inner = self.inner.lock();
285        inner
286            .lines
287            .remove(&self.line_key)
288            .expect("line_key is always valid");
289    }
290}
291
292/// A set of line interrupts and their target mappings.
293#[derive(Inspect)]
294pub struct LineSet {
295    #[inspect(flatten)]
296    state: Mutex<LineSetState>,
297}
298
299#[derive(Inspect, Default)]
300struct LineSetState {
301    #[inspect(with = "inspect_mappings")]
302    targets: Vec<TargetMapping>,
303    #[inspect(iter_by_key)]
304    lines: BTreeMap<u32, Arc<Mutex<LineInterruptInner>>>,
305}
306
307#[derive(Clone)]
308struct TargetMapping {
309    source_range: RangeInclusive<u32>,
310    target_start: u32,
311    debug_label: Arc<str>,
312    target: Arc<dyn LineSetTarget>,
313}
314
315fn inspect_mappings(mappings: &[TargetMapping]) -> impl '_ + Inspect {
316    inspect::iter_by_key(mappings.iter().map(|mapping| {
317        (
318            format!(
319                "{}:{}-{}",
320                mapping.debug_label,
321                mapping.target_start,
322                mapping.target_start + (mapping.source_range.end() - mapping.source_range.start())
323            ),
324            format!(
325                "{}-{}",
326                mapping.source_range.start(),
327                mapping.source_range.end(),
328            ),
329        )
330    }))
331}
332
333impl LineSet {
334    /// Creates a new line set.
335    pub fn new() -> Self {
336        Self {
337            state: Default::default(),
338        }
339    }
340
341    /// Adds a target mapping to the set.
342    ///
343    /// The mapping is over a portion of the line set as specified by
344    /// `source_range`, and it is mapped into the target's vector space starting
345    /// at `target_start`.
346    pub fn add_target(
347        &self,
348        source_range: RangeInclusive<u32>,
349        target_start: u32,
350        debug_label: impl Into<Arc<str>>,
351        target: Arc<dyn LineSetTarget>,
352    ) {
353        let debug_label = debug_label.into();
354        let mut state = self.state.lock();
355        // Add this target to any existing lines that overlap with
356        // `source_range`.
357        for (&vector, line) in &mut state.lines {
358            if source_range.contains(&vector) {
359                let target_vector = vector - source_range.start() + target_start;
360                let is_high = {
361                    let mut line = line.lock();
362                    line.targets.push(Target {
363                        debug_label: debug_label.clone(),
364                        inner: target.clone(),
365                        vector: target_vector,
366                    });
367                    line.lines.iter().any(|(_, line)| line.is_high)
368                };
369                if is_high {
370                    target.set_irq(target_vector, true);
371                }
372            }
373        }
374        state.targets.push(TargetMapping {
375            source_range,
376            target_start,
377            target,
378            debug_label,
379        });
380    }
381
382    /// Adds a new line interrupt to the set.
383    pub fn new_line(
384        &self,
385        vector: u32,
386        debug_label: impl Into<Cow<'static, str>>,
387    ) -> Result<LineInterrupt, NewLineError> {
388        self.new_line_(vector, debug_label.into())
389    }
390
391    fn new_line_(
392        &self,
393        vector: u32,
394        debug_label: Cow<'static, str>,
395    ) -> Result<LineInterrupt, NewLineError> {
396        let mut state = self.state.lock();
397        let state = &mut *state;
398        let line = match state.lines.entry(vector) {
399            Entry::Occupied(entry) => {
400                let inner = entry.get();
401                let line_key = inner
402                    .lock()
403                    .add_line(debug_label)
404                    .ok_or(NewLineError::TooMany(vector))?;
405
406                LineInterrupt {
407                    inner: inner.clone(),
408                    line_key,
409                }
410            }
411            Entry::Vacant(entry) => {
412                let inner = Arc::new(Mutex::new(LineInterruptInner::new(
413                    debug_label,
414                    vector,
415                    state
416                        .targets
417                        .iter()
418                        .filter(|&mapping| mapping.source_range.contains(&vector))
419                        .map(|mapping| Target {
420                            debug_label: mapping.debug_label.clone(),
421                            inner: mapping.target.clone(),
422                            vector: vector - mapping.source_range.start() + mapping.target_start,
423                        })
424                        .collect(),
425                )));
426                entry.insert(inner.clone());
427                LineInterrupt { inner, line_key: 0 }
428            }
429        };
430        Ok(line)
431    }
432}
433
434#[expect(missing_docs)] // self explanatory struct/functions
435pub mod test_helpers {
436    use crate::line_interrupt::LineSetTarget;
437    use parking_lot::Mutex;
438    use std::collections::BTreeMap;
439    use std::sync::Arc;
440    use std::task::Context;
441    use std::task::Poll;
442    use std::task::Waker;
443
444    pub struct TestLineInterruptTarget {
445        state: Mutex<BTreeMap<u32, LineState>>,
446    }
447
448    #[derive(Default)]
449    struct LineState {
450        is_high: bool,
451        waker: Option<Waker>,
452    }
453
454    impl TestLineInterruptTarget {
455        pub fn new_arc() -> Arc<TestLineInterruptTarget> {
456            Arc::new(TestLineInterruptTarget {
457                state: Default::default(),
458            })
459        }
460
461        pub fn is_high(&self, vector: u32) -> bool {
462            self.state.lock().get(&vector).is_some_and(|s| s.is_high)
463        }
464
465        pub fn poll_high(&self, cx: &mut Context<'_>, vector: u32) -> Poll<()> {
466            let mut state = self.state.lock();
467            let state = state.get_mut(&vector).unwrap();
468            if state.is_high {
469                Poll::Ready(())
470            } else {
471                state.waker = Some(cx.waker().clone());
472                Poll::Pending
473            }
474        }
475    }
476
477    impl LineSetTarget for TestLineInterruptTarget {
478        fn set_irq(&self, vector: u32, high: bool) {
479            let mut state = self.state.lock();
480            let state = &mut state.entry(vector).or_default();
481            state.is_high = high;
482            if high {
483                if let Some(waker) = state.waker.take() {
484                    waker.wake();
485                }
486            }
487        }
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494    use crate::line_interrupt::test_helpers::TestLineInterruptTarget;
495
496    #[test]
497    fn basic() {
498        let intcon = TestLineInterruptTarget::new_arc();
499
500        let line0 = LineInterrupt::new_with_target("line0", intcon.clone(), 0);
501        let line1 = LineInterrupt::new_with_target("line1", intcon.clone(), 1);
502
503        line0.set_level(true);
504        assert!(intcon.is_high(0));
505        line0.set_level(false);
506        assert!(!intcon.is_high(0));
507
508        line1.set_level(true);
509        assert!(intcon.is_high(1));
510        line1.set_level(false);
511        assert!(!intcon.is_high(1));
512    }
513
514    #[test]
515    fn multi_target() {
516        let intcon1 = TestLineInterruptTarget::new_arc();
517        let intcon2 = TestLineInterruptTarget::new_arc();
518
519        let line_set = LineSet::new();
520
521        let line = line_set.new_line(2, "line").unwrap();
522
523        line_set.add_target(1..=5, 7, "intcon1", intcon1.clone());
524        line.set_level(true);
525        line_set.add_target(2..=2, 3, "intcon2", intcon2.clone());
526
527        assert!(intcon1.is_high(8));
528        assert!(intcon2.is_high(3));
529    }
530
531    #[test]
532    fn shared_line() {
533        let intcon = TestLineInterruptTarget::new_arc();
534
535        let line_set = LineSet::new();
536        line_set.add_target(0..=0, 0, "intcon", intcon.clone());
537
538        let line00 = line_set.new_line(0, "line00").unwrap();
539        let line01 = line_set.new_line(0, "line01").unwrap();
540
541        line00.set_level(true);
542        assert!(intcon.is_high(0));
543        line01.set_level(true);
544        assert!(intcon.is_high(0));
545        line00.set_level(false);
546        assert!(intcon.is_high(0)); // still high, since line01 is still asserting
547        line01.set_level(false);
548        assert!(!intcon.is_high(0)); // finally low
549    }
550
551    #[test]
552    fn drop_impl() {
553        let intcon = TestLineInterruptTarget::new_arc();
554
555        let line_set = LineSet::new();
556        line_set.add_target(0..=0, 0, "intcon", intcon.clone());
557
558        let line00 = line_set.new_line(0, "line00").unwrap();
559        let line01 = line_set.new_line(0, "line01").unwrap();
560
561        line00.set_level(true);
562        assert!(intcon.is_high(0));
563        line01.set_level(true);
564        assert!(intcon.is_high(0));
565        line00.set_level(false);
566        assert!(intcon.is_high(0)); // still high, since line01 is still asserting
567        drop(line01);
568        assert!(!intcon.is_high(0)); // low, because line01 disappeared
569    }
570
571    #[test]
572    fn share_drop_loop() {
573        let intcon = TestLineInterruptTarget::new_arc();
574
575        let line_set = LineSet::new();
576        line_set.add_target(0..=0, 0, "intcon", intcon.clone());
577
578        let _line00 = line_set.new_line(0, "line00").unwrap();
579
580        for _ in 0..1000 {
581            let line01 = line_set.new_line(0, "line01").unwrap();
582            line01.set_level(true);
583            assert!(intcon.is_high(0));
584            drop(line01);
585            assert!(!intcon.is_high(0));
586        }
587    }
588}