mos_hardware/mega65/
memory.rs

1// copyright 2023 mikael lund aka wombat
2//
3// licensed under the apache license, version 2.0 (the "license");
4// you may not use this file except in compliance with the license.
5// you may obtain a copy of the license at
6//
7//     http://www.apache.org/licenses/license-2.0
8//
9// unless required by applicable law or agreed to in writing, software
10// distributed under the license is distributed on an "as is" basis,
11// without warranties or conditions of any kind, either express or implied.
12// see the license for the specific language governing permissions and
13// limitations under the license.
14
15//! Memory related tools
16
17use super::libc;
18use crate::poke;
19use alloc::{string::String, vec::Vec};
20use core::mem::MaybeUninit;
21
22/// Maximum value in 28-bit address space
23pub const MAX_28_BIT_ADDRESS: u32 = 0xFFFFFFF;
24
25/// Read into 28 bit memory
26pub fn lpeek(address: u32) -> u8 {
27    unsafe { libc::lpeek(address as i32) }
28}
29
30/// Write into 28 bit memory
31///
32/// # Safety
33/// Unsafe as it writes directly to memory
34pub unsafe fn lpoke(address: u32, value: u8) {
35    libc::lpoke(address as i32, value)
36}
37
38/// DMA copy in 28 bit address space
39///
40/// # Safety
41/// Unsafe as it writes directly to memory
42pub unsafe fn lcopy(source: u32, destination: u32, length: usize) {
43    if length > 0 {
44        unsafe { libc::lcopy(source as i32, destination as i32, length as u16) };
45    }
46}
47
48/// Allocator for 28-bit memory
49///
50/// This can be used to allocate memory in 28-bit address space.
51///
52/// # Examples
53///
54/// The allocated 28-bit region is described by a fat `Ptr28` pointer,
55/// which implements the `From` trait for common
56/// types such as `Vec<u8>` and `String`.
57/// ~~~
58/// let mut bank = Allocator::new(0x40000);
59/// let a = Vec::<u8>::from([7, 9, 13]);
60/// let ptr = bank.write(a.as_slice()); // dma write
61/// let b = Vec::<u8>::from(ptr); // dma read
62/// assert_eq!(a, b);
63/// ~~~
64///
65/// `Vec<Ptr28>` can be traversed almost as if a vector of values:
66/// ~~~
67/// let cnt = Vec::from([bank.push(b"first"), bank.push(b"second")])
68///      .iter()
69///      .copied()
70///      .map(String::from) // dma write
71///      .filter(|s| s.starts_with('s'))
72///      .count();
73/// assert_eq!(cnt, 1);
74/// ~~~
75pub struct Allocator {
76    /// Current 28-bit address
77    pub address: u32,
78}
79
80impl Allocator {
81    /// New allocator starting at `address`
82    pub const fn new(address: u32) -> Self {
83        Self { address }
84    }
85    /// DMA copy bytes to next free 28-bit memory location.
86    ///
87    /// Every call to `write` advances the address by `bytes.len()`.
88    pub fn write(&mut self, bytes: &[u8]) -> Ptr28 {
89        let len = bytes.len();
90        let ptr = Ptr28 {
91            address: self.address,
92            len,
93        };
94        unsafe {
95            lcopy(bytes.as_ptr() as u32, self.address, len);
96        }
97        self.address += len as u32;
98        ptr
99    }
100}
101
102/// Fat pointer to region in 28-bit address space
103#[derive(Clone, Copy)]
104pub struct Ptr28 {
105    /// Address
106    pub address: u32,
107    /// Length in bytes
108    pub len: usize,
109}
110
111impl From<Ptr28> for String {
112    fn from(value: Ptr28) -> Self {
113        // naughty camouflage of unsafe code...
114        unsafe { Self::from_utf8_unchecked(value.into()) }
115    }
116}
117
118impl From<Ptr28> for Vec<u8> {
119    fn from(value: Ptr28) -> Self {
120        MemoryIterator::new(value.address).get_chunk(value.len)
121    }
122}
123
124/// Never-ending iterator to lpeek into 28-bit memory
125///
126/// The address is automatically pushed forward with every byte read.
127///
128/// # Examples
129/// ~~~
130/// const ADDRESS: u32 = 0x40000;
131/// let mut mem = MemoryIterator::new(ADDRESS);
132/// let byte: u8 = mem.next().unwrap();
133/// let v: Vec<u8> = mem.get_chunk(10);
134/// for byte in mem.take(4) {
135///     println!("{}", byte);
136/// }
137/// assert_eq!(mem.address, ADDRESS + 1 + 10 + 4);
138/// ~~~
139///
140/// # Todo
141///
142/// This should eventually be submitted to the `mos-hardware` crate.
143#[derive(Copy, Clone, Eq, PartialEq)]
144pub struct MemoryIterator {
145    /// Next address
146    pub address: u32,
147}
148
149impl MemoryIterator {
150    pub const fn new(address: u32) -> Self {
151        Self { address }
152    }
153
154    /// Peek `n` bytes using fast Direct Memory Access (DMA) copy
155    ///
156    /// # Todo
157    ///
158    /// - Check that the DMA copy works as expected
159    #[allow(clippy::uninit_vec)]
160    pub fn get_chunk(&mut self, n: usize) -> Vec<u8> {
161        let mut dst = Vec::<u8>::with_capacity(n);
162        unsafe {
163            dst.set_len(n);
164            lcopy(self.address, dst.as_mut_slice().as_ptr() as u32, n);
165        }
166        self.address += n as u32;
167        dst
168    }
169}
170
171impl Iterator for MemoryIterator {
172    type Item = u8;
173    fn next(&mut self) -> Option<Self::Item> {
174        let value = lpeek(self.address);
175        self.address += 1;
176        Some(value)
177    }
178
179    #[allow(clippy::uninit_assumed_init)]
180    fn next_chunk<const N: usize>(
181        &mut self,
182    ) -> Result<[Self::Item; N], core::array::IntoIter<Self::Item, N>>
183    where
184        Self: Sized,
185    {
186        let dst: [Self::Item; N] = unsafe { MaybeUninit::uninit().assume_init() };
187        unsafe {
188            lcopy(self.address, dst.as_ptr() as u32, N);
189        }
190        self.address += N as u32;
191        Ok(dst)
192    }
193
194    #[cfg(version("1.69"))]
195    fn advance_by(&mut self, n: usize) -> Result<(), core::num::NonZeroUsize> {
196        self.address += n as u32;
197        Ok(())
198    }
199    #[cfg(not(version("1.69")))]
200    fn advance_by(&mut self, n: usize) -> Result<(), usize> {
201        self.address += n as u32;
202        Ok(())
203    }
204}
205
206impl Default for libc::dmagic_dmalist {
207    fn default() -> Self {
208        Self {
209            option_0b: 0x0b,
210            option_80: 0x80,
211            option_81: 0x81,
212            option_85: 0x85,
213            sub_cmd: 0x00,
214            end_of_options: 0x00,
215            dest_skip: 1,
216            source_addr: 0,
217            source_bank: 0,
218            source_mb: 0,
219            dest_addr: 0,
220            dest_bank: 0,
221            dest_mb: 0,
222            count: 0,
223            modulo: 0,
224            command: Self::COPY,
225        }
226    }
227}
228
229impl libc::dmagic_dmalist {
230    /// Copy command
231    const COPY: u8 = 1;
232    /// Perform the copy once data is in place
233    pub fn do_dma(&self) {
234        let self_ptr = core::ptr::addr_of!(self) as u16;
235        unsafe {
236            libc::mega65_io_enable();
237            poke!(0xd702 as *mut u8, 0);
238            poke!(0xd704 as *mut u8, 0); // List is in $00xxxxx
239            poke!(0xd701 as *mut u8, (self_ptr >> 8) as u8);
240            poke!(0xd701 as *mut u8, (self_ptr & 0xff) as u8); // triggers enhanced DMA
241        }
242    }
243    /// Set source address
244    fn set_source(&mut self, src: u32) {
245        // User should provide 28-bit address for IO
246        // (otherwise we can't DMA to/from RAM under IO)
247        //  if (source_address>=0xd000 && source_address<0xe000)
248        //    dmalist.source_bank|=0x80;
249        self.source_mb = (src >> 20) as u8;
250        self.source_addr = (src & 0xffff) as u16;
251        self.source_bank = ((src >> 16) & 0x0f) as u8;
252    }
253
254    /// Set destination address
255    fn set_destinaion(&mut self, dst: u32) {
256        // User should provide 28-bit address for IO
257        // (otherwise we can't DMA to/from RAM under IO)
258        //  if (destination_address>=0xd000 && destination_address<0xe000)
259        //    dmalist.dest_bank|=0x80;
260        self.dest_mb = (dst >> 20) as u8;
261        self.dest_addr = (dst & 0xffff) as u16;
262        self.dest_bank = ((dst >> 16) & 0x0f) as u8;
263    }
264    fn init(&mut self) {
265        self.option_0b = 0x0b;
266        self.option_80 = 0x80;
267        self.option_81 = 0x81;
268        self.option_85 = 0x85;
269        self.sub_cmd = 0x00;
270        self.end_of_options = 0x00;
271        self.dest_skip = 1;
272    }
273
274    /// Peek into memory using DMA copy
275    pub fn lpeek(&mut self, src: u32) -> u8 {
276        let dst: u8 = 0;
277        self.copy(src, (dst as *mut u8) as u32, 1);
278        dst
279    }
280
281    pub fn lpoke(&mut self, dst: u32, value: u8) {
282        self.copy((value as *mut u8) as u32, dst, 1);
283    }
284
285    /// DMA copy `n` bytes from `src` address to `dst` address
286    pub fn copy(&mut self, src: u32, dst: u32, n: usize) {
287        self.command = Self::COPY;
288        self.count = n as u16;
289        self.init();
290        self.set_source(src);
291        self.set_destinaion(dst);
292        self.do_dma();
293    }
294}