[ Index ] [ Prev: Taming The MMU ] [ Next: A Few Words on Serial ]
Although this MMU uses a two level page table scheme, which is very common among 32 bit processors intended not to address more than a couple gigabytes of physical memory, this MMU still manages to be very particular in the way the page tables are managed.
Let's have a look at idt/mmu.h. We can find the following constants:
/* * Hardware context and segment information * Mnemonic decoding: * PTE - Page Table Entry * PT - A page table or group of PTEs (aka "segment") * PDE - Page Directory Entry * PDT - Page Directory Table or group of PDEs */ #define NPTEPERPT 1024 /* number of ptes per page table */ #define NPTEPERPTSHIFT 10 /* log2(NPTEPERPT) */ #define NPDEPERPDT 512 /* number of pdes per page dir table */ #define NPDEPERPDTSHIFT 9 /* log2(NPDEPERPDT) */ [...] /* * defines for performing software table walk based on a virtual address */ #define PDT_INDEX_SIZE 9 /* num bits of pdt index in va */ #define PT_INDEX_SIZE 10 /* num bits of pt index in va */ #define PDT_INDEX_SHIFT 23 /* shift for pde index */ #define PT_INDEX_SHIFT 13 /* shift for pt index */ #define PDT_INDEX_MASK 0xff800000 /* mask for pdt index */ #define PT_INDEX_MASK 0x007fe000 /* mask for pdt index */ [...] #define PDE_SIZE_SHIFT 3 /* log2(sizeof(struct pde)) */ #define PTE_SIZE_SHIFT 2 /* log2(sizeof(struct pte)) */
The first surprising thing is that, unlike many MMU, page tables are smaller than page themselves: while the pages are 8KB (since the last 13 bits of the virtual addresses are not interpreted), the page tables only contain 1024 entries of one 4 bytes word each, thus are 4KB long.
Moreover, the first level is even more surprising: entries there are not the expected 4 bytes page table pointer per entry, but 8 bytes! And since there are only 512 of them, the page directory is 4KB long, too.
Note that this unexpected setup still allow us to address 4GB of memory: evey page being 8KB, a page table can manage 1024 pages, thus 8MB; then the page directory can manage 512 page tables, thus 4GB.
The explanation for the 8 byte size of the page directory entries (first level) lies not in idt/mmu.h, but hidden in idt/vm_hat.h, which defines this:
#ifdef S4000 struct pde { addr_t pde_pa; /* pa of beginning of pte array */ addr_t pde_va; /* va of beginning of pte array */ }; #endif S4000So the MMU needs us to provide both physical and virtual addresses to our page tables. Why not?
Now, to connect those tables to the MMU logic, the address for the page directory needs to be stored in a MMU register, the Page Directory Base Ptr; it is accessed through ASI_PDBR, and of course, needs to contain a physical address.
With the physical address of the page directory, and the physical address of all page tables known, the MMU can retrieve any page table entry without needing to be reentrant. This is necessary for proper fault handling.
Actually, I am not sure the MMU ever uses the virtual address field in the page directory entries; but the operating system kernel can benefit from this. Moreover, this allows the page directory to have the same size as the page tables.
The page table entries layout is defined in idt/pte.h as:
#ifndef LOCORE struct pte { unsigned int pg_pfnum:19; /* page frame number */ unsigned int :1; unsigned int pg_lock:1; /* software lock pte bit */ unsigned int pg_nosync:1; /* software don't sync pg_r and pg_m */ unsigned int pg_r:1; /* software reference bit */ unsigned int pg_m:1; /* software modify bit */ unsigned int pg_sro:1; /* software read-only bit , for pg_m */ unsigned int pg_sv:1; /* software valid bit, for pg_r */ unsigned int pg_g:1; /* global bit */ unsigned int pg_ma:2; /* memory attribute bits */ unsigned int pg_prot:2; /* access protection */ unsigned int pg_v:1; /* valid bit */ }; #endif !LOCORE #define PG_V 0x00000001 /* page is valid */ #define PG_RO 0x00000002 /* read only page */ #define PG_UP 0x00000004 /* user protected page */ #define PG_MA0 0x00000008 /* memory attribute bit 0 */ #define PG_MA1 0x00000010 /* memory attribute bit 1 */ #define PG_MA (PG_MA1|PG_MA0) #define PG_G 0x00000020 /* global bit */ #define PG_SV 0x00000040 /* sftw valid, for sftw pg_r bit */ #define PG_SRO 0x00000080 /* sftw read-only, for sftw pg_m bit */ #define PG_M 0x00000100 /* software modify bit */ #define PG_R 0x00000200 /* software reference bit */ #define PG_NOSYNC 0x00000400 /* sftw nosync */ #define PG_LOCK 0x00000800 /* sftw lock */ #define PG_UNUSED1 0x00001000 /* unused */ #define PG_IO 0x00001000 /* this is defined to satisfy mapin(). * It is used by mapin() to * determine whether PTELD_IO should * be passed to segkmem_mapin(). */ #define PG_PFNUM 0xFFFFE000 /* page frame number mask */ [...] /* values for MA bits */ #define MA_IO 0 /* i/o */ #define MA_CACHE 1 /* cacheable */ #define MA_BYTE_SHARED 2 /* byte-writeable shared */ #ifdef SELFMOD #define MA_TEXT_PROTECT 2 /* protected from text accesses */ #endif SELFMOD #define MA_SHARED 3 /* non-byte-writeable shared */ #define MAKE_MA(v) ((v) << 3) #define PG_MA_IO MAKE_MA(MA_IO) #define PG_MA_CACHE MAKE_MA(MA_CACHE) #define PG_MA_BYTE_SHARED MAKE_MA(MA_BYTE_SHARED) #ifdef SELFMOD #define PG_MA_TEXT_PROTECT MAKE_MA(MA_TEXT_PROTECT) #endif SELFMOD #define PG_MA_SHARED MAKE_MA(MA_SHARED)
Nothing earth-shaking here, this is even pretty poor: the MMU enforces page validity, writeability, supervisor access restriction, and a few cacheability bits, but nothing more. No page execution check, no hardware modify and reference bits (which are the basis of the copy-on-write mechanism).
[ Index ] [ Prev: Taming The MMU ] [ Next: A Few Words on Serial ]