Skip to main content

game_toolkit_dosaudio/
dma.rs

1use std::sync::{
2    Arc,
3    atomic::{AtomicU8, Ordering},
4};
5
6use crate::{DmaDirection, DmaRequest, Error, PortRange, Result};
7
8const MEMORY_SIZE: usize = 1 << 20;
9pub(crate) mod playback;
10pub use playback::DmaCompletion;
11
12/// Shared, zero-initialized conventional memory spanning physical addresses
13/// 00000h through FFFFFh. Clones share the same bytes without locking.
14///
15/// Individual bytes are atomic; bulk reads and writes are not atomic snapshots.
16/// All accesses validate their entire range before reading or writing any byte.
17#[derive(Clone, Debug)]
18pub struct ConventionalMemory {
19    bytes: Arc<[AtomicU8]>,
20}
21
22impl Default for ConventionalMemory {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl ConventionalMemory {
29    /// Allocate one megabyte of zeroed memory, shared by subsequent clones.
30    pub fn new() -> Self {
31        Self {
32            bytes: (0..MEMORY_SIZE).map(|_| AtomicU8::new(0)).collect(),
33        }
34    }
35
36    fn range(&self, address: u32, length: usize) -> Result<core::ops::Range<usize>> {
37        let start = usize::try_from(address).map_err(|_| Error::TooLarge)?;
38        let end = start
39            .checked_add(length)
40            .filter(|&end| end <= MEMORY_SIZE)
41            .ok_or(Error::Config(
42                "conventional memory access exceeds one megabyte",
43            ))?;
44        Ok(start..end)
45    }
46
47    /// Read one physical byte, or report an address outside conventional memory.
48    pub fn read_byte(&self, address: u32) -> Result<u8> {
49        let range = self.range(address, 1)?;
50        Ok(self.bytes[range.start].load(Ordering::Acquire))
51    }
52
53    /// Write one physical byte, or report an address outside conventional memory.
54    pub fn write_byte(&self, address: u32, value: u8) -> Result<()> {
55        let range = self.range(address, 1)?;
56        self.bytes[range.start].store(value, Ordering::Release);
57        Ok(())
58    }
59
60    /// Write a fully validated physical range; errors leave memory unchanged.
61    pub fn write(&self, address: u32, bytes: &[u8]) -> Result<()> {
62        let range = self.range(address, bytes.len())?;
63        for (destination, &value) in self.bytes[range].iter().zip(bytes) {
64            destination.store(value, Ordering::Release);
65        }
66        Ok(())
67    }
68
69    /// Read a fully validated physical range; errors leave `out` unchanged.
70    pub fn read(&self, address: u32, out: &mut [u8]) -> Result<()> {
71        let range = self.range(address, out.len())?;
72        for (source, destination) in self.bytes[range].iter().zip(out) {
73            *destination = source.load(Ordering::Acquire);
74        }
75        Ok(())
76    }
77}
78
79/// One bus-width unit delivered by the host DMA controller.
80#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub enum DmaUnit {
82    /// One byte on DMA channels 0 through 3.
83    Byte(u8),
84    /// One little-endian word on DMA channels 5 through 7.
85    Word(u16),
86}
87
88#[derive(Clone, Copy, Debug)]
89struct Channel {
90    address: u16,
91    count: u16,
92    base_address: u16,
93    base_count: u16,
94    page: u8,
95    mode: u8,
96    masked: bool,
97    active: bool,
98}
99
100impl Channel {
101    const RESET: Self = Self {
102        address: 0,
103        count: 0,
104        base_address: 0,
105        base_count: 0,
106        page: 0,
107        mode: 0,
108        masked: true,
109        active: false,
110    };
111}
112
113/// The two PC/AT 8237 DMA controllers, serviced one unit per device request.
114///
115/// Models address/count registers, AT page registers, masks, transfer direction,
116/// auto-initialization and terminal count. Address/count accesses share one
117/// low/high flip-flop per controller. Channel 4 is the cascade and transfers no
118/// device data. Only memory-to-device transfers are implemented.
119///
120/// This models register-visible transfer state, without bus arbitration, timing,
121/// memory-to-memory transfers or peripheral-to-memory writes. Demand, single and
122/// block modes all service one unit per call; cascade mode does not transfer.
123#[derive(Clone, Copy, Debug)]
124pub struct DmaController {
125    channels: [Channel; 8],
126    high_byte: [bool; 2],
127    command: [u8; 2],
128    terminal_count: [u8; 2],
129    software_request: [u8; 2],
130}
131
132impl Default for DmaController {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl DmaController {
139    /// Construct two controllers with all channels masked and inactive.
140    pub const fn new() -> Self {
141        Self {
142            channels: [Channel::RESET; 8],
143            high_byte: [false; 2],
144            command: [0; 2],
145            terminal_count: [0; 2],
146            software_request: [0; 2],
147        }
148    }
149
150    /// I/O ports decoded by the controllers and their seven AT page registers.
151    pub fn ports(&self) -> &[PortRange] {
152        const PORTS: &[PortRange] = &[
153            PortRange::new(0x00, 0x0f),
154            PortRange::new(0x81, 0x83),
155            PortRange::single(0x87),
156            PortRange::new(0x89, 0x8b),
157            PortRange::single(0xc0),
158            PortRange::single(0xc2),
159            PortRange::single(0xc4),
160            PortRange::single(0xc6),
161            PortRange::single(0xc8),
162            PortRange::single(0xca),
163            PortRange::single(0xcc),
164            PortRange::single(0xce),
165            PortRange::single(0xd0),
166            PortRange::single(0xd2),
167            PortRange::single(0xd4),
168            PortRange::single(0xd6),
169            PortRange::single(0xd8),
170            PortRange::single(0xda),
171            PortRange::single(0xdc),
172            PortRange::single(0xde),
173        ];
174        PORTS
175    }
176
177    fn page_channel(port: u16) -> Option<usize> {
178        match port {
179            0x87 => Some(0),
180            0x83 => Some(1),
181            0x81 => Some(2),
182            0x82 => Some(3),
183            0x8b => Some(5),
184            0x89 => Some(6),
185            0x8a => Some(7),
186            _ => None,
187        }
188    }
189
190    fn register(port: u16) -> Option<(usize, usize)> {
191        match port {
192            0x00..=0x0f => Some((0, usize::from(port))),
193            0xc0..=0xde if port & 1 == 0 => Some((1, usize::from((port - 0xc0) / 2))),
194            _ => None,
195        }
196    }
197
198    fn set_half(register: &mut u16, value: u8, high: bool) {
199        *register = if high {
200            (*register & 0x00ff) | (u16::from(value) << 8)
201        } else {
202            (*register & 0xff00) | u16::from(value)
203        };
204    }
205
206    /// Write an 8237 register or AT page register. Unknown ports ignore writes.
207    pub fn outb(&mut self, port: u16, value: u8) {
208        if let Some(channel) = Self::page_channel(port) {
209            self.channels[channel].page = value;
210            return;
211        }
212        let Some((chip, register)) = Self::register(port) else {
213            return;
214        };
215        if register < 8 {
216            let index = chip * 4 + register / 2;
217            if index != 4 {
218                let channel = &mut self.channels[index];
219                if register & 1 == 0 {
220                    Self::set_half(&mut channel.address, value, self.high_byte[chip]);
221                    Self::set_half(&mut channel.base_address, value, self.high_byte[chip]);
222                } else {
223                    Self::set_half(&mut channel.count, value, self.high_byte[chip]);
224                    Self::set_half(&mut channel.base_count, value, self.high_byte[chip]);
225                }
226                channel.active = true;
227            }
228            self.high_byte[chip] = !self.high_byte[chip];
229            return;
230        }
231        let index = chip * 4 + usize::from(value & 3);
232        match register {
233            8 => self.command[chip] = value,
234            9 => {
235                let bit = 1 << (value & 3);
236                if value & 4 == 0 {
237                    self.software_request[chip] &= !bit;
238                } else {
239                    self.software_request[chip] |= bit;
240                }
241            }
242            10 => self.channels[index].masked = value & 4 != 0,
243            11 => {
244                if index != 4 {
245                    self.channels[index].mode = value;
246                }
247            }
248            12 => self.high_byte[chip] = false,
249            13 => {
250                self.command[chip] = 0;
251                self.terminal_count[chip] = 0;
252                self.software_request[chip] = 0;
253                self.high_byte[chip] = false;
254                for channel in &mut self.channels[chip * 4..chip * 4 + 4] {
255                    channel.masked = true;
256                }
257            }
258            14 => {
259                for channel in &mut self.channels[chip * 4..chip * 4 + 4] {
260                    channel.masked = false;
261                }
262            }
263            15 => {
264                for (local, channel) in self.channels[chip * 4..chip * 4 + 4].iter_mut().enumerate()
265                {
266                    channel.masked = value & (1 << local) != 0;
267                }
268            }
269            _ => {}
270        }
271    }
272
273    /// Read a current address/count, page or status register. Status reads clear
274    /// terminal-count flags; other unsupported reads return FFh.
275    pub fn inb(&mut self, port: u16) -> u8 {
276        if let Some(channel) = Self::page_channel(port) {
277            return self.channels[channel].page;
278        }
279        let Some((chip, register)) = Self::register(port) else {
280            return 0xff;
281        };
282        if register < 8 {
283            let index = chip * 4 + register / 2;
284            let value = if index == 4 {
285                0xffff
286            } else if register & 1 == 0 {
287                self.channels[index].address
288            } else {
289                self.channels[index].count
290            };
291            let byte = if self.high_byte[chip] {
292                (value >> 8) as u8
293            } else {
294                value as u8
295            };
296            self.high_byte[chip] = !self.high_byte[chip];
297            byte
298        } else if register == 8 {
299            let value = self.terminal_count[chip] | (self.software_request[chip] << 4);
300            self.terminal_count[chip] = 0;
301            value
302        } else {
303            0xff
304        }
305    }
306
307    /// Read one DMA unit and advance the channel only after memory access
308    /// succeeds. Masked, disabled, exhausted or incompatible requests return
309    /// `Ok(None)` without changing channel state.
310    ///
311    /// Byte channels wrap within a 64 KiB page. Word channels shift their
312    /// 16-bit address by one, ignore page bit zero and wrap within 128 KiB.
313    /// Count FFFFh means 65,536 units. Terminal count either reloads the base
314    /// registers in auto-init mode or stops the channel until reprogrammed.
315    pub fn transfer(
316        &mut self,
317        request: DmaRequest,
318        memory: &ConventionalMemory,
319    ) -> Result<Option<DmaUnit>> {
320        let index = usize::from(request.channel.number());
321        let chip = index / 4;
322        let channel = self.channels[index];
323        if request.direction != DmaDirection::MemoryToDevice
324            || channel.masked
325            || !channel.active
326            || self.command[chip] & 4 != 0
327            || channel.mode & 0x0c != 0x08
328            || channel.mode & 0xc0 == 0xc0
329        {
330            return Ok(None);
331        }
332        let unit = if chip == 0 {
333            let address = (u32::from(channel.page) << 16) | u32::from(channel.address);
334            DmaUnit::Byte(memory.read_byte(address)?)
335        } else {
336            let address =
337                (u32::from(channel.page & 0xfe) << 16) | (u32::from(channel.address) << 1);
338            let mut bytes = [0; 2];
339            memory.read(address, &mut bytes)?;
340            DmaUnit::Word(u16::from_le_bytes(bytes))
341        };
342        let current = &mut self.channels[index];
343        current.address = if channel.mode & 0x20 != 0 {
344            channel.address.wrapping_sub(1)
345        } else {
346            channel.address.wrapping_add(1)
347        };
348        current.count = channel.count.wrapping_sub(1);
349        if channel.count == 0 {
350            self.terminal_count[chip] |= 1 << (index % 4);
351            self.software_request[chip] &= !(1 << (index % 4));
352            if channel.mode & 0x10 != 0 {
353                current.address = current.base_address;
354                current.count = current.base_count;
355            } else {
356                current.active = false;
357            }
358        }
359        Ok(Some(unit))
360    }
361}