/*
* Based on: arch/blackfin/kernel/cplb-mpu/cplbmgr.c
* Author: Michael McTernan <mmcternan@airvana.com>
*
* Description: CPLB miss handler.
*
* Modified:
* Copyright 2008 Airvana Inc.
* Copyright 2008-2009 Analog Devices Inc.
*
* Licensed under the GPL-2 or later
*/
#include <linux/kernel.h>
#include <asm/blackfin.h>
#include <asm/cplbinit.h>
#include <asm/cplb.h>
#include <asm/mmu_context.h>
#include <asm/traps.h>
/*
* 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)
{
_disable_dcplb();
bfin_write32(DCPLB_DATA0 + idx * 4, data);
bfin_write32(DCPLB_ADDR0 + idx * 4, addr);
_enable_dcplb();
#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)
{
_disable_icplb();
bfin_write32(ICPLB_DATA0 + idx * 4, data);
bfin_write32(ICPLB_ADDR0 + idx * 4, addr);
_enable_icplb();
#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;
idx = 0;
do {
eaddr = icplb_bounds[idx].eaddr;
if (addr < eaddr)
break;
base = eaddr;
} while (++idx < icplb_nr_bounds);
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, cplb_pagesize, cplb_pageflags;
nr_dcplb_miss[cpu]++;
if (unlikely(status & FAULT_USERSUPV))
nr_dcplb_supv_miss[cpu]++;
base = 0;
idx = 0;
do {
eaddr = dcplb_bounds[idx].eaddr;
if (addr < eaddr)
break;
base = eaddr;
} while (++idx < dcplb_nr_bounds);
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;
addr &= ~(SIZE_1M - 1);
d_data |= PAGE_SIZE_1MB;
/* BF60x support large than 4M CPLB page size */
#ifdef PAGE_SIZE_16MB
cplb_pageflags = PAGE_SIZE_16MB;
cplb_pagesize = SIZE_16M;
#else
cplb_pageflags = PAGE_SIZE_4MB;
cplb_pagesize = SIZE_4M;
#endif
find_pagesize:
addr1 = addr & ~(cplb_pagesize - 1);
if (addr1 >= base && (addr1 + cplb_pagesize) <= eaddr) {
/*
* This works because
* (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB.
*/
d_data |= cplb_pageflags;
addr = addr1;
goto found_pagesize;
} else {
if (cplb_pagesize > SIZE_4M) {
cplb_pageflags = PAGE_SIZE_4MB;
cplb_pagesize = SIZE_4M;
goto find_pagesize;
}
}
found_pagesize:
#ifdef CONFIG_BF60x
if ((addr >= ASYNC_BANK0_BASE)
&& (addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE))
d_data |= PAGE_SIZE_64MB;
#endif
/* 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;
unsigned int cpu = raw_smp_processor_id();
switch (cause) {
case VEC_CPLB_I_M:
return icplb_miss(cpu);
case VEC_CPLB_M:
return dcplb_miss(cpu);
default:
return CPLB_UNKNOWN_ERR;
}
}