| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  | ** 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 <linux/bootmem.h>
 | 
					
						
							|  |  |  | #include <linux/bitops.h>
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <asm/setup.h>
 | 
					
						
							|  |  |  | #include <asm/traps.h>
 | 
					
						
							|  |  |  | #include <asm/uaccess.h>
 | 
					
						
							|  |  |  | #include <asm/page.h>
 | 
					
						
							|  |  |  | #include <asm/pgtable.h>
 | 
					
						
							|  |  |  | #include <asm/sun3mmu.h>
 | 
					
						
							|  |  |  | #include <asm/segment.h>
 | 
					
						
							|  |  |  | #include <asm/oplib.h>
 | 
					
						
							|  |  |  | #include <asm/mmu_context.h>
 | 
					
						
							|  |  |  | #include <asm/dvma.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #undef DEBUG_MMU_EMU
 | 
					
						
							|  |  |  | #define DEBUG_PROM_MAPS
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  | ** 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 | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-09 17:43:19 +09:00
										 |  |  | unsigned long m68k_vmalloc_end; | 
					
						
							|  |  |  | EXPORT_SYMBOL(m68k_vmalloc_end); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 */ | 
					
						
							| 
									
										
										
										
											2008-07-17 21:16:27 +02:00
										 |  |  | static struct mm_struct *ctx_alloc[CONTEXTS_NUM] = { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |     [0] = (struct mm_struct *)0xffffffff | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* has this context been mmdrop'd? */ | 
					
						
							|  |  |  | static unsigned char ctx_avail = CONTEXTS_NUM-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(unsigned long bootmem_end) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long seg, num; | 
					
						
							|  |  |  | 	int i,j; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* pmeg align the end of bootmem, adding another pmeg,
 | 
					
						
							|  |  |  | 	 * later bootmem allocations will likely need it */ | 
					
						
							|  |  |  | 	bootmem_end = (bootmem_end + (2 * SUN3_PMEG_SIZE)) & ~SUN3_PMEG_MASK; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* mark all of the pmegs used thus far as reserved */ | 
					
						
							|  |  |  | 	for (i=0; i < __pa(bootmem_end) / SUN3_PMEG_SIZE ; ++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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* liberate all existing mappings in the rest of kernel space */ | 
					
						
							|  |  |  | 	for(seg = bootmem_end; seg < 0x0f800000; seg += SUN3_PMEG_SIZE) { | 
					
						
							|  |  |  | 		i = sun3_get_segmap(seg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if(!pmeg_alloc[i]) { | 
					
						
							|  |  |  | #ifdef DEBUG_MMU_EMU
 | 
					
						
							|  |  |  | 			printk("freed: "); | 
					
						
							|  |  |  | 			print_pte_vaddr (seg); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 			sun3_put_segmap(seg, SUN3_INVALID_PMEG); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	j = 0; | 
					
						
							|  |  |  | 	for (num=0, seg=0x0F800000; seg<0x10000000; seg+=16*PAGE_SIZE) { | 
					
						
							|  |  |  | 		if (sun3_get_segmap (seg) != SUN3_INVALID_PMEG) { | 
					
						
							|  |  |  | #ifdef DEBUG_PROM_MAPS
 | 
					
						
							|  |  |  | 			for(i = 0; i < 16; i++) { | 
					
						
							|  |  |  | 				printk ("mapped:"); | 
					
						
							|  |  |  | 				print_pte_vaddr (seg + (i*PAGE_SIZE)); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 			// the lowest mapping here is the end of our
 | 
					
						
							|  |  |  | 			// vmalloc region
 | 
					
						
							| 
									
										
										
										
											2009-12-09 17:43:19 +09:00
										 |  |  | 			if (!m68k_vmalloc_end) | 
					
						
							|  |  |  | 				m68k_vmalloc_end = seg; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// 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; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dvma_init(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* 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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* 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(context) { | 
					
						
							|  |  |  | 	     if(!ctx_alloc[context]) | 
					
						
							|  |  |  | 		     panic("clear_context: context not allocated\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	     ctx_alloc[context]->context = SUN3_INVALID_CONTEXT; | 
					
						
							|  |  |  | 	     ctx_alloc[context] = (struct mm_struct *)0; | 
					
						
							|  |  |  | 	     ctx_avail++; | 
					
						
							|  |  |  |      } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      oldctx = sun3_get_context(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      sun3_put_context(context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      for(i = 0; i < SUN3_INVALID_PMEG; i++) { | 
					
						
							|  |  |  | 	     if((pmeg_ctx[i] == context) && (pmeg_alloc[i] == 1)) { | 
					
						
							|  |  |  | 		     sun3_put_segmap(pmeg_vaddr[i], SUN3_INVALID_PMEG); | 
					
						
							|  |  |  | 		     pmeg_ctx[i] = 0; | 
					
						
							|  |  |  | 		     pmeg_alloc[i] = 0; | 
					
						
							|  |  |  | 		     pmeg_vaddr[i] = 0; | 
					
						
							|  |  |  | 	     } | 
					
						
							|  |  |  |      } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      sun3_put_context(oldctx); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* gets an empty context.  if full, kills the next context listed to
 | 
					
						
							|  |  |  |    die first */ | 
					
						
							|  |  |  | /* This context invalidation scheme is, well, totally arbitrary, I'm
 | 
					
						
							| 
									
										
										
										
											2007-10-20 01:20:32 +02:00
										 |  |  |    sure it could be much more intelligent...  but it gets the job done | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |    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; | 
					
						
							|  |  |  | 	static unsigned char next_to_die = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if(!ctx_avail) { | 
					
						
							|  |  |  | 		/* kill someone to get our context */ | 
					
						
							|  |  |  | 		new = next_to_die; | 
					
						
							|  |  |  | 		clear_context(new); | 
					
						
							|  |  |  | 		next_to_die = (next_to_die + 1) & 0x7; | 
					
						
							|  |  |  | 		if(!next_to_die) | 
					
						
							|  |  |  | 			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_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?
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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; | 
					
						
							|  |  |  | } |