4

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
zx64
  • 41
  • 2
  • 3
    Do you need threads? If not, switch to `Rc` and `Cell` / `RefCell`. – Shepmaster Aug 01 '16 at 01:49
  • Right now I don't need threads, but not sure if I would use them in the future. – zx64 Aug 01 '16 at 02:08
  • 2
    @zx64: Might want to start without then, rustc will emit an error if you try to share something you should not when moving to a threaded program anyway :) – Matthieu M. Aug 01 '16 at 12:11
  • `Mmu` trait is good idea, but why do you keep stuff as `Mutex>`, if you could just have `Mutex` and `Mutex`? You're unnecessary loosing the concrete type which should be avoided. You can still call trait methods implemented by concrete type. Also, `Arc` and `Mutex` are unncessary until you figure out which parts exacty do you want to make multirehtreaded. You probably want just `Vec`, or `Vec>>`. Other than this, it's not really a Rust question, that can just answered correctly. – dpc.pw Aug 15 '16 at 18:19

0 Answers0