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}