Skip to main content

game_toolkit_dosaudio/dma/
playback.rs

1use std::{collections::VecDeque, sync::Arc};
2
3use crate::device::sb::{DacState, SbControl};
4use crate::{
5    ConventionalMemory, DmaChannel, DmaController, DmaUnit, Error, IrqRequest, Result, Tick,
6};
7
8/// A DSP block completion observed while servicing attached conventional memory.
9///
10/// Polling this event does not acknowledge the hardware interrupt: read the DSP's
11/// byte or word interrupt-acknowledge port separately. Events remain distinct
12/// when several auto-init blocks complete with the IRQ line still asserted.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct DmaCompletion {
15    /// Machine timestamp of the DMA unit that completed the DSP block.
16    pub at: Tick,
17    /// Configured interrupt line and its assertion state at block completion.
18    pub irq: IrqRequest,
19    /// Physical byte or word channel that supplied the final DMA unit.
20    pub channel: DmaChannel,
21    /// DSP block-completion counter, as reported at this completion.
22    pub block: u64,
23}
24
25pub(crate) struct MemoryPlayback {
26    pub controller: DmaController,
27    pub memory: Arc<ConventionalMemory>,
28    completions: VecDeque<DmaCompletion>,
29    capacity: usize,
30}
31
32#[derive(Clone, Copy)]
33pub(crate) struct MemoryPlan {
34    pub sb: SbControl,
35    pub controller: DmaController,
36    pub commands: usize,
37}
38
39impl MemoryPlayback {
40    pub(crate) fn new(memory: Arc<ConventionalMemory>, capacity: usize) -> Self {
41        Self {
42            controller: DmaController::new(),
43            memory,
44            completions: VecDeque::with_capacity(capacity),
45            capacity,
46        }
47    }
48
49    fn walk(
50        memory: &ConventionalMemory,
51        mut sb: SbControl,
52        mut controller: DmaController,
53        span: (Tick, Tick),
54        mut emit: impl FnMut(Tick, DacState),
55        mut complete: impl FnMut(DmaCompletion) -> Result<()>,
56    ) -> Result<MemoryPlan> {
57        let (from, to) = span;
58        let mut commands = 0_usize;
59        while let Some(next) = sb.next_dma_tick() {
60            // A masked/exhausted controller may leave an overdue DSP request.
61            // Its first successful transfer resumes at current machine time.
62            let due = next.max(from);
63            if due > to {
64                break;
65            }
66            let Some(request) = sb.dma_request(due) else {
67                break;
68            };
69            let Some(unit) = controller.transfer(request, memory)? else {
70                break;
71            };
72            let before = sb.completed_blocks();
73            let state = match unit {
74                DmaUnit::Byte(value) => sb.dma_write(due, request.channel, value)?,
75                DmaUnit::Word(value) => sb.dma_write16(due, request.channel, value)?,
76            };
77            commands = commands.checked_add(1).ok_or(Error::TooLarge)?;
78            emit(due, state);
79            if sb.completed_blocks() != before {
80                complete(DmaCompletion {
81                    at: due,
82                    irq: sb.irq_request(),
83                    channel: request.channel,
84                    block: sb.completed_blocks(),
85                })?;
86            }
87        }
88        Ok(MemoryPlan {
89            sb,
90            controller,
91            commands,
92        })
93    }
94
95    pub(crate) fn plan(&self, sb: SbControl, from: Tick, to: Tick) -> Result<MemoryPlan> {
96        let mut pending = self.completions.len();
97        Self::walk(
98            &self.memory,
99            sb,
100            self.controller,
101            (from, to),
102            |_, _| {},
103            |_| {
104                pending = pending.checked_add(1).ok_or(Error::TooLarge)?;
105                if pending > self.capacity {
106                    Err(Error::IrqQueueFull)
107                } else {
108                    Ok(())
109                }
110            },
111        )
112    }
113
114    pub(crate) fn commit(
115        &mut self,
116        sb: &mut SbControl,
117        from: Tick,
118        to: Tick,
119        emit: impl FnMut(Tick, DacState),
120    ) -> Result<()> {
121        // Preflight here also makes this operation transactional independently of
122        // the bus's command-queue reservation. Memory size and DMA addresses are
123        // stable between these passes; only atomic sample values may change.
124        self.plan(*sb, from, to)?;
125        let completions = &mut self.completions;
126        let plan = Self::walk(
127            &self.memory,
128            *sb,
129            self.controller,
130            (from, to),
131            emit,
132            |event| {
133                completions.push_back(event);
134                Ok(())
135            },
136        )?;
137        *sb = plan.sb;
138        self.controller = plan.controller;
139        Ok(())
140    }
141
142    pub(crate) fn next(&self, sb: &SbControl, now: Tick) -> Option<Tick> {
143        let due = sb.next_dma_tick()?.max(now);
144        let request = sb.dma_request(due)?;
145        let mut controller = self.controller;
146        match controller.transfer(request, &self.memory) {
147            Ok(None) => None,
148            // Surface memory errors through the next actual advance, so a bad
149            // address cannot silently stall an offline render forever.
150            Ok(Some(_)) | Err(_) => Some(due),
151        }
152    }
153
154    pub(crate) fn poll(&mut self) -> Option<DmaCompletion> {
155        self.completions.pop_front()
156    }
157}