2005-04-17 02:20:36 +04: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 11:43:19 +03:00
unsigned long m68k_vmalloc_end ;
EXPORT_SYMBOL ( m68k_vmalloc_end ) ;
2005-04-17 02:20:36 +04: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 23:16:27 +04:00
static struct mm_struct * ctx_alloc [ CONTEXTS_NUM ] = {
2005-04-17 02:20:36 +04: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 11:43:19 +03:00
if ( ! m68k_vmalloc_end )
m68k_vmalloc_end = seg ;
2005-04-17 02:20:36 +04: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 03:20:32 +04:00
sure it could be much more intelligent . . . but it gets the job done
2005-04-17 02:20:36 +04: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 ;
}