Hi guys,
There's been some on-list chatter regarding adding an MMU to the
LatticeMico32 core. I've been scribbling down some ideas regarding this,
and figured it'd be nice to share :)
My first thought was "how do we handle supervisor mode?" -- nearly every
MMU I've seen has some separation of user and kernel code. The x86 has
Rings 0 to 3, the ARM has User, Supervisor, IRQ and FastIRQ modes (and
OS Mode if you tie an ARM1 to a MEMC1), and so on.
So what we need is a way to differentiate between at least two privilege
levels, and a way to allow Supervisor code to switch to User mode, but
not the other way around.
There's a fairly obvious solution to this:
- We have two bits of privilege store, giving four privilege levels.
- We have two PRIVILEGE registers (2x2bits = one nibble). These are
called "Last Privilege" and "Current Privilege".
- Current Privilege is immutable. Last Privilege is mutable, but only
to the extent that its current value can be reduced. That is to say, you
change it from L3 (Supervisor) to L2, L1 or L0 (User), but only the CPU
can set it back to L3.
- On reset, both Current Priv and Last Priv are set to L3 (Supervisor)
- When an Exception is invoked (SYSCALL, IRQ, NMI, or one of the
Abort exceptions), the CPU copies Current Privilege into Last Privilege,
at the same time that it copies IE into EIE.
- When an ERET is executed, Last Priv is copied into Current Priv.
This restores the privilege state of the code which was running before
the exception occurred. We assume that an exception cannot occur while
another
- To drop privileges (assuming unallocated CSR 0x0b is used as the
Privilege Control CSR):
RCSR 0xb, r1
AND r1, r1, 0xfff3 ; clear lastpriv
ERET
Assumption: the bit layout of CSR 0x0b is:
[ - | LastPriv | CurPriv ]
31.. 4 3 2 1 0
- To regain privileges after dropping them, we have to issue a
SYSCALL, and set LastPriv to 0b11 before we ERET.
- The MMU will need to know the privilege level of the
currently-executing instruction. So we add two output pins for CurPriv.
- Memory access rights enforcement is left up to an external MMU.
This leaves the problem of memory mapping. Whenever we change the
mapping, we will need to flush the ICACHE and DCACHE... Other than that,
the only potential issue is the instruction pipeline -- to deal with
that, we add a few NOPs after the cache flush requests, and keep the
Kernel stuff in a fixed area of memory (read as: the kernel remains
static in both virtual and physical space; user code may move around as
the memory allocator deems necessary).
In theory this should be backwards compatible with any previous code
(RTEMS, the Linux-nommu port, ...), as long as the MMU defaults to a
linear 1:1 logical/physical mapping on boot. This is similar to how the
68000 did things -- if you didn't want privilege separation, you ignored
that particular status bit and wrote all your code in supervisor mode.
If you had an MMU, you could stop user code from meddling with hardware
registers and such.
Any comments, guys?
Thanks,
--
Phil.
[email protected]
http://www.philpem.me.uk/
_______________________________________________
http://lists.milkymist.org/listinfo.cgi/devel-milkymist.org
IRC: #milkymist@Freenode
Twitter: www.twitter.com/milkymistvj
Ideas? http://milkymist.uservoice.com