2009-01-07 23:14:38 +08:00
/*
* File : arch / blackfin / kernel / cplb - nompu - c / cplbmgr . c
* Based on : arch / blackfin / kernel / cplb - mpu / cplbmgr . c
* Author : Michael McTernan < mmcternan @ airvana . com >
*
* Created : 01 Nov2008
* Description : CPLB miss handler .
*
* Modified :
* Copyright 2008 Airvana Inc .
* Copyright 2004 - 2007 Analog Devices Inc .
*
* Bugs : Enter bugs at http : //blackfin.uclinux.org/
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/kernel.h>
# include <asm/blackfin.h>
# include <asm/cplbinit.h>
# include <asm/cplb.h>
# include <asm/mmu_context.h>
2009-06-09 01:18:41 +00:00
# include <asm/traps.h>
2009-01-07 23:14:38 +08:00
/*
* WARNING
*
* This file is compiled with certain - ffixed - reg options . We have to
* make sure not to call any functions here that could clobber these
* registers .
*/
int nr_dcplb_miss [ NR_CPUS ] , nr_icplb_miss [ NR_CPUS ] ;
int nr_dcplb_supv_miss [ NR_CPUS ] , nr_icplb_supv_miss [ NR_CPUS ] ;
int nr_cplb_flush [ NR_CPUS ] , nr_dcplb_prot [ NR_CPUS ] ;
# ifdef CONFIG_EXCPT_IRQ_SYSC_L1
# define MGR_ATTR __attribute__((l1_text))
# else
# define MGR_ATTR
# endif
static inline void write_dcplb_data ( int cpu , int idx , unsigned long data ,
unsigned long addr )
{
2009-08-07 01:20:58 +00:00
_disable_dcplb ( ) ;
2009-01-07 23:14:38 +08:00
bfin_write32 ( DCPLB_DATA0 + idx * 4 , data ) ;
bfin_write32 ( DCPLB_ADDR0 + idx * 4 , addr ) ;
2009-08-07 01:20:58 +00:00
_enable_dcplb ( ) ;
2009-01-07 23:14:38 +08:00
# ifdef CONFIG_CPLB_INFO
dcplb_tbl [ cpu ] [ idx ] . addr = addr ;
dcplb_tbl [ cpu ] [ idx ] . data = data ;
# endif
}
static inline void write_icplb_data ( int cpu , int idx , unsigned long data ,
unsigned long addr )
{
2009-08-07 01:20:58 +00:00
_disable_icplb ( ) ;
2009-01-07 23:14:38 +08:00
bfin_write32 ( ICPLB_DATA0 + idx * 4 , data ) ;
bfin_write32 ( ICPLB_ADDR0 + idx * 4 , addr ) ;
2009-08-07 01:20:58 +00:00
_enable_icplb ( ) ;
2009-01-07 23:14:38 +08:00
# ifdef CONFIG_CPLB_INFO
icplb_tbl [ cpu ] [ idx ] . addr = addr ;
icplb_tbl [ cpu ] [ idx ] . data = data ;
# endif
}
/* Counters to implement round-robin replacement. */
static int icplb_rr_index [ NR_CPUS ] PDT_ATTR ;
static int dcplb_rr_index [ NR_CPUS ] PDT_ATTR ;
/*
* Find an ICPLB entry to be evicted and return its index .
*/
static int evict_one_icplb ( int cpu )
{
int i = first_switched_icplb + icplb_rr_index [ cpu ] ;
if ( i > = MAX_CPLBS ) {
i - = MAX_CPLBS - first_switched_icplb ;
icplb_rr_index [ cpu ] - = MAX_CPLBS - first_switched_icplb ;
}
icplb_rr_index [ cpu ] + + ;
return i ;
}
static int evict_one_dcplb ( int cpu )
{
int i = first_switched_dcplb + dcplb_rr_index [ cpu ] ;
if ( i > = MAX_CPLBS ) {
i - = MAX_CPLBS - first_switched_dcplb ;
dcplb_rr_index [ cpu ] - = MAX_CPLBS - first_switched_dcplb ;
}
dcplb_rr_index [ cpu ] + + ;
return i ;
}
MGR_ATTR static int icplb_miss ( int cpu )
{
unsigned long addr = bfin_read_ICPLB_FAULT_ADDR ( ) ;
int status = bfin_read_ICPLB_STATUS ( ) ;
int idx ;
unsigned long i_data , base , addr1 , eaddr ;
nr_icplb_miss [ cpu ] + + ;
if ( unlikely ( status & FAULT_USERSUPV ) )
nr_icplb_supv_miss [ cpu ] + + ;
base = 0 ;
2009-02-04 16:49:45 +08:00
idx = 0 ;
do {
2009-01-07 23:14:38 +08:00
eaddr = icplb_bounds [ idx ] . eaddr ;
if ( addr < eaddr )
break ;
base = eaddr ;
2009-02-04 16:49:45 +08:00
} while ( + + idx < icplb_nr_bounds ) ;
2009-01-07 23:14:38 +08:00
if ( unlikely ( idx = = icplb_nr_bounds ) )
return CPLB_NO_ADDR_MATCH ;
i_data = icplb_bounds [ idx ] . data ;
if ( unlikely ( i_data = = 0 ) )
return CPLB_NO_ADDR_MATCH ;
addr1 = addr & ~ ( SIZE_4M - 1 ) ;
addr & = ~ ( SIZE_1M - 1 ) ;
i_data | = PAGE_SIZE_1MB ;
if ( addr1 > = base & & ( addr1 + SIZE_4M ) < = eaddr ) {
/*
* This works because
* ( PAGE_SIZE_4MB & PAGE_SIZE_1MB ) = = PAGE_SIZE_1MB .
*/
i_data | = PAGE_SIZE_4MB ;
addr = addr1 ;
}
/* Pick entry to evict */
idx = evict_one_icplb ( cpu ) ;
write_icplb_data ( cpu , idx , i_data , addr ) ;
return CPLB_RELOADED ;
}
MGR_ATTR static int dcplb_miss ( int cpu )
{
unsigned long addr = bfin_read_DCPLB_FAULT_ADDR ( ) ;
int status = bfin_read_DCPLB_STATUS ( ) ;
int idx ;
unsigned long d_data , base , addr1 , eaddr ;
nr_dcplb_miss [ cpu ] + + ;
if ( unlikely ( status & FAULT_USERSUPV ) )
nr_dcplb_supv_miss [ cpu ] + + ;
base = 0 ;
2009-02-04 16:49:45 +08:00
idx = 0 ;
do {
2009-01-07 23:14:38 +08:00
eaddr = dcplb_bounds [ idx ] . eaddr ;
if ( addr < eaddr )
break ;
base = eaddr ;
2009-02-04 16:49:45 +08:00
} while ( + + idx < dcplb_nr_bounds ) ;
2009-01-07 23:14:38 +08:00
if ( unlikely ( idx = = dcplb_nr_bounds ) )
return CPLB_NO_ADDR_MATCH ;
d_data = dcplb_bounds [ idx ] . data ;
if ( unlikely ( d_data = = 0 ) )
return CPLB_NO_ADDR_MATCH ;
addr1 = addr & ~ ( SIZE_4M - 1 ) ;
addr & = ~ ( SIZE_1M - 1 ) ;
d_data | = PAGE_SIZE_1MB ;
if ( addr1 > = base & & ( addr1 + SIZE_4M ) < = eaddr ) {
/*
* This works because
* ( PAGE_SIZE_4MB & PAGE_SIZE_1MB ) = = PAGE_SIZE_1MB .
*/
d_data | = PAGE_SIZE_4MB ;
addr = addr1 ;
}
/* Pick entry to evict */
idx = evict_one_dcplb ( cpu ) ;
write_dcplb_data ( cpu , idx , d_data , addr ) ;
return CPLB_RELOADED ;
}
MGR_ATTR int cplb_hdr ( int seqstat , struct pt_regs * regs )
{
int cause = seqstat & 0x3f ;
2009-08-20 04:17:47 +00:00
unsigned int cpu = raw_smp_processor_id ( ) ;
2009-01-07 23:14:38 +08:00
switch ( cause ) {
2009-06-09 01:18:41 +00:00
case VEC_CPLB_I_M :
2009-01-07 23:14:38 +08:00
return icplb_miss ( cpu ) ;
2009-06-09 01:18:41 +00:00
case VEC_CPLB_M :
2009-01-07 23:14:38 +08:00
return dcplb_miss ( cpu ) ;
default :
return CPLB_UNKNOWN_ERR ;
}
}