patch-2.3.17 linux/arch/m68k/sun3/mmu_emu.c

Next file: linux/arch/m68k/sun3/prom/Makefile
Previous file: linux/arch/m68k/sun3/leds.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.16/linux/arch/m68k/sun3/mmu_emu.c linux/arch/m68k/sun3/mmu_emu.c
@@ -0,0 +1,445 @@
+/*
+** Tablewalk MMU emulator
+**
+** by Toshiyasu Morita
+**
+** Started 1/16/98 @ 2:22 am
+*/
+
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/delay.h>
+
+#include <asm/setup.h>
+#include <asm/traps.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/sun3mmu.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+#include <asm/oplib.h>
+#include <asm/mmu_context.h>
+
+extern void prom_reboot (char *) __attribute__ ((__noreturn__));
+
+#undef DEBUG_MMU_EMU
+
+/*
+** Defines
+*/
+
+#define CONTEXTS_NUM		8
+#define SEGMAPS_PER_CONTEXT_NUM 2048
+#define PAGES_PER_SEGMENT	16
+#define PMEGS_NUM		256
+#define PMEG_MASK		0xFF
+
+/*
+** Globals
+*/
+
+unsigned long vmalloc_end = 0;
+unsigned long pmeg_vaddr[PMEGS_NUM];
+unsigned char pmeg_alloc[PMEGS_NUM];
+unsigned char pmeg_ctx[PMEGS_NUM];
+
+/* pointers to the mm structs for each task in each
+   context. 0xffffffff is a marker for kernel context */
+struct mm_struct *ctx_alloc[CONTEXTS_NUM] = {0xffffffff, 0, 0, 0, 0, 0, 0, 0};
+/* has this context been mmdrop'd? */
+unsigned char ctx_live[CONTEXTS_NUM] = {1, 0, 0, 0, 0, 0, 0, 0};
+static unsigned char ctx_avail = CONTEXTS_NUM-1;
+unsigned char ctx_next_to_die = 1;
+
+/* array of pages to be marked off for the rom when we do mem_init later */
+/* 256 pages lets the rom take up to 2mb of physical ram..  I really
+   hope it never wants mote than that. */
+unsigned long rom_pages[256];
+
+/* Print a PTE value in symbolic form. For debugging. */
+void print_pte (pte_t pte)
+{
+#if 0
+	/* Verbose version. */
+	unsigned long val = pte_val (pte);
+	printk (" pte=%lx [addr=%lx",
+		val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT);
+	if (val & SUN3_PAGE_VALID)	printk (" valid");
+	if (val & SUN3_PAGE_WRITEABLE)	printk (" write");
+	if (val & SUN3_PAGE_SYSTEM)	printk (" sys");
+	if (val & SUN3_PAGE_NOCACHE)	printk (" nocache");
+	if (val & SUN3_PAGE_ACCESSED)	printk (" accessed");
+	if (val & SUN3_PAGE_MODIFIED)	printk (" modified");
+	switch (val & SUN3_PAGE_TYPE_MASK) {
+		case SUN3_PAGE_TYPE_MEMORY: printk (" memory"); break;
+		case SUN3_PAGE_TYPE_IO:     printk (" io");     break;
+		case SUN3_PAGE_TYPE_VME16:  printk (" vme16");  break;
+		case SUN3_PAGE_TYPE_VME32:  printk (" vme32");  break;
+	}
+	printk ("]\n");
+#else
+	/* Terse version. More likely to fit on a line. */
+	unsigned long val = pte_val (pte);
+	char flags[7], *type;
+
+	flags[0] = (val & SUN3_PAGE_VALID)     ? 'v' : '-';
+	flags[1] = (val & SUN3_PAGE_WRITEABLE) ? 'w' : '-';
+	flags[2] = (val & SUN3_PAGE_SYSTEM)    ? 's' : '-';
+	flags[3] = (val & SUN3_PAGE_NOCACHE)   ? 'x' : '-';
+	flags[4] = (val & SUN3_PAGE_ACCESSED)  ? 'a' : '-';
+	flags[5] = (val & SUN3_PAGE_MODIFIED)  ? 'm' : '-';
+	flags[6] = '\0';
+
+	switch (val & SUN3_PAGE_TYPE_MASK) {
+		case SUN3_PAGE_TYPE_MEMORY: type = "memory"; break;
+		case SUN3_PAGE_TYPE_IO:     type = "io"    ; break;
+		case SUN3_PAGE_TYPE_VME16:  type = "vme16" ; break;
+		case SUN3_PAGE_TYPE_VME32:  type = "vme32" ; break;
+		default: type = "unknown?"; break;
+	}
+
+	printk (" pte=%08lx [%07lx %s %s]\n",
+		val, (val & SUN3_PAGE_PGNUM_MASK) << PAGE_SHIFT, flags, type);
+#endif
+}
+
+/* Print the PTE value for a given virtual address. For debugging. */
+void print_pte_vaddr (unsigned long vaddr)
+{
+	printk (" vaddr=%lx [%02lx]", vaddr, sun3_get_segmap (vaddr));
+	print_pte (__pte (sun3_get_pte (vaddr)));
+}
+
+/*
+ * Initialise the MMU emulator.
+ */
+void mmu_emu_init(void)
+{
+	unsigned long seg, num;
+	int i,j;
+	extern char _stext, _etext;
+	unsigned long page;
+
+	memset(rom_pages, 0, sizeof(rom_pages));
+	memset(pmeg_vaddr, 0, sizeof(pmeg_vaddr));
+	memset(pmeg_alloc, 0, sizeof(pmeg_alloc));
+	memset(pmeg_ctx, 0, sizeof(pmeg_ctx));
+
+#ifdef DEBUG_MMU_EMU
+	printk ("mmu_emu_init: stext=%p etext=%p pmegs=%u\n", &_stext,
+		&_etext, (&_etext-&_stext+SUN3_PMEG_SIZE-1) >>
+		SUN3_PMEG_SIZE_BITS); 
+#endif
+
+	/* mark the pmegs copied in sun3-head.S as used */
+	for (i=0; i<10; ++i)
+		pmeg_alloc[i] = 2;
+
+	/* I'm thinking that most of the top pmeg's are going to be
+	   used for something, and we probably shouldn't risk it */
+	for(num = 0xf0; num <= 0xff; num++)
+		pmeg_alloc[num] = 2;
+
+	j = 0;
+	for (num=0, seg=0x0F800000; seg<0x10000000; seg+=16*PAGE_SIZE) {
+		if (sun3_get_segmap (seg) != SUN3_INVALID_PMEG) {
+#ifdef DEBUG_MMU_EMU
+			printk ("mapped:");
+			print_pte_vaddr (seg);
+#endif
+			// the lowest mapping here is the end of our
+			// vmalloc region
+			if(!vmalloc_end)
+				vmalloc_end = seg;
+
+			// mark the segmap alloc'd, and reserve any
+			// of the first 0xbff pages the hardware is
+			// already using...  does any sun3 support > 24mb?
+			pmeg_alloc[sun3_get_segmap(seg)] = 2;
+			for(i = 0; i < SUN3_PMEG_SIZE; i += PAGE_SIZE)
+			{
+				page = (sun3_get_pte(seg+i) &
+					SUN3_PAGE_PGNUM_MASK);
+
+				if((page) && (page < 0xbff)) {
+					rom_pages[j] = page;
+					j++;
+				}
+			}
+		}
+	}
+
+	
+	/* blank everything below the kernel, and we've got the base
+	   mapping to start all the contexts off with... */
+	for(seg = 0; seg < PAGE_OFFSET; seg += SUN3_PMEG_SIZE) 
+		sun3_put_segmap(seg, SUN3_INVALID_PMEG);
+
+	set_fs(MAKE_MM_SEG(3));
+	for(seg = 0; seg < 0x10000000; seg += SUN3_PMEG_SIZE) {
+		i = sun3_get_segmap(seg);
+		for(j = 1; j < CONTEXTS_NUM; j++)
+			(*(romvec->pv_setctxt))(j, (void *)seg, i);
+	}
+	set_fs(KERNEL_DS);
+	
+}
+
+/* called during mem_init to create the needed holes in the mem
+   mappings */
+void mmu_emu_reserve_pages(unsigned long max_page)
+{
+	int i = 0;
+	
+	while(rom_pages[i] != 0) {
+		// don't tamper with pages that wound up after end_mem
+		if(rom_pages[i] < max_page) 
+			set_bit(PG_reserved, &mem_map[rom_pages[i]].flags);
+	     i++;
+	}
+}
+
+/* erase the mappings for a dead context.  Uses the pg_dir for hints
+   as the pmeg tables proved somewhat unreliable, and unmapping all of
+   TASK_SIZE was much slower and no more stable. */
+/* todo: find a better way to keep track of the pmegs used by a
+   context for when they're cleared */
+void clear_context(unsigned long context)
+{
+     unsigned char oldctx;
+     unsigned long i;
+    
+     if(!ctx_alloc[context])
+	     panic("clear_context: context not allocated\n");
+
+     oldctx = sun3_get_context();
+
+     sun3_put_context(context);
+
+     /* ctx_live denotes if we're clearing a context with an active
+	mm, in which case we can use the pgd for clues and also should
+	mark that mm as lacking a context.  if the context has been
+	mmdrop'd, then flush outright. */
+
+     if(!ctx_live[context]) {
+	     for(i = 0; i < TASK_SIZE ; i += SUN3_PMEG_SIZE) 
+		     sun3_put_segmap(i, SUN3_INVALID_PMEG);
+     } else {
+	     pgd_t *pgd;
+	     
+	     pgd = ctx_alloc[context]->pgd;
+	     ctx_alloc[context]->context = SUN3_INVALID_CONTEXT;
+	     for(i = 0; i < (TASK_SIZE>>PGDIR_SHIFT); i++, pgd++)
+	     {
+		     if(pgd_val(*pgd)) {
+			     sun3_put_segmap(i<<PGDIR_SHIFT,
+					     SUN3_INVALID_PMEG);
+		     }
+	     }
+     }
+     
+     for(i = 0; i < SUN3_INVALID_PMEG; i++) {
+	     if((pmeg_ctx[i] == context) && (pmeg_alloc[i] != 2)) {
+		     pmeg_ctx[i] = 0;
+		     pmeg_alloc[i] = 0;
+		     pmeg_vaddr[i] = 0;
+	     }
+     }
+     
+     ctx_alloc[context] = (struct mm_struct *)0;
+     ctx_avail++;
+}
+
+/* gets an empty context.  if full, kills the next context listed to
+   die first */
+/* This context invalidation scheme is, well, totally arbitrary, I'm
+   sure it could be much more intellegent...  but it gets the job done
+   for now without much overhead in making it's decision. */
+/* todo: come up with optimized scheme for flushing contexts */
+unsigned long get_free_context(struct mm_struct *mm) 
+{
+	unsigned long new = 1;
+
+	if(!ctx_avail) {
+		/* kill someone to get our context */
+		new = ctx_next_to_die;
+		clear_context(new);
+		ctx_next_to_die = (ctx_next_to_die + 1) & 0x7;
+		if(!ctx_next_to_die)
+			ctx_next_to_die++;
+	} else {
+		while(new < CONTEXTS_NUM) {
+			if(ctx_alloc[new])
+				new++;
+			else
+				break;
+		}
+		// check to make sure one was really free...
+		if(new == CONTEXTS_NUM) 
+			panic("get_free_context: failed to find free context");
+	}
+
+	ctx_alloc[new] = mm;
+	ctx_live[new] = 1;
+	ctx_avail--;
+
+	return new;
+}
+
+/*
+ * Dynamically select a `spare' PMEG and use it to map virtual `vaddr' in
+ * `context'. Maintain internal PMEG management structures. This doesn't
+ * actually map the physical address, but does clear the old mappings.
+ */
+//todo: better allocation scheme? but is extra complexity worthwhile?
+//todo: only clear old entries if necessary? how to tell?
+
+static inline void mmu_emu_map_pmeg (int context, int vaddr)
+{
+	static unsigned char curr_pmeg = 128;
+	int i;
+
+	/* Round address to PMEG boundary. */
+	vaddr &= ~SUN3_PMEG_MASK;
+
+	/* Find a spare one. */
+	while (pmeg_alloc[curr_pmeg] == 2)
+		++curr_pmeg;
+
+
+#ifdef DEBUG_MMU_EMU
+printk("mmu_emu_map_pmeg: pmeg %x to context %d vaddr %x\n",
+       curr_pmeg, context, vaddr);
+#endif
+
+	/* Invalidate old mapping for the pmeg, if any */
+	if (pmeg_alloc[curr_pmeg] == 1) {
+		sun3_put_context(pmeg_ctx[curr_pmeg]);
+		sun3_put_segmap (pmeg_vaddr[curr_pmeg], SUN3_INVALID_PMEG);
+		sun3_put_context(context);
+	}
+
+	/* Update PMEG management structures. */
+	// don't take pmeg's away from the kernel...
+	if(vaddr >= PAGE_OFFSET) {
+		/* map kernel pmegs into all contexts */
+		unsigned char i;
+		
+		for(i = 0; i < CONTEXTS_NUM; i++) {
+			sun3_put_context(i);
+			sun3_put_segmap (vaddr, curr_pmeg);
+		}
+		sun3_put_context(context);
+		pmeg_alloc[curr_pmeg] = 2;
+		pmeg_ctx[curr_pmeg] = 0;
+		
+	}
+	else {
+		pmeg_alloc[curr_pmeg] = 1;
+		pmeg_ctx[curr_pmeg] = context;
+		sun3_put_segmap (vaddr, curr_pmeg);
+
+	}
+	pmeg_vaddr[curr_pmeg] = vaddr;
+
+	/* Set hardware mapping and clear the old PTE entries. */
+	for (i=0; i<SUN3_PMEG_SIZE; i+=SUN3_PTE_SIZE) 
+		sun3_put_pte (vaddr + i, SUN3_PAGE_SYSTEM);
+
+	/* Consider a different one next time. */
+	++curr_pmeg;
+}
+
+/*
+ * Handle a pagefault at virtual address `vaddr'; check if there should be a
+ * page there (specifically, whether the software pagetables indicate that
+ * there is). This is necessary due to the limited size of the second-level
+ * Sun3 hardware pagetables (256 groups of 16 pages). If there should be a
+ * mapping present, we select a `spare' PMEG and use it to create a mapping.
+ * `read_flag' is nonzero for a read fault; zero for a write. Returns nonzero
+ * if we successfully handled the fault.
+ */
+//todo: should we bump minor pagefault counter? if so, here or in caller?
+//todo: possibly inline this into bus_error030 in <asm/buserror.h> ?
+
+// kernel_fault is set when a kernel page couldn't be demand mapped,
+// and forces another try using the kernel page table.  basically a
+// hack so that vmalloc would work correctly.
+
+int mmu_emu_handle_fault (unsigned long vaddr, int read_flag, int kernel_fault)
+{
+	unsigned long segment, offset;
+	unsigned char context;
+	pte_t *pte;
+	pgd_t * crp;
+
+	if(current->mm == NULL) {
+		crp = swapper_pg_dir;
+		context = 0;
+	} else {
+		context = current->mm->context;
+		if(kernel_fault) 
+			crp = swapper_pg_dir;
+		else
+			crp = current->mm->pgd;
+	}
+
+#ifdef DEBUG_MMU_EMU
+	printk ("mmu_emu_handle_fault: vaddr=%lx type=%s crp=%p\n",
+		vaddr, read_flag ? "read" : "write", crp);
+#endif
+
+	segment = (vaddr >> SUN3_PMEG_SIZE_BITS) & 0x7FF;
+	offset  = (vaddr >> SUN3_PTE_SIZE_BITS) & 0xF;
+
+#ifdef DEBUG_MMU_EMU
+	printk ("mmu_emu_handle_fault: segment=%lx offset=%lx\n", segment, offset);
+#endif
+
+	pte = (pte_t *) pgd_val (*(crp + segment));
+
+//todo: next line should check for valid pmd properly.
+	if (!pte) {
+//                printk ("mmu_emu_handle_fault: invalid pmd\n");
+                return 0;
+        }
+
+	pte = (pte_t *) __va ((unsigned long)(pte + offset));
+
+	/* Make sure this is a valid page */
+	if (!(pte_val (*pte) & SUN3_PAGE_VALID)) 
+		return 0;
+
+	/* Make sure there's a pmeg allocated for the page */
+	if (sun3_get_segmap (vaddr&~SUN3_PMEG_MASK) == SUN3_INVALID_PMEG) 
+		mmu_emu_map_pmeg (context, vaddr);
+
+	/* Write the pte value to hardware MMU */
+	sun3_put_pte (vaddr&PAGE_MASK, pte_val (*pte));
+
+	/* Update software copy of the pte value */
+// I'm not sure this is necessary. If this is required, we ought to simply
+// copy this out when we reuse the PMEG or at some other convenient time.
+// Doing it here is fairly meaningless, anyway, as we only know about the
+// first access to a given page. --m
+	if (!read_flag) {
+		if (pte_val (*pte) & SUN3_PAGE_WRITEABLE)
+			pte_val (*pte) |= (SUN3_PAGE_ACCESSED
+					   | SUN3_PAGE_MODIFIED);
+		else 
+			return 0;	/* Write-protect error. */
+	} else
+		pte_val (*pte) |= SUN3_PAGE_ACCESSED;
+
+#ifdef DEBUG_MMU_EMU
+	printk ("seg:%d crp:%p ->", get_fs().seg, crp);
+	print_pte_vaddr (vaddr);
+	printk ("\n");
+#endif
+
+	return 1;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)