2005-04-17 02:20:36 +04:00
/*
* TLB support routines .
*
* Copyright ( C ) 1998 - 2001 , 2003 Hewlett - Packard Co
* David Mosberger - Tang < davidm @ hpl . hp . com >
*
* 08 / 02 / 00 A . Mallick < asit . k . mallick @ intel . com >
* Modified RID allocation for SMP
* Goutham Rao < goutham . rao @ intel . com >
* IPI based ptc implementation and A - step IPI implementation .
2005-11-01 00:44:47 +03:00
* Rohit Seth < rohit . seth @ intel . com >
* Ken Chen < kenneth . w . chen @ intel . com >
2007-12-13 18:03:07 +03:00
* Christophe de Dinechin < ddd @ hp . com > : Avoid ptc . e on memory allocation
2008-04-04 22:05:59 +04:00
* Copyright ( C ) 2007 Intel Corp
* Fenghua Yu < fenghua . yu @ intel . com >
* Add multiple ptc . g / ptc . ga instruction support in global tlb purge .
2005-04-17 02:20:36 +04:00
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/smp.h>
# include <linux/mm.h>
2005-11-01 00:44:47 +03:00
# include <linux/bootmem.h>
2005-04-17 02:20:36 +04:00
# include <asm/delay.h>
# include <asm/mmu_context.h>
# include <asm/pgalloc.h>
# include <asm/pal.h>
# include <asm/tlbflush.h>
2005-11-01 00:44:47 +03:00
# include <asm/dma.h>
2008-04-03 22:02:58 +04:00
# include <asm/processor.h>
2008-04-04 22:05:59 +04:00
# include <asm/sal.h>
2008-04-03 22:02:58 +04:00
# include <asm/tlb.h>
2005-04-17 02:20:36 +04:00
static struct {
unsigned long mask ; /* mask of supported purge page-sizes */
2005-10-30 04:47:04 +03:00
unsigned long max_bits ; /* log2 of largest supported purge page-size */
2005-04-17 02:20:36 +04:00
} purge ;
struct ia64_ctx ia64_ctx = {
2007-04-15 21:21:23 +04:00
. lock = __SPIN_LOCK_UNLOCKED ( ia64_ctx . lock ) ,
. next = 1 ,
. max_ctx = ~ 0U
2005-04-17 02:20:36 +04:00
} ;
DEFINE_PER_CPU ( u8 , ia64_need_tlb_flush ) ;
2008-04-03 22:02:58 +04:00
DEFINE_PER_CPU ( u8 , ia64_tr_num ) ; /*Number of TR slots in current processor*/
DEFINE_PER_CPU ( u8 , ia64_tr_used ) ; /*Max Slot number used by kernel*/
struct ia64_tr_entry __per_cpu_idtrs [ NR_CPUS ] [ 2 ] [ IA64_TR_ALLOC_MAX ] ;
2005-04-17 02:20:36 +04:00
2005-11-01 00:44:47 +03:00
/*
* Initializes the ia64_ctx . bitmap array based on max_ctx + 1.
* Called after cpu_init ( ) has setup ia64_ctx . max_ctx based on
* maximum RID that is supported by boot CPU .
*/
void __init
mmu_context_init ( void )
{
ia64_ctx . bitmap = alloc_bootmem ( ( ia64_ctx . max_ctx + 1 ) > > 3 ) ;
ia64_ctx . flushmap = alloc_bootmem ( ( ia64_ctx . max_ctx + 1 ) > > 3 ) ;
}
2005-04-17 02:20:36 +04:00
/*
* Acquire the ia64_ctx . lock before calling this function !
*/
void
wrap_mmu_context ( struct mm_struct * mm )
{
2005-10-30 04:47:04 +03:00
int i , cpu ;
2005-11-01 00:44:47 +03:00
unsigned long flush_bit ;
2005-04-17 02:20:36 +04:00
2005-11-01 00:44:47 +03:00
for ( i = 0 ; i < = ia64_ctx . max_ctx / BITS_PER_LONG ; i + + ) {
flush_bit = xchg ( & ia64_ctx . flushmap [ i ] , 0 ) ;
ia64_ctx . bitmap [ i ] ^ = flush_bit ;
2005-04-17 02:20:36 +04:00
}
2005-11-01 00:44:47 +03:00
/* use offset at 300 to skip daemons */
ia64_ctx . next = find_next_zero_bit ( ia64_ctx . bitmap ,
ia64_ctx . max_ctx , 300 ) ;
ia64_ctx . limit = find_next_bit ( ia64_ctx . bitmap ,
ia64_ctx . max_ctx , ia64_ctx . next ) ;
2005-10-30 04:47:04 +03:00
/*
* can ' t call flush_tlb_all ( ) here because of race condition
* with O ( 1 ) scheduler [ EF ]
*/
cpu = get_cpu ( ) ; /* prevent preemption/migration */
for_each_online_cpu ( i )
if ( i ! = cpu )
per_cpu ( ia64_need_tlb_flush , i ) = 1 ;
put_cpu ( ) ;
2005-04-17 02:20:36 +04:00
local_flush_tlb_all ( ) ;
}
2008-04-04 22:05:59 +04:00
/*
* Implement " spinaphores " . . . like counting semaphores , but they
* spin instead of sleeping . If there are ever any other users for
* this primitive it can be moved up to a spinaphore . h header .
*/
struct spinaphore {
atomic_t cur ;
} ;
static inline void spinaphore_init ( struct spinaphore * ss , int val )
{
atomic_set ( & ss - > cur , val ) ;
}
static inline void down_spin ( struct spinaphore * ss )
{
while ( unlikely ( ! atomic_add_unless ( & ss - > cur , - 1 , 0 ) ) )
while ( atomic_read ( & ss - > cur ) = = 0 )
cpu_relax ( ) ;
}
static inline void up_spin ( struct spinaphore * ss )
{
atomic_add ( 1 , & ss - > cur ) ;
}
static struct spinaphore ptcg_sem ;
static u16 nptcg = 1 ;
static int need_ptcg_sem = 1 ;
static int toolatetochangeptcgsem = 0 ;
2008-03-14 23:57:08 +03:00
/*
* Kernel parameter " nptcg= " overrides max number of concurrent global TLB
* purges which is reported from either PAL or SAL PALO .
*
* We don ' t have sanity checking for nptcg value . It ' s the user ' s responsibility
* for valid nptcg value on the platform . Otherwise , kernel may hang in some
* cases .
*/
static int __init
set_nptcg ( char * str )
{
int value = 0 ;
get_option ( & str , & value ) ;
setup_ptcg_sem ( value , NPTCG_FROM_KERNEL_PARAMETER ) ;
return 1 ;
}
__setup ( " nptcg= " , set_nptcg ) ;
2008-04-04 22:05:59 +04:00
/*
* Maximum number of simultaneous ptc . g purges in the system can
* be defined by PAL_VM_SUMMARY ( in which case we should take
* the smallest value for any cpu in the system ) or by the PAL
* override table ( in which case we should ignore the value from
* PAL_VM_SUMMARY ) .
*
2008-03-14 23:57:08 +03:00
* Kernel parameter " nptcg= " overrides maximum number of simultanesous ptc . g
* purges defined in either PAL_VM_SUMMARY or PAL override table . In this case ,
* we should ignore the value from either PAL_VM_SUMMARY or PAL override table .
*
2008-04-04 22:05:59 +04:00
* Complicating the logic here is the fact that num_possible_cpus ( )
* isn ' t fully setup until we start bringing cpus online .
*/
void
2008-03-14 23:57:08 +03:00
setup_ptcg_sem ( int max_purges , int nptcg_from )
2008-04-04 22:05:59 +04:00
{
2008-03-14 23:57:08 +03:00
static int kp_override ;
static int palo_override ;
2008-04-04 22:05:59 +04:00
static int firstcpu = 1 ;
if ( toolatetochangeptcgsem ) {
2008-04-25 18:13:09 +04:00
if ( nptcg_from = = NPTCG_FROM_PAL & & max_purges = = 0 )
BUG_ON ( 1 < nptcg ) ;
else
BUG_ON ( max_purges < nptcg ) ;
2008-04-04 22:05:59 +04:00
return ;
}
2008-03-14 23:57:08 +03:00
if ( nptcg_from = = NPTCG_FROM_KERNEL_PARAMETER ) {
kp_override = 1 ;
nptcg = max_purges ;
goto resetsema ;
}
if ( kp_override ) {
need_ptcg_sem = num_possible_cpus ( ) > nptcg ;
return ;
}
if ( nptcg_from = = NPTCG_FROM_PALO ) {
palo_override = 1 ;
2008-04-04 22:05:59 +04:00
/* In PALO max_purges == 0 really means it! */
if ( max_purges = = 0 )
panic ( " Whoa! Platform does not support global TLB purges. \n " ) ;
nptcg = max_purges ;
if ( nptcg = = PALO_MAX_TLB_PURGES ) {
need_ptcg_sem = 0 ;
return ;
}
goto resetsema ;
}
2008-03-14 23:57:08 +03:00
if ( palo_override ) {
2008-04-04 22:05:59 +04:00
if ( nptcg ! = PALO_MAX_TLB_PURGES )
need_ptcg_sem = ( num_possible_cpus ( ) > nptcg ) ;
return ;
}
/* In PAL_VM_SUMMARY max_purges == 0 actually means 1 */
if ( max_purges = = 0 ) max_purges = 1 ;
if ( firstcpu ) {
nptcg = max_purges ;
firstcpu = 0 ;
}
if ( max_purges < nptcg )
nptcg = max_purges ;
if ( nptcg = = PAL_MAX_PURGES ) {
need_ptcg_sem = 0 ;
return ;
} else
need_ptcg_sem = ( num_possible_cpus ( ) > nptcg ) ;
resetsema :
spinaphore_init ( & ptcg_sem , max_purges ) ;
}
2005-04-17 02:20:36 +04:00
void
2005-10-30 04:47:04 +03:00
ia64_global_tlb_purge ( struct mm_struct * mm , unsigned long start ,
unsigned long end , unsigned long nbits )
2005-04-17 02:20:36 +04:00
{
2007-12-13 18:03:07 +03:00
struct mm_struct * active_mm = current - > active_mm ;
2008-04-04 22:05:59 +04:00
toolatetochangeptcgsem = 1 ;
2007-12-13 18:03:07 +03:00
if ( mm ! = active_mm ) {
/* Restore region IDs for mm */
if ( mm & & active_mm ) {
activate_context ( mm ) ;
} else {
flush_tlb_all ( ) ;
return ;
}
2005-10-28 00:41:04 +04:00
}
2008-04-04 22:05:59 +04:00
if ( need_ptcg_sem )
down_spin ( & ptcg_sem ) ;
do {
/*
* Flush ALAT entries also .
*/
ia64_ptcga ( start , ( nbits < < 2 ) ) ;
ia64_srlz_i ( ) ;
start + = ( 1UL < < nbits ) ;
} while ( start < end ) ;
if ( need_ptcg_sem )
up_spin ( & ptcg_sem ) ;
2007-12-13 18:03:07 +03:00
if ( mm ! = active_mm ) {
activate_context ( active_mm ) ;
}
2005-04-17 02:20:36 +04:00
}
void
local_flush_tlb_all ( void )
{
unsigned long i , j , flags , count0 , count1 , stride0 , stride1 , addr ;
addr = local_cpu_data - > ptce_base ;
count0 = local_cpu_data - > ptce_count [ 0 ] ;
count1 = local_cpu_data - > ptce_count [ 1 ] ;
stride0 = local_cpu_data - > ptce_stride [ 0 ] ;
stride1 = local_cpu_data - > ptce_stride [ 1 ] ;
local_irq_save ( flags ) ;
for ( i = 0 ; i < count0 ; + + i ) {
for ( j = 0 ; j < count1 ; + + j ) {
ia64_ptce ( addr ) ;
addr + = stride1 ;
}
addr + = stride0 ;
}
local_irq_restore ( flags ) ;
ia64_srlz_i ( ) ; /* srlz.i implies srlz.d */
}
void
2005-10-30 04:47:04 +03:00
flush_tlb_range ( struct vm_area_struct * vma , unsigned long start ,
unsigned long end )
2005-04-17 02:20:36 +04:00
{
struct mm_struct * mm = vma - > vm_mm ;
unsigned long size = end - start ;
unsigned long nbits ;
2005-10-28 00:41:04 +04:00
# ifndef CONFIG_SMP
2005-04-17 02:20:36 +04:00
if ( mm ! = current - > active_mm ) {
mm - > context = 0 ;
return ;
}
2005-10-28 00:41:04 +04:00
# endif
2005-04-17 02:20:36 +04:00
nbits = ia64_fls ( size + 0xfff ) ;
2005-10-30 04:47:04 +03:00
while ( unlikely ( ( ( 1UL < < nbits ) & purge . mask ) = = 0 ) & &
( nbits < purge . max_bits ) )
2005-04-17 02:20:36 +04:00
+ + nbits ;
if ( nbits > purge . max_bits )
nbits = purge . max_bits ;
start & = ~ ( ( 1UL < < nbits ) - 1 ) ;
2005-10-30 04:16:28 +03:00
preempt_disable ( ) ;
2006-03-07 01:12:54 +03:00
# ifdef CONFIG_SMP
2009-03-16 06:42:48 +03:00
if ( mm ! = current - > active_mm | | cpumask_weight ( mm_cpumask ( mm ) ) ! = 1 ) {
2006-03-07 01:12:54 +03:00
platform_global_tlb_purge ( mm , start , end , nbits ) ;
preempt_enable ( ) ;
return ;
}
# endif
2005-04-17 02:20:36 +04:00
do {
ia64_ptcl ( start , ( nbits < < 2 ) ) ;
start + = ( 1UL < < nbits ) ;
} while ( start < end ) ;
2005-10-30 04:16:28 +03:00
preempt_enable ( ) ;
2005-04-17 02:20:36 +04:00
ia64_srlz_i ( ) ; /* srlz.i implies srlz.d */
}
EXPORT_SYMBOL ( flush_tlb_range ) ;
void __devinit
ia64_tlb_init ( void )
{
2007-07-11 19:26:30 +04:00
ia64_ptce_info_t uninitialized_var ( ptce_info ) ; /* GCC be quiet */
2005-04-17 02:20:36 +04:00
unsigned long tr_pgbits ;
long status ;
2008-04-03 22:02:58 +04:00
pal_vm_info_1_u_t vm_info_1 ;
pal_vm_info_2_u_t vm_info_2 ;
int cpu = smp_processor_id ( ) ;
2005-04-17 02:20:36 +04:00
if ( ( status = ia64_pal_vm_page_size ( & tr_pgbits , & purge . mask ) ) ! = 0 ) {
2007-11-20 04:47:53 +03:00
printk ( KERN_ERR " PAL_VM_PAGE_SIZE failed with status=%ld; "
2005-04-17 02:20:36 +04:00
" defaulting to architected purge page-sizes. \n " , status ) ;
purge . mask = 0x115557000UL ;
}
purge . max_bits = ia64_fls ( purge . mask ) ;
ia64_get_ptce ( & ptce_info ) ;
local_cpu_data - > ptce_base = ptce_info . base ;
local_cpu_data - > ptce_count [ 0 ] = ptce_info . count [ 0 ] ;
local_cpu_data - > ptce_count [ 1 ] = ptce_info . count [ 1 ] ;
local_cpu_data - > ptce_stride [ 0 ] = ptce_info . stride [ 0 ] ;
local_cpu_data - > ptce_stride [ 1 ] = ptce_info . stride [ 1 ] ;
2005-10-30 04:47:04 +03:00
local_flush_tlb_all ( ) ; /* nuke left overs from bootstrapping... */
2008-04-03 22:02:58 +04:00
status = ia64_pal_vm_summary ( & vm_info_1 , & vm_info_2 ) ;
if ( status ) {
printk ( KERN_ERR " ia64_pal_vm_summary=%ld \n " , status ) ;
per_cpu ( ia64_tr_num , cpu ) = 8 ;
return ;
}
per_cpu ( ia64_tr_num , cpu ) = vm_info_1 . pal_vm_info_1_s . max_itr_entry + 1 ;
if ( per_cpu ( ia64_tr_num , cpu ) >
( vm_info_1 . pal_vm_info_1_s . max_dtr_entry + 1 ) )
per_cpu ( ia64_tr_num , cpu ) =
vm_info_1 . pal_vm_info_1_s . max_dtr_entry + 1 ;
if ( per_cpu ( ia64_tr_num , cpu ) > IA64_TR_ALLOC_MAX ) {
2008-10-18 00:47:53 +04:00
static int justonce = 1 ;
2008-04-03 22:02:58 +04:00
per_cpu ( ia64_tr_num , cpu ) = IA64_TR_ALLOC_MAX ;
2008-10-18 00:47:53 +04:00
if ( justonce ) {
justonce = 0 ;
printk ( KERN_DEBUG " TR register number exceeds "
" IA64_TR_ALLOC_MAX! \n " ) ;
}
2008-04-03 22:02:58 +04:00
}
}
/*
* is_tr_overlap
*
* Check overlap with inserted TRs .
*/
static int is_tr_overlap ( struct ia64_tr_entry * p , u64 va , u64 log_size )
{
u64 tr_log_size ;
u64 tr_end ;
u64 va_rr = ia64_get_rr ( va ) ;
u64 va_rid = RR_TO_RID ( va_rr ) ;
u64 va_end = va + ( 1 < < log_size ) - 1 ;
if ( va_rid ! = RR_TO_RID ( p - > rr ) )
return 0 ;
tr_log_size = ( p - > itir & 0xff ) > > 2 ;
tr_end = p - > ifa + ( 1 < < tr_log_size ) - 1 ;
if ( va > tr_end | | p - > ifa > va_end )
return 0 ;
return 1 ;
}
/*
* ia64_insert_tr in virtual mode . Allocate a TR slot
*
* target_mask : 0x1 : itr , 0x2 : dtr , 0x3 : idtr
*
* va : virtual address .
* pte : pte entries inserted .
* log_size : range to be covered .
*
* Return value : < 0 : error No .
*
* > = 0 : slot number allocated for TR .
* Must be called with preemption disabled .
*/
int ia64_itr_entry ( u64 target_mask , u64 va , u64 pte , u64 log_size )
{
int i , r ;
unsigned long psr ;
struct ia64_tr_entry * p ;
int cpu = smp_processor_id ( ) ;
r = - EINVAL ;
/*Check overlap with existing TR entries*/
if ( target_mask & 0x1 ) {
p = & __per_cpu_idtrs [ cpu ] [ 0 ] [ 0 ] ;
for ( i = IA64_TR_ALLOC_BASE ; i < = per_cpu ( ia64_tr_used , cpu ) ;
i + + , p + + ) {
if ( p - > pte & 0x1 )
if ( is_tr_overlap ( p , va , log_size ) ) {
printk ( KERN_DEBUG " Overlapped Entry "
" Inserted for TR Reigster!! \n " ) ;
goto out ;
}
}
}
if ( target_mask & 0x2 ) {
p = & __per_cpu_idtrs [ cpu ] [ 1 ] [ 0 ] ;
for ( i = IA64_TR_ALLOC_BASE ; i < = per_cpu ( ia64_tr_used , cpu ) ;
i + + , p + + ) {
if ( p - > pte & 0x1 )
if ( is_tr_overlap ( p , va , log_size ) ) {
printk ( KERN_DEBUG " Overlapped Entry "
" Inserted for TR Reigster!! \n " ) ;
goto out ;
}
}
}
for ( i = IA64_TR_ALLOC_BASE ; i < per_cpu ( ia64_tr_num , cpu ) ; i + + ) {
switch ( target_mask & 0x3 ) {
case 1 :
if ( ! ( __per_cpu_idtrs [ cpu ] [ 0 ] [ i ] . pte & 0x1 ) )
goto found ;
continue ;
case 2 :
if ( ! ( __per_cpu_idtrs [ cpu ] [ 1 ] [ i ] . pte & 0x1 ) )
goto found ;
continue ;
case 3 :
if ( ! ( __per_cpu_idtrs [ cpu ] [ 0 ] [ i ] . pte & 0x1 ) & &
! ( __per_cpu_idtrs [ cpu ] [ 1 ] [ i ] . pte & 0x1 ) )
goto found ;
continue ;
default :
r = - EINVAL ;
goto out ;
}
}
found :
if ( i > = per_cpu ( ia64_tr_num , cpu ) )
return - EBUSY ;
/*Record tr info for mca hander use!*/
if ( i > per_cpu ( ia64_tr_used , cpu ) )
per_cpu ( ia64_tr_used , cpu ) = i ;
psr = ia64_clear_ic ( ) ;
if ( target_mask & 0x1 ) {
ia64_itr ( 0x1 , i , va , pte , log_size ) ;
ia64_srlz_i ( ) ;
p = & __per_cpu_idtrs [ cpu ] [ 0 ] [ i ] ;
p - > ifa = va ;
p - > pte = pte ;
p - > itir = log_size < < 2 ;
p - > rr = ia64_get_rr ( va ) ;
}
if ( target_mask & 0x2 ) {
ia64_itr ( 0x2 , i , va , pte , log_size ) ;
ia64_srlz_i ( ) ;
p = & __per_cpu_idtrs [ cpu ] [ 1 ] [ i ] ;
p - > ifa = va ;
p - > pte = pte ;
p - > itir = log_size < < 2 ;
p - > rr = ia64_get_rr ( va ) ;
}
ia64_set_psr ( psr ) ;
r = i ;
out :
return r ;
}
EXPORT_SYMBOL_GPL ( ia64_itr_entry ) ;
/*
* ia64_purge_tr
*
* target_mask : 0x1 : purge itr , 0x2 : purge dtr , 0x3 purge idtr .
* slot : slot number to be freed .
*
* Must be called with preemption disabled .
*/
void ia64_ptr_entry ( u64 target_mask , int slot )
{
int cpu = smp_processor_id ( ) ;
int i ;
struct ia64_tr_entry * p ;
if ( slot < IA64_TR_ALLOC_BASE | | slot > = per_cpu ( ia64_tr_num , cpu ) )
return ;
if ( target_mask & 0x1 ) {
p = & __per_cpu_idtrs [ cpu ] [ 0 ] [ slot ] ;
if ( ( p - > pte & 0x1 ) & & is_tr_overlap ( p , p - > ifa , p - > itir > > 2 ) ) {
p - > pte = 0 ;
ia64_ptr ( 0x1 , p - > ifa , p - > itir > > 2 ) ;
ia64_srlz_i ( ) ;
}
}
if ( target_mask & 0x2 ) {
p = & __per_cpu_idtrs [ cpu ] [ 1 ] [ slot ] ;
if ( ( p - > pte & 0x1 ) & & is_tr_overlap ( p , p - > ifa , p - > itir > > 2 ) ) {
p - > pte = 0 ;
ia64_ptr ( 0x2 , p - > ifa , p - > itir > > 2 ) ;
ia64_srlz_i ( ) ;
}
}
for ( i = per_cpu ( ia64_tr_used , cpu ) ; i > = IA64_TR_ALLOC_BASE ; i - - ) {
if ( ( __per_cpu_idtrs [ cpu ] [ 0 ] [ i ] . pte & 0x1 ) | |
( __per_cpu_idtrs [ cpu ] [ 1 ] [ i ] . pte & 0x1 ) )
break ;
}
per_cpu ( ia64_tr_used , cpu ) = i ;
2005-04-17 02:20:36 +04:00
}
2008-04-03 22:02:58 +04:00
EXPORT_SYMBOL_GPL ( ia64_ptr_entry ) ;