Path: blob/master/arch/blackfin/kernel/cplb-mpu/cplbmgr.c
15126 views
/*1* Blackfin CPLB exception handling for when MPU in on2*3* Copyright 2008-2009 Analog Devices Inc.4*5* Licensed under the GPL-2 or later.6*/78#include <linux/module.h>9#include <linux/mm.h>1011#include <asm/blackfin.h>12#include <asm/cacheflush.h>13#include <asm/cplb.h>14#include <asm/cplbinit.h>15#include <asm/mmu_context.h>1617/*18* WARNING19*20* This file is compiled with certain -ffixed-reg options. We have to21* make sure not to call any functions here that could clobber these22* registers.23*/2425int page_mask_nelts;26int page_mask_order;27unsigned long *current_rwx_mask[NR_CPUS];2829int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS];30int nr_icplb_supv_miss[NR_CPUS], nr_dcplb_prot[NR_CPUS];31int nr_cplb_flush[NR_CPUS];3233#ifdef CONFIG_EXCPT_IRQ_SYSC_L134#define MGR_ATTR __attribute__((l1_text))35#else36#define MGR_ATTR37#endif3839/*40* Given the contents of the status register, return the index of the41* CPLB that caused the fault.42*/43static inline int faulting_cplb_index(int status)44{45int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF);46return 30 - signbits;47}4849/*50* Given the contents of the status register and the DCPLB_DATA contents,51* return true if a write access should be permitted.52*/53static inline int write_permitted(int status, unsigned long data)54{55if (status & FAULT_USERSUPV)56return !!(data & CPLB_SUPV_WR);57else58return !!(data & CPLB_USER_WR);59}6061/* Counters to implement round-robin replacement. */62static int icplb_rr_index[NR_CPUS], dcplb_rr_index[NR_CPUS];6364/*65* Find an ICPLB entry to be evicted and return its index.66*/67MGR_ATTR static int evict_one_icplb(unsigned int cpu)68{69int i;70for (i = first_switched_icplb; i < MAX_CPLBS; i++)71if ((icplb_tbl[cpu][i].data & CPLB_VALID) == 0)72return i;73i = first_switched_icplb + icplb_rr_index[cpu];74if (i >= MAX_CPLBS) {75i -= MAX_CPLBS - first_switched_icplb;76icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb;77}78icplb_rr_index[cpu]++;79return i;80}8182MGR_ATTR static int evict_one_dcplb(unsigned int cpu)83{84int i;85for (i = first_switched_dcplb; i < MAX_CPLBS; i++)86if ((dcplb_tbl[cpu][i].data & CPLB_VALID) == 0)87return i;88i = first_switched_dcplb + dcplb_rr_index[cpu];89if (i >= MAX_CPLBS) {90i -= MAX_CPLBS - first_switched_dcplb;91dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb;92}93dcplb_rr_index[cpu]++;94return i;95}9697MGR_ATTR static noinline int dcplb_miss(unsigned int cpu)98{99unsigned long addr = bfin_read_DCPLB_FAULT_ADDR();100int status = bfin_read_DCPLB_STATUS();101unsigned long *mask;102int idx;103unsigned long d_data;104105nr_dcplb_miss[cpu]++;106107d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;108#ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE109if (bfin_addr_dcacheable(addr)) {110d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;111# ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH112d_data |= CPLB_L1_AOW | CPLB_WT;113# endif114}115#endif116117if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {118addr = L2_START;119d_data = L2_DMEMORY;120} else if (addr >= physical_mem_end) {121if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {122mask = current_rwx_mask[cpu];123if (mask) {124int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT;125int idx = page >> 5;126int bit = 1 << (page & 31);127128if (mask[idx] & bit)129d_data |= CPLB_USER_RD;130}131} else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH132&& (status & (FAULT_RW | FAULT_USERSUPV)) == FAULT_USERSUPV) {133addr &= ~(1 * 1024 * 1024 - 1);134d_data &= ~PAGE_SIZE_4KB;135d_data |= PAGE_SIZE_1MB;136} else137return CPLB_PROT_VIOL;138} else if (addr >= _ramend) {139d_data |= CPLB_USER_RD | CPLB_USER_WR;140if (reserved_mem_dcache_on)141d_data |= CPLB_L1_CHBL;142} else {143mask = current_rwx_mask[cpu];144if (mask) {145int page = addr >> PAGE_SHIFT;146int idx = page >> 5;147int bit = 1 << (page & 31);148149if (mask[idx] & bit)150d_data |= CPLB_USER_RD;151152mask += page_mask_nelts;153if (mask[idx] & bit)154d_data |= CPLB_USER_WR;155}156}157idx = evict_one_dcplb(cpu);158159addr &= PAGE_MASK;160dcplb_tbl[cpu][idx].addr = addr;161dcplb_tbl[cpu][idx].data = d_data;162163_disable_dcplb();164bfin_write32(DCPLB_DATA0 + idx * 4, d_data);165bfin_write32(DCPLB_ADDR0 + idx * 4, addr);166_enable_dcplb();167168return 0;169}170171MGR_ATTR static noinline int icplb_miss(unsigned int cpu)172{173unsigned long addr = bfin_read_ICPLB_FAULT_ADDR();174int status = bfin_read_ICPLB_STATUS();175int idx;176unsigned long i_data;177178nr_icplb_miss[cpu]++;179180/* If inside the uncached DMA region, fault. */181if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend)182return CPLB_PROT_VIOL;183184if (status & FAULT_USERSUPV)185nr_icplb_supv_miss[cpu]++;186187/*188* First, try to find a CPLB that matches this address. If we189* find one, then the fact that we're in the miss handler means190* that the instruction crosses a page boundary.191*/192for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) {193if (icplb_tbl[cpu][idx].data & CPLB_VALID) {194unsigned long this_addr = icplb_tbl[cpu][idx].addr;195if (this_addr <= addr && this_addr + PAGE_SIZE > addr) {196addr += PAGE_SIZE;197break;198}199}200}201202i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB;203204#ifdef CONFIG_BFIN_EXTMEM_ICACHEABLE205/*206* Normal RAM, and possibly the reserved memory area, are207* cacheable.208*/209if (addr < _ramend ||210(addr < physical_mem_end && reserved_mem_icache_on))211i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;212#endif213214if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {215addr = L2_START;216i_data = L2_IMEMORY;217} else if (addr >= physical_mem_end) {218if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {219if (!(status & FAULT_USERSUPV)) {220unsigned long *mask = current_rwx_mask[cpu];221222if (mask) {223int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT;224int idx = page >> 5;225int bit = 1 << (page & 31);226227mask += 2 * page_mask_nelts;228if (mask[idx] & bit)229i_data |= CPLB_USER_RD;230}231}232} else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH233&& (status & FAULT_USERSUPV)) {234addr &= ~(1 * 1024 * 1024 - 1);235i_data &= ~PAGE_SIZE_4KB;236i_data |= PAGE_SIZE_1MB;237} else238return CPLB_PROT_VIOL;239} else if (addr >= _ramend) {240i_data |= CPLB_USER_RD;241if (reserved_mem_icache_on)242i_data |= CPLB_L1_CHBL;243} else {244/*245* Two cases to distinguish - a supervisor access must246* necessarily be for a module page; we grant it247* unconditionally (could do better here in the future).248* Otherwise, check the x bitmap of the current process.249*/250if (!(status & FAULT_USERSUPV)) {251unsigned long *mask = current_rwx_mask[cpu];252253if (mask) {254int page = addr >> PAGE_SHIFT;255int idx = page >> 5;256int bit = 1 << (page & 31);257258mask += 2 * page_mask_nelts;259if (mask[idx] & bit)260i_data |= CPLB_USER_RD;261}262}263}264idx = evict_one_icplb(cpu);265addr &= PAGE_MASK;266icplb_tbl[cpu][idx].addr = addr;267icplb_tbl[cpu][idx].data = i_data;268269_disable_icplb();270bfin_write32(ICPLB_DATA0 + idx * 4, i_data);271bfin_write32(ICPLB_ADDR0 + idx * 4, addr);272_enable_icplb();273274return 0;275}276277MGR_ATTR static noinline int dcplb_protection_fault(unsigned int cpu)278{279int status = bfin_read_DCPLB_STATUS();280281nr_dcplb_prot[cpu]++;282283if (status & FAULT_RW) {284int idx = faulting_cplb_index(status);285unsigned long data = dcplb_tbl[cpu][idx].data;286if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) &&287write_permitted(status, data)) {288data |= CPLB_DIRTY;289dcplb_tbl[cpu][idx].data = data;290bfin_write32(DCPLB_DATA0 + idx * 4, data);291return 0;292}293}294return CPLB_PROT_VIOL;295}296297MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs)298{299int cause = seqstat & 0x3f;300unsigned int cpu = raw_smp_processor_id();301switch (cause) {302case 0x23:303return dcplb_protection_fault(cpu);304case 0x2C:305return icplb_miss(cpu);306case 0x26:307return dcplb_miss(cpu);308default:309return 1;310}311}312313void flush_switched_cplbs(unsigned int cpu)314{315int i;316unsigned long flags;317318nr_cplb_flush[cpu]++;319320flags = hard_local_irq_save();321_disable_icplb();322for (i = first_switched_icplb; i < MAX_CPLBS; i++) {323icplb_tbl[cpu][i].data = 0;324bfin_write32(ICPLB_DATA0 + i * 4, 0);325}326_enable_icplb();327328_disable_dcplb();329for (i = first_switched_dcplb; i < MAX_CPLBS; i++) {330dcplb_tbl[cpu][i].data = 0;331bfin_write32(DCPLB_DATA0 + i * 4, 0);332}333_enable_dcplb();334hard_local_irq_restore(flags);335336}337338void set_mask_dcplbs(unsigned long *masks, unsigned int cpu)339{340int i;341unsigned long addr = (unsigned long)masks;342unsigned long d_data;343unsigned long flags;344345if (!masks) {346current_rwx_mask[cpu] = masks;347return;348}349350flags = hard_local_irq_save();351current_rwx_mask[cpu] = masks;352353if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) {354addr = L2_START;355d_data = L2_DMEMORY;356} else {357d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;358#ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE359d_data |= CPLB_L1_CHBL;360# ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH361d_data |= CPLB_L1_AOW | CPLB_WT;362# endif363#endif364}365366_disable_dcplb();367for (i = first_mask_dcplb; i < first_switched_dcplb; i++) {368dcplb_tbl[cpu][i].addr = addr;369dcplb_tbl[cpu][i].data = d_data;370bfin_write32(DCPLB_DATA0 + i * 4, d_data);371bfin_write32(DCPLB_ADDR0 + i * 4, addr);372addr += PAGE_SIZE;373}374_enable_dcplb();375hard_local_irq_restore(flags);376}377378379