Skip to main content

bonsai_eval_utils/
ram_disk.rs

1//! In-memory disk implementation for testing and debugging.
2//!
3//! This module contains a simple in-memory disk implementation that can be used
4//! for testing and debugging. It is not suitable for production use.
5//!
6//! It will emulate NOR flash by combining the data with the buffer via bitwise
7//! AND. Additonally it will enforce the specified write granularity and block
8//! size.
9
10
11use alloc::boxed::Box;
12use alloc::vec;
13use core::fmt;
14use core::ops;
15
16use bonsai_disk::Disk;
17use embedded_io::ErrorType;
18use generic_array::ConstArrayLength;
19use generic_array::IntoArrayLength;
20use generic_array::typenum::Const;
21
22use crate::IecNumber;
23use crate::MetricNumber;
24
25
26
27extern crate alloc;
28
29
30#[derive(Clone, Copy, Default)]
31pub struct AccessStats {
32	pub reads: usize,
33	pub read_bytes: usize,
34	pub writes: usize,
35	pub write_bytes: usize,
36	pub erases: usize,
37}
38impl ops::Add for AccessStats {
39	type Output = Self;
40
41	fn add(self, other: Self) -> Self {
42		Self {
43			reads: self.reads + other.reads,
44			read_bytes: self.read_bytes + other.read_bytes,
45			writes: self.writes + other.writes,
46			write_bytes: self.write_bytes + other.write_bytes,
47			erases: self.erases + other.erases,
48		}
49	}
50}
51impl ops::Sub for AccessStats {
52	type Output = Self;
53
54	fn sub(self, other: Self) -> Self {
55		Self {
56			reads: self.reads.saturating_sub(other.reads),
57			read_bytes: self.read_bytes.saturating_sub(other.read_bytes),
58			writes: self.writes.saturating_sub(other.writes),
59			write_bytes: self.write_bytes.saturating_sub(other.write_bytes),
60			erases: self.erases.saturating_sub(other.erases),
61		}
62	}
63}
64impl ops::AddAssign for AccessStats {
65	fn add_assign(&mut self, other: Self) {
66		*self = self.clone() + other;
67	}
68}
69impl ops::SubAssign for AccessStats {
70	fn sub_assign(&mut self, other: Self) {
71		*self = self.clone() - other;
72	}
73}
74impl ops::Mul<usize> for AccessStats {
75	type Output = Self;
76
77	fn mul(self, rhs: usize) -> Self {
78		Self {
79			reads: self.reads * rhs,
80			read_bytes: self.read_bytes * rhs,
81			writes: self.writes * rhs,
82			write_bytes: self.write_bytes * rhs,
83			erases: self.erases * rhs,
84		}
85	}
86}
87impl ops::Div<usize> for AccessStats {
88	type Output = Self;
89
90	fn div(self, rhs: usize) -> Self {
91		Self {
92			reads: self.reads / rhs,
93			read_bytes: self.read_bytes / rhs,
94			writes: self.writes / rhs,
95			write_bytes: self.write_bytes / rhs,
96			erases: self.erases / rhs,
97		}
98	}
99}
100impl ops::MulAssign<usize> for AccessStats {
101	fn mul_assign(&mut self, rhs: usize) {
102		*self = self.clone() * rhs;
103	}
104}
105impl ops::DivAssign<usize> for AccessStats {
106	fn div_assign(&mut self, rhs: usize) {
107		*self = self.clone() / rhs;
108	}
109}
110impl fmt::Debug for AccessStats {
111	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112		if self.writes == 0 && self.write_bytes == 0 && self.erases == 0 {
113			f.debug_struct("ReadStats")
114				.field("reads", &self.reads)
115				.field("read_bytes", &self.read_bytes)
116				.finish()
117		} else {
118			f.debug_struct("AccessStats")
119				.field("reads", &self.reads)
120				.field("read_bytes", &self.read_bytes)
121				.field("writes", &self.writes)
122				.field("write_bytes", &self.write_bytes)
123				.field("erases", &self.erases)
124				.finish()
125		}
126	}
127}
128impl fmt::Display for AccessStats {
129	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130		if self.writes == 0 && self.write_bytes == 0 && self.erases == 0 {
131			write!(
132				f,
133				"reads {}, {}B",
134				MetricNumber(self.reads),
135				IecNumber(self.read_bytes)
136			)
137		} else {
138			write!(
139				f,
140				"reads {}, {}B, writes {}, {}B, erases {} pages",
141				MetricNumber(self.reads),
142				IecNumber(self.read_bytes),
143				MetricNumber(self.writes),
144				IecNumber(self.write_bytes),
145				MetricNumber(self.erases),
146			)
147		}
148	}
149}
150
151/// A simple in-memory disk.
152///
153/// Used for testing and debugging.
154#[derive(Debug, Clone)]
155pub struct RamDisk<const BLOCK_SIZE: usize, const WRITE_SIZE: usize> {
156	pub data: Box<[u8]>,
157
158	pub stats: AccessStats,
159}
160impl<const BLOCK_SIZE: usize, const WRITE_SIZE: usize> RamDisk<BLOCK_SIZE, WRITE_SIZE> {
161	pub fn new(blocks: usize) -> Self {
162		Self {
163			data: vec![0; blocks * BLOCK_SIZE].into_boxed_slice(),
164			stats: AccessStats::default(),
165		}
166	}
167
168	pub fn new_erased(blocks: usize) -> Self {
169		Self {
170			data: vec![0xFF; blocks * BLOCK_SIZE].into_boxed_slice(),
171			stats: AccessStats::default(),
172		}
173	}
174
175	pub fn clear_stats(&mut self) {
176		self.stats = AccessStats::default();
177	}
178}
179
180
181impl<const BLOCK_SIZE: usize, const WRITE_SIZE: usize> AsMut<Self>
182	for RamDisk<BLOCK_SIZE, WRITE_SIZE>
183{
184	fn as_mut(&mut self) -> &mut Self {
185		self
186	}
187}
188
189impl<const BLOCK_SIZE: usize, const WRITE_SIZE: usize> ErrorType
190	for RamDisk<BLOCK_SIZE, WRITE_SIZE>
191{
192	type Error = core::convert::Infallible;
193}
194impl<const BLOCK_SIZE: usize, const WRITE_SIZE: usize> Disk for RamDisk<BLOCK_SIZE, WRITE_SIZE>
195where
196	Const<WRITE_SIZE>: IntoArrayLength,
197	// Const<WRITE_SIZE>: ToUInt,
198	// typenum::U<WRITE_SIZE>: ArrayLength,
199{
200	type WRITE_GRANULARITY = ConstArrayLength<WRITE_SIZE>;
201
202	const ERASE_BLOCK_SIZE: usize = BLOCK_SIZE;
203
204	#[track_caller]
205	fn read(&mut self, offset: usize, buf: &mut [u8]) -> Result<usize, Self::Error> {
206		assert!(BLOCK_SIZE % WRITE_SIZE == 0);
207		assert!(BLOCK_SIZE >= WRITE_SIZE);
208
209		assert!(
210			offset % WRITE_SIZE == 0,
211			"Unaligned read: {offset} % {WRITE_SIZE}"
212		);
213		assert!(
214			offset + buf.len() <= self.data.len(),
215			"Read out of bounds: {offset} + {} > {}",
216			buf.len(),
217			self.data.len()
218		);
219
220		// An empty buffer is a valid read.
221		if buf.len() == 0 {
222			return Ok(0);
223		}
224
225		assert!(
226			buf.len() >= WRITE_SIZE,
227			"Buffer too small: {} < {WRITE_SIZE}",
228			buf.len()
229		);
230
231		// Round buf len down to the next multiple of WRITE_SIZE
232		let buf_len = buf.len() / WRITE_SIZE * WRITE_SIZE;
233
234		// Read at most one block.
235		let block_end = ((offset / BLOCK_SIZE) + 1) * BLOCK_SIZE;
236		let read_end = usize::min(block_end, offset + buf_len);
237		let read_len = read_end - offset;
238
239		buf[..read_len].copy_from_slice(&self.data[offset..read_end]);
240		self.stats.reads += 1;
241		self.stats.read_bytes += read_len;
242
243		Ok(read_len)
244	}
245
246	#[track_caller]
247	fn write(&mut self, offset: usize, buf: &[u8]) -> Result<usize, Self::Error> {
248		assert!(BLOCK_SIZE % WRITE_SIZE == 0);
249		assert!(BLOCK_SIZE >= WRITE_SIZE);
250
251		assert!(
252			offset % WRITE_SIZE == 0,
253			"Unaligned write: {offset} % {WRITE_SIZE}"
254		);
255		assert!(
256			offset + buf.len() <= self.data.len(),
257			"Write out of bounds: {offset} + {} > {}",
258			buf.len(),
259			self.data.len()
260		);
261
262		// An empty buffer is a valid read.
263		if buf.len() == 0 {
264			return Ok(0);
265		}
266
267		assert!(
268			buf.len() >= WRITE_SIZE,
269			"Buffer too small: {} < {WRITE_SIZE}",
270			buf.len()
271		);
272
273		// Round buf len down to the next multiple of WRITE_SIZE
274		let buf_len = buf.len() / WRITE_SIZE * WRITE_SIZE;
275
276		// Write at most one block.
277		let block_end = ((offset / BLOCK_SIZE) + 1) * BLOCK_SIZE;
278		let write_end = usize::min(block_end, offset + buf_len);
279		let write_len = write_end - offset;
280
281		// Emulate NOR flash by combining the data with the buffer via bitwise AND
282		for i in 0..write_len {
283			self.data[offset + i] &= buf[i];
284		}
285
286		self.stats.writes += 1;
287		self.stats.write_bytes += write_len;
288
289		Ok(write_len)
290	}
291
292	#[track_caller]
293	fn erase(&mut self, block: usize) -> Result<(), Self::Error> {
294		assert!(BLOCK_SIZE % WRITE_SIZE == 0);
295		assert!(BLOCK_SIZE >= WRITE_SIZE);
296
297		let offset = block * BLOCK_SIZE;
298		let len = BLOCK_SIZE;
299
300		// Emulate NOR flash by setting the block to all ones
301		for i in (offset)..(offset + len) {
302			self.data[i] = 0xFF;
303		}
304
305		self.stats.erases += 1;
306
307		Ok(())
308	}
309
310	#[track_caller]
311	fn block_count(&self) -> usize {
312		assert!(BLOCK_SIZE % WRITE_SIZE == 0);
313		assert!(BLOCK_SIZE >= WRITE_SIZE);
314
315		debug_assert!(self.data.len() % BLOCK_SIZE == 0);
316
317		self.data.len() / BLOCK_SIZE
318	}
319}
320
321
322#[cfg(test)]
323mod test {
324	use super::*;
325
326	const BLOCK_SIZE: usize = 512;
327	const WRITE_SIZE: usize = 8;
328
329	#[test]
330	fn test_ramdisk_simple_write() {
331		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
332
333		// Write data to the disk
334		let write_data = [1, 2, 3, 4, 5, 6, 7, 8];
335		let write_result = disk.write(2 * WRITE_SIZE, &write_data);
336		assert!(write_result.is_ok());
337		assert_eq!(write_result.unwrap(), write_data.len());
338
339		// Check the content of the array in the ram disk directly
340		for i in 0..write_data.len() {
341			assert_eq!(disk.data[2 * WRITE_SIZE + i], write_data[i]);
342		}
343
344		// Assert write stats
345		assert_eq!(disk.stats.writes, 1);
346		assert_eq!(disk.stats.write_bytes, write_data.len());
347		assert_eq!(disk.stats.reads, 0);
348		assert_eq!(disk.stats.read_bytes, 0);
349		assert_eq!(disk.stats.erases, 0);
350	}
351
352	#[test]
353	fn test_ramdisk_partial_write() {
354		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
355
356		// Write data to the disk with a 12 byte buffer
357		let write_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
358		let write_result = disk.write(2 * WRITE_SIZE, &write_data);
359		assert!(write_result.is_ok());
360		assert_eq!(write_result.unwrap(), 8); // Only the first 8 bytes should be written
361
362		// Check the content of the array in the ram disk directly
363		for i in 0..8 {
364			assert_eq!(disk.data[2 * WRITE_SIZE + i], write_data[i]);
365		}
366
367		// Ensure the remaining bytes are unchanged (still erased)
368		for i in 8..12 {
369			assert_eq!(disk.data[2 * WRITE_SIZE + i], 0xFF);
370		}
371
372		// Assert write stats
373		assert_eq!(disk.stats.writes, 1);
374		assert_eq!(disk.stats.write_bytes, 8);
375		assert_eq!(disk.stats.reads, 0);
376		assert_eq!(disk.stats.read_bytes, 0);
377		assert_eq!(disk.stats.erases, 0);
378	}
379
380	#[test]
381	fn test_ramdisk_simple_read() {
382		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
383
384		// Directly set some bytes in the disk array
385		let direct_data = [10, 20, 30, 40, 50, 60, 70, 80];
386		for i in 0..direct_data.len() {
387			disk.data[2 * WRITE_SIZE + i] = direct_data[i];
388		}
389
390		// Read data from the disk
391		let mut read_data = [0u8; 8];
392		let read_result = disk.read(2 * WRITE_SIZE, &mut read_data);
393		assert!(read_result.is_ok());
394		assert_eq!(read_result.unwrap(), direct_data.len());
395		assert_eq!(read_data, direct_data);
396
397		// Assert read stats
398		assert_eq!(disk.stats.reads, 1);
399		assert_eq!(disk.stats.read_bytes, direct_data.len());
400		assert_eq!(disk.stats.writes, 0);
401		assert_eq!(disk.stats.write_bytes, 0);
402		assert_eq!(disk.stats.erases, 0);
403	}
404
405	#[test]
406	fn test_ramdisk_partial_read() {
407		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
408
409		// Directly set some bytes in the disk array
410		let direct_data = [10, 20, 30, 40, 50, 60, 70, 80];
411		for i in 0..direct_data.len() {
412			disk.data[2 * WRITE_SIZE + i] = direct_data[i];
413		}
414
415		// Read data from the disk with a 12 byte buffer
416		let mut read_data = [0u8; 12];
417		let read_result = disk.read(2 * WRITE_SIZE, &mut read_data);
418		assert!(read_result.is_ok());
419		assert_eq!(read_result.unwrap(), 8); // Only the first 8 bytes should be read
420
421		// Check the content of the read data
422		for i in 0..8 {
423			assert_eq!(read_data[i], direct_data[i]);
424		}
425
426		// Ensure the remaining bytes are unchanged (still zero)
427		for i in 8..12 {
428			assert_eq!(read_data[i], 0);
429		}
430
431		// Assert read stats
432		assert_eq!(disk.stats.reads, 1);
433		assert_eq!(disk.stats.read_bytes, 8);
434		assert_eq!(disk.stats.writes, 0);
435		assert_eq!(disk.stats.write_bytes, 0);
436		assert_eq!(disk.stats.erases, 0);
437	}
438
439	#[test]
440	fn test_ramdisk_read_write() {
441		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
442
443		// Write data to the disk
444		let write_data = [1, 2, 3, 4, 5, 6, 7, 8];
445		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
446		assert!(write_result.is_ok());
447		assert_eq!(write_result.unwrap(), write_data.len());
448
449		// Read data from the disk
450		let mut read_data = [0u8; 8];
451		let read_result = disk.read(3 * WRITE_SIZE, &mut read_data);
452		assert!(read_result.is_ok());
453		assert_eq!(read_result.unwrap(), write_data.len());
454		assert_eq!(read_data, write_data);
455
456		// Assert read/write stats
457		assert_eq!(disk.stats.reads, 1);
458		assert_eq!(disk.stats.read_bytes, write_data.len());
459		assert_eq!(disk.stats.writes, 1);
460		assert_eq!(disk.stats.write_bytes, write_data.len());
461		assert_eq!(disk.stats.erases, 0);
462	}
463
464	#[test]
465	fn test_ramdisk_single_write_two_reads() {
466		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
467
468		// Write data to the disk
469		let write_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
470		let write_result = disk.write(2 * WRITE_SIZE, &write_data);
471		assert!(write_result.is_ok());
472		assert_eq!(write_result.unwrap(), write_data.len());
473
474		// First read from the disk
475		let mut read_data1 = [0u8; 8];
476		let read_result1 = disk.read(2 * WRITE_SIZE, &mut read_data1);
477		assert!(read_result1.is_ok());
478		assert_eq!(read_result1.unwrap(), 8);
479		assert_eq!(read_data1, [1, 2, 3, 4, 5, 6, 7, 8]);
480
481		// Second read from the disk
482		let mut read_data2 = [0u8; 8];
483		let read_result2 = disk.read(2 * WRITE_SIZE + 8, &mut read_data2);
484		assert!(read_result2.is_ok());
485		assert_eq!(read_result2.unwrap(), 8);
486		assert_eq!(read_data2, [9, 10, 11, 12, 13, 14, 15, 16]);
487
488		// Assert read/write stats
489		assert_eq!(disk.stats.reads, 2);
490		assert_eq!(disk.stats.read_bytes, 16);
491		assert_eq!(disk.stats.writes, 1);
492		assert_eq!(disk.stats.write_bytes, 16);
493		assert_eq!(disk.stats.erases, 0);
494	}
495
496	#[test]
497	fn test_ramdisk_two_writes_single_read() {
498		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
499
500		// First write to the disk
501		let write_data1 = [1, 2, 3, 4, 5, 6, 7, 8];
502		let write_result1 = disk.write(2 * WRITE_SIZE, &write_data1);
503		assert!(write_result1.is_ok());
504		assert_eq!(write_result1.unwrap(), write_data1.len());
505
506		// Second write to the disk
507		let write_data2 = [9, 10, 11, 12, 13, 14, 15, 16];
508		let write_result2 = disk.write(3 * WRITE_SIZE, &write_data2);
509		assert!(write_result2.is_ok());
510		assert_eq!(write_result2.unwrap(), write_data2.len());
511
512		// Read data from the disk
513		let mut read_data = [0u8; 16];
514		let read_result = disk.read(2 * WRITE_SIZE, &mut read_data);
515		assert!(read_result.is_ok());
516		assert_eq!(read_result.unwrap(), 16);
517		assert_eq!(
518			read_data,
519			[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
520		);
521
522		// Assert read/write stats
523		assert_eq!(disk.stats.reads, 1);
524		assert_eq!(disk.stats.read_bytes, 16);
525		assert_eq!(disk.stats.writes, 2);
526		assert_eq!(disk.stats.write_bytes, 16);
527		assert_eq!(disk.stats.erases, 0);
528	}
529
530	#[test]
531	fn test_ramdisk_repeated_write() {
532		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
533
534		// Write data to the disk
535		let write_data = [1, 2, 3, 4, 5, 6, 7, 8];
536		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
537		assert!(write_result.is_ok());
538		assert_eq!(write_result.unwrap(), write_data.len());
539
540		// Write the same data again to the same location
541		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
542		assert!(write_result.is_ok());
543		assert_eq!(write_result.unwrap(), write_data.len());
544
545		// Read data from the disk
546		let mut read_data = [0u8; 8];
547		let read_result = disk.read(3 * WRITE_SIZE, &mut read_data);
548		assert!(read_result.is_ok());
549		assert_eq!(read_result.unwrap(), write_data.len());
550		assert_eq!(read_data, write_data);
551
552		// Assert read/write stats
553		assert_eq!(disk.stats.reads, 1);
554		assert_eq!(disk.stats.read_bytes, write_data.len());
555		assert_eq!(disk.stats.writes, 2);
556		assert_eq!(disk.stats.write_bytes, write_data.len() * 2);
557		assert_eq!(disk.stats.erases, 0);
558	}
559
560	#[test]
561	fn test_ramdisk_erase() {
562		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new_erased(4);
563
564		// Write data to the disk
565		let write_data = [1, 2, 3, 4, 5, 6, 7, 8];
566		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
567		assert!(write_result.is_ok());
568		assert_eq!(write_result.unwrap(), write_data.len());
569
570		// Erase the block
571		let erase_result = disk.erase(0);
572		assert!(erase_result.is_ok());
573
574		// Read data from the disk
575		let mut read_data = [0u8; 8];
576		let read_result = disk.read(3 * WRITE_SIZE, &mut read_data);
577		assert!(read_result.is_ok());
578		assert_eq!(read_result.unwrap(), write_data.len());
579		// No longer what we wrote
580		assert_ne!(read_data, write_data);
581
582		// Assert read/write stats
583		assert_eq!(disk.stats.reads, 1);
584		assert_eq!(disk.stats.read_bytes, 8);
585		assert_eq!(disk.stats.writes, 1);
586		assert_eq!(disk.stats.write_bytes, 8);
587		assert_eq!(disk.stats.erases, 1);
588	}
589
590	#[test]
591	fn test_ramdisk_write_without_erase() {
592		let mut disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new(4);
593
594		// Just write something to make it non-erased
595		let write_garbage = [0; 8];
596		let write_result = disk.write(3 * WRITE_SIZE, &write_garbage);
597		assert!(write_result.is_ok());
598		assert_eq!(write_result.unwrap(), write_garbage.len());
599
600		// Write data to the disk without erasing yields garbage
601		let write_data = [1, 2, 3, 4, 5, 6, 7, 8];
602		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
603		assert!(write_result.is_ok());
604		assert_eq!(write_result.unwrap(), write_data.len());
605
606		// Read data from the disk
607		let mut read_data = [0u8; 8];
608		let read_result = disk.read(3 * WRITE_SIZE, &mut read_data);
609		assert!(read_result.is_ok());
610		assert_eq!(read_result.unwrap(), write_data.len());
611		// Not what we wrote
612		assert_ne!(read_data, write_data);
613
614		// Erase the block
615		let erase_result = disk.erase(0);
616		assert!(erase_result.is_ok());
617
618		// Write data to the disk after erasing works
619		let write_result = disk.write(3 * WRITE_SIZE, &write_data);
620		assert!(write_result.is_ok());
621		assert_eq!(write_result.unwrap(), write_data.len());
622
623		// Read data from the disk
624		let read_result = disk.read(3 * WRITE_SIZE, &mut read_data);
625		assert!(read_result.is_ok());
626		assert_eq!(read_result.unwrap(), write_data.len());
627		// Exactly what we wrote
628		assert_eq!(read_data, write_data);
629
630		// Assert read/write stats
631		assert_eq!(disk.stats.reads, 2);
632		assert_eq!(disk.stats.read_bytes, 16);
633		assert_eq!(disk.stats.writes, 3);
634		assert_eq!(disk.stats.write_bytes, 24);
635		assert_eq!(disk.stats.erases, 1);
636	}
637
638	#[test]
639	fn test_ramdisk_block_count() {
640		let disk = RamDisk::<BLOCK_SIZE, WRITE_SIZE>::new(3);
641
642		assert_eq!(disk.block_count(), 3);
643	}
644}