I'm modeling a system that has a CPU, GPU, MMU, APU, and maybe other things. The CPU will have mutable references to the GPU, MMU, and APU. I also would like the MMU to have a way to be able to call specific functions on the GPU and APU. Where this comes into play is when I'm mapping memory to different locations. The MMU takes care of that, and will dispatch to GPU or APU if the memory request is in those devices.
Here is how I modeled it using Arc
and Mutex
. I was wondering if there is a cleaner way of achieving what I did here, or if this is the correct method.
use std::sync::{Arc, Mutex};
trait MMU {
fn read(&self, addr: usize) -> u8;
fn write(&mut self, addr: usize, value: u8);
}
#[allow(dead_code)]
struct Cpu {
apu: Arc<Mutex<Box<MMU>>>,
mmu: Box<Mmu>,
gpu: Arc<Mutex<Box<MMU>>>,
}
struct Mmu {
map: Vec<(usize, usize, Arc<Mutex<Box<MMU>>>)>,
}
impl Mmu {
fn new() -> Mmu {
Mmu { map: vec![] }
}
fn add_mapping(&mut self, start: usize, end: usize, cb: Arc<Mutex<Box<MMU>>>) {
self.map.push((start, end, cb));
}
fn read(&self, addr: usize) -> u8 {
// See if the addr is in a range that is mapped, then
// call read on it.
for i in self.map.iter() {
if i.0 <= addr && addr <= i.1 {
let d = i.2.clone();
let d = d.lock().unwrap();
return d.read(addr);
}
}
println!("Mmu.read: {}", addr);
0
}
fn write(&mut self, addr: usize, value: u8) {
// See if the addr is in a range that is mapped, then
// call write on it.
for i in self.map.iter() {
if i.0 <= addr && addr <= i.1 {
let d = i.2.clone();
let mut d = d.lock().unwrap();
d.write(addr, value);
return;
}
}
println!("Mmu.write: {} {}", addr, value);
}
}
struct Gpu;
impl MMU for Gpu {
fn read(&self, addr: usize) -> u8 {
println!("Gpu.read: {}", addr);
0
}
fn write(&mut self, addr: usize, value: u8) {
println!("Gpu.write: {} {}", addr, value);
}
}
struct Apu;
impl MMU for Apu {
fn read(&self, addr: usize) -> u8 {
println!("Apu.read: {}", addr);
0
}
fn write(&mut self, addr: usize, value: u8) {
println!("Apu.write: {} {}", addr, value);
}
}
fn main() {
let apu = Arc::new(Mutex::new(Box::new(Apu) as Box<MMU>));
let gpu = Arc::new(Mutex::new(Box::new(Gpu) as Box<MMU>));
let mut mmu = Box::new(Mmu::new());
// If a memory read/write occurs at 0x300-0x400, then the
// GPU should handle it.
mmu.add_mapping(0x300, 0x400, gpu.clone());
// If a memory read/write occurs at 0x100-0x200, then the
// GPU should handle it.
mmu.add_mapping(0x100, 0x200, apu.clone());
// Otherwise the MMU will handle it.
let mut c = Cpu {
apu: apu,
gpu: gpu,
mmu: mmu,
};
c.mmu.read(0);
c.mmu.write(0, 5);
c.mmu.read(0x150);
c.mmu.write(0x150, 5);
c.mmu.read(0x350);
c.mmu.write(0x350, 5);
}
Rust play URL
I notice that my solution isn't ideal because the apu
and gpu
stored in the Cpu
are references to MMU
, and not to the original Apu
or Gpu
struct. It would need to be changed so that Cpu
can call other functions on Gpu
or Apu
that aren't defined in the MMU
trait.