1#![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#[derive(Debug, Error)]
21pub enum NewLineError {
22 #[error("irq {0} has been shared too many times")]
24 TooMany(u32),
25}
26
27pub trait LineSetTarget: Send + Sync {
39 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; if self.lines.len() >= MAX_SHARED {
133 return None;
134 }
135
136 let line_key = self.fresh_line_key;
137 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
152pub 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 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 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 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 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 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#[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 pub fn new() -> Self {
336 Self {
337 state: Default::default(),
338 }
339 }
340
341 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 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 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)] pub 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)); line01.set_level(false);
548 assert!(!intcon.is_high(0)); }
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)); drop(line01);
568 assert!(!intcon.is_high(0)); }
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}