Porting OpenBSD to the Solbourne S4000

[ Index ] [ Prev: Taming The MMU ] [ Next: A Few Words on Serial ]

Taming The MMU, continued

Page Directories & Page Tables

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 */
 * 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 S4000
So 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)
#ifdef SELFMOD
#endif SELFMOD

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 ]