/*
* File: arch/blackfin/mach-common/cplbmgtr.S
* Based on:
* Author: LG Soft India
*
* Created: ?
* Description: CPLB replacement routine for CPLB mismatch
*
* Modified:
* Copyright 2004-2006 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* Usage: int _cplb_mgr(is_data_miss,int enable_cache)
* is_data_miss==2 => Mark as Dirty, write to the clean data page
* is_data_miss==1 => Replace a data CPLB.
* is_data_miss==0 => Replace an instruction CPLB.
*
* Returns:
* CPLB_RELOADED => Successfully updated CPLB table.
* CPLB_NO_UNLOCKED => All CPLBs are locked, so cannot be evicted.
* This indicates that the CPLBs in the configuration
* tablei are badly configured, as this should never
* occur.
* CPLB_NO_ADDR_MATCH => The address being accessed, that triggered the
* exception, is not covered by any of the CPLBs in
* the configuration table. The application is
* presumably misbehaving.
* CPLB_PROT_VIOL => The address being accessed, that triggered the
* exception, was not a first-write to a clean Write
* Back Data page, and so presumably is a genuine
* violation of the page's protection attributes.
* The application is misbehaving.
*/
#include <linux/linkage.h>
#include <asm/blackfin.h>
#include <asm/cplb.h>
#ifdef CONFIG_EXCPT_IRQ_SYSC_L1
.section .l1.text
#else
.text
#endif
.align 2;
ENTRY(_cplb_mgr)
[--SP]=( R7:4,P5:3 );
CC = R0 == 2;
IF CC JUMP .Ldcplb_write;
CC = R0 == 0;
IF !CC JUMP .Ldcplb_miss_compare;
/* ICPLB Miss Exception. We need to choose one of the
* currently-installed CPLBs, and replace it with one
* from the configuration table.
*/
/* A multi-word instruction can cross a page boundary. This means the
* first part of the instruction can be in a valid page, but the
* second part is not, and hence generates the instruction miss.
* However, the fault address is for the start of the instruction,
* not the part that's in the bad page. Therefore, we have to check
* whether the fault address applies to a page that is already present
* in the table.
*/
P4.L = LO(ICPLB_FAULT_ADDR);
P4.H = HI(ICPLB_FAULT_ADDR);
P1 = 16;
P5.L = _page_size_table;
P5.H = _page_size_table;
P0.L = LO(ICPLB_DATA0);
P0.H = HI(ICPLB_DATA0);
R4 = [P4]; /* Get faulting address*/
R6 = 64; /* Advance past the fault address, which*/
R6 = R6 + R4; /* we'll use if we find a match*/
R3 = ((16 << 8) | 2); /* Extract mask, two bits at posn 16 */
R5 = 0;
.Lisearch:
R1 = [P0-0x100]; /* Address for this CPLB */
R0 = [P0++]; /* Info for this CPLB*/
CC = BITTST(R0,0); /* Is the CPLB valid?*/
IF !CC JUMP .Lnomatch; /* Skip it, if not.*/
CC = R4 < R1(IU); /* If fault address less than page start*/
IF CC JUMP .Lnomatch; /* then skip this one.*/
R2 = EXTRACT(R0,R3.L) (Z); /* Get page size*/
P1 = R2;
P1 = P5 + (P1<<2); /* index into page-size table*/
R2 = [P1]; /* Get the page size*/
R1 = R1 + R2; /* and add to page start, to get page end*/
CC = R4 < R1(IU); /* and see whether fault addr is in page.*/
IF !CC R4 = R6; /* If so, advance the address and finish loop.*/
IF !CC JUMP .Lisearch_done;
.Lnomatch:
/* Go around again*/
R5 += 1;
CC = BITTST(R5, 4); /* i.e CC = R5 >= 16*/
IF !CC JUMP .Lisearch;
.Lisearch_done:
I0 = R4; /* Fault address we'll search for*/
/* set up pointers */
P0.L = LO(ICPLB_DATA0);
P0.H = HI(ICPLB_DATA0);
/* The replacement procedure for ICPLBs */
P4.L = LO(IMEM_CONTROL);
P4.H = HI(IMEM_CONTROL);
/* Turn off CPLBs while we work, necessary according to HRM before
* modifying CPLB descriptors
*/
R5 = [P4]; /* Control Register*/
BITCLR(R5,ENICPLB_P);
CLI R1;
SSYNC; /* SSYNC required before writing to IMEM_CONTROL. */
.align 8;
[P4] = R5;
SSYNC;
STI R1;
R1 = -1; /* end point comparison */
R3 = 16; /* counter */
/* Search through CPLBs for first non-locked entry */
/* Overwrite it by moving everyone else up by 1 */
.Licheck_lock:
R0 = [P0++];
R3 = R3 + R1;
CC = R3 == R1;
IF CC JUMP .Lall_locked;
CC = BITTST(R0, 0); /* an invalid entry is good */
IF !CC JUMP .Lifound_victim;
CC = BITTST(R0,1); /* but a locked entry isn't */
IF CC JUMP .Licheck_lock;
.Lifound_victim:
#ifdef CONFIG_CPLB_INFO
R7 = [P0 - 0x104];
P2.L = _ipdt_table;
P2.H = _ipdt_table;
P3.L = _ipdt_swapcount_table;
P3.H = _ipdt_swapcount_table;
P3 += -4;
.Licount:
R2 = [P2]; /* address from config table */
P2 += 8;
P3 += 8;
CC = R2==-1;
IF CC JUMP .Licount_done;
CC = R7==R2;
IF !CC JUMP .Licount;
R7 = [P3];
R7 += 1;
[P3] = R7;
CSYNC;
.Licount_done:
#endif
LC0=R3;
LSETUP(.Lis_move,.Lie_move) LC0;
.Lis_move:
R0 = [P0];
[P0 - 4] = R0;
R0 = [P0 - 0x100];
[P0-0x104] = R0;
.Lie_move:
P0+=4;
/* Clear ICPLB_DATA15, in case we don't find a replacement
* otherwise, we would have a duplicate entry, and will crash
*/
R0 = 0;
[P0 - 4] = R0;
/* We've made space in the ICPLB table, so that ICPLB15
* is now free to be overwritten. Next, we have to determine
* which CPLB we need to install, from the configuration
* table. This is a matter of getting the start-of-page
* addresses and page-lengths from the config table, and
* determining whether the fault address falls within that
* range.
*/
P2.L = _ipdt_table;
P2.H = _ipdt_table;
#ifdef CONFIG_CPLB_INFO
P3.L = _ipdt_swapcount_table;
P3.H = _ipdt_swapcount_table;
P3 += -8;
#endif
P0.L = _page_size_table;
P0.H = _page_size_table;
/* Retrieve our fault address (which may have been advanced
* because the faulting instruction crossed a page boundary).
*/
R0 = I0;
/* An extraction pattern, to get the page-size bits from
* the CPLB data entry. Bits 16-17, so two bits at posn 16.
*/
R1 = ((16<<8)|2);
.Linext: R4 = [P2++]; /* address from config table */
R2 = [P2++]; /* data from config table */
#ifdef CONFIG_CPLB_INFO
P3 += 8;
#endif
CC = R4 == -1; /* End of config table*/
IF CC JUMP .Lno_page_in_table;
/* See if failed address > start address */
CC = R4 <= R0(IU);
IF !CC JUMP .Linext;
/* extract page size (17:16)*/
R3 = EXTRACT(R2, R1.L) (Z);
/* add page size to addr to get range */
P5 = R3;
P5 = P0 + (P5 << 2); /* scaled, for int access*/
R3 = [P5];
R3 = R3 + R4;
/* See if failed address < (start address + page size) */
CC = R0 < R3(IU);
IF !CC JUMP .Linext;
/* We've found a CPLB in the config table that covers
* the faulting address, so install this CPLB into the
* last entry of the table.
*/
P1.L = LO(ICPLB_DATA15); /* ICPLB_DATA15 */
P1.H = HI(ICPLB_DATA15);
[P1] = R2;
[P1-0x100] = R4;
#ifdef CONFIG_CPLB_INFO
R3 = [P3];
R3 += 1;
[P3] = R3;
#endif
/* P4 points to IMEM_CONTROL, and R5 contains its old
* value, after we disabled ICPLBS. Re-enable them.
*/
BITSET(R5,ENICPLB_P);
CLI R2;
SSYNC; /* SSYNC required before writing to IMEM_CONTROL. */
.align 8;
[P4] = R5;
SSYNC;
STI R2;
( R7:4,P5:3 ) = [SP++];
R0 = CPLB_RELOADED;
RTS;
/* FAILED CASES*/
.Lno_page_in_table:
R0 = CPLB_NO_ADDR_MATCH;
JUMP .Lfail_ret;
.Lall_locked:
R0 = CPLB_NO_UNLOCKED;
JUMP .Lfail_ret;
.Lprot_violation:
R0 = CPLB_PROT_VIOL;
.Lfail_ret:
/* Make sure we turn protection/cache back on, even in the failing case */
BITSET(R5,ENICPLB_P);
CLI R2;
SSYNC; /* SSYNC required before writing to IMEM_CONTROL. */
.align 8;
[P4] = R5;
SSYNC;
STI R2;
( R7:4,P5:3 ) = [SP++];
RTS;
.Ldcplb_write:
/* if a DCPLB is marked as write-back (CPLB_WT==0), and
* it is clean (CPLB_DIRTY==0), then a write to the
* CPLB's page triggers a protection violation. We have to
* mark the CPLB as dirty, to indicate that there are
* pending writes associated with the CPLB.
*/
P4.L = LO(DCPLB_STATUS);
P4.H = HI(DCPLB_STATUS);
P3.L = LO(DCPLB_DATA0);
P3.H = HI(DCPLB_DATA0);
R5 = [P4];
/* A protection violation can be caused by more than just writes
* to a clean WB page, so we have to ensure that:
* - It's a write
* - to a clean WB page
* - and is allowed in the mode the access occurred.
*/
CC = BITTST(R5, 16); /* ensure it was a write*/
IF !CC JUMP .Lprot_violation;
/* to check the rest, we have to retrieve the DCPLB.*/
/* The low half of DCPLB_STATUS is a bit mask*/
R2 = R5.L (Z); /* indicating which CPLB triggered the event.*/
R3 = 30; /* so we can use this to determine the offset*/
R2.L = SIGNBITS R2;
R2 = R2.L (Z); /* into the DCPLB table.*/
R3 = R3 - R2;
P4 = R3;
P3 = P3 + (P4<<2);
R3 = [P3]; /* Retrieve the CPLB*/
/* Now we can check whether it's a clean WB page*/
CC = BITTST(R3, 14); /* 0==WB, 1==WT*/
IF CC JUMP .Lprot_violation;
CC = BITTST(R3, 7); /* 0 == clean, 1 == dirty*/
IF CC JUMP .Lprot_violation;
/* Check whether the write is allowed in the mode that was active.*/
R2 = 1<<3; /* checking write in user mode*/
CC = BITTST(R5, 17); /* 0==was user, 1==was super*/
R5 = CC;
R2 <<= R5; /* if was super, check write in super mode*/
R2 = R3 & R2;
CC = R2 == 0;
IF CC JUMP .Lprot_violation;
/* It's a genuine write-to-clean-page.*/
BITSET(R3, 7); /* mark as dirty*/
[P3] = R3; /* and write back.*/
NOP;
CSYNC;
( R7:4,P5:3 ) = [SP++];
R0 = CPLB_RELOADED;
RTS;
.Ldcplb_miss_compare:
/* Data CPLB Miss event. We need to choose a CPLB to
* evict, and then locate a new CPLB to install from the
* config table, that covers the faulting address.
*/
P1.L = LO(DCPLB_DATA15);
P1.H = HI(DCPLB_DATA15);
P4.L = LO(DCPLB_FAULT_ADDR);
P4.H = HI(DCPLB_FAULT_ADDR);
R4 = [P4];
I0 = R4;
/* The replacement procedure for DCPLBs*/
R6 = R1; /* Save for later*/
/* Turn off CPLBs while we work.*/
P4.L = LO(DMEM_CONTROL);
P4.H = HI(DMEM_CONTROL);
R5 = [P4];
BITCLR(R5,ENDCPLB_P);
CLI R0;
SSYNC; /* SSYNC required before writing to DMEM_CONTROL. */
.align 8;
[P4] = R5;
SSYNC;
STI R0;
/* Start looking for a CPLB to evict. Our order of preference
* is: invalid CPLBs, clean CPLBs, dirty CPLBs. Locked CPLBs
* are no good.
*/
I1.L = LO(DCPLB_DATA0);
I1.H = HI(DCPLB_DATA0);
P1 = 2;
P2 = 16;
I2.L = _dcplb_preference;
I2.H = _dcplb_preference;
LSETUP(.Lsdsearch1, .Ledsearch1) LC0 = P1;
.Lsdsearch1:
R0 = [I2++]; /* Get the bits we're interested in*/
P0 = I1; /* Go back to start of table*/
LSETUP (.Lsdsearch2, .Ledsearch2) LC1 = P2;
.Lsdsearch2:
R1 = [P0++]; /* Fetch each installed CPLB in turn*/
R2 = R1 & R0; /* and test for interesting bits.*/
CC = R2 == 0; /* If none are set, it'll do.*/
IF !CC JUMP .Lskip_stack_check;
R2 = [P0 - 0x104]; /* R2 - PageStart */
P3.L = _page_size_table; /* retrieve end address */
P3.H = _page_size_table; /* retrieve end address */
R3 = 0x1002; /* 16th - position, 2 bits -length */
#if ANOMALY_05000209
nop; /* Anomaly 05000209 */
#endif
R7 = EXTRACT(R1,R3.l);
R7 = R7 << 2; /* Page size index offset */
P5 = R7;
P3 = P3 + P5;
R7 = [P3]; /* page size in bytes */
R7 = R2 + R7; /* R7 - PageEnd */
R4 = SP; /* Test SP is in range */
CC = R7 < R4; /* if PageEnd < SP */
IF CC JUMP .Ldfound_victim;
R3 = 0x284; /* stack length from start of trap till
* the point.
* 20 stack locations for future modifications
*/
R4 = R4 + R3;
CC = R4 < R2; /* if SP + stacklen < PageStart */
IF CC JUMP .Ldfound_victim;
.Lskip_stack_check:
.Ledsearch2: NOP;
.Ledsearch1: NOP;
/* If we got here, we didn't find a DCPLB we considered
* replacable, which means all of them were locked.
*/
JUMP .Lall_locked;
.Ldfound_victim:
#ifdef CONFIG_CPLB_INFO
R7 = [P0 - 0x104];
P2.L = _dpdt_table;
P2.H = _dpdt_table;
P3.L = _dpdt_swapcount_table;
P3.H = _dpdt_swapcount_table;
P3 += -4;
.Ldicount:
R2 = [P2];
P2 += 8;
P3 += 8;
CC = R2==-1;
IF CC JUMP .Ldicount_done;
CC = R7==R2;
IF !CC JUMP .Ldicount;
R7 = [P3];
R7 += 1;
[P3] = R7;
.Ldicount_done:
#endif
/* Clean down the hardware loops*/
R2 = 0;
LC1 = R2;
LC0 = R2;
/* There's a suitable victim in [P0-4] (because we've
* advanced already).
*/
.LDdoverwrite:
/* [P0-4] is a suitable victim CPLB, so we want to
* overwrite it by moving all the following CPLBs
* one space closer to the start.
*/
R1.L = LO(DCPLB_DATA16); /* DCPLB_DATA15 + 4 */
R1.H = HI(DCPLB_DATA16);
R0 = P0;
/* If the victim happens to be in DCPLB15,
* we don't need to move anything.
*/
CC = R1 == R0;
IF CC JUMP .Lde_moved;
R1 = R1 - R0;
R1 >>= 2;
P1 = R1;
LSETUP(.Lds_move, .Lde_move) LC0=P1;
.Lds_move:
R0 = [P0++]; /* move data */
[P0 - 8] = R0;
R0 = [P0-0x104] /* move address */
.Lde_move:
[P0-0x108] = R0;
.Lde_moved:
NOP;
/* Clear DCPLB_DATA15, in case we don't find a replacement
* otherwise, we would have a duplicate entry, and will crash
*/
R0 = 0;
[P0 - 0x4] = R0;
/* We've now made space in DCPLB15 for the new CPLB to be
* installed. The next stage is to locate a CPLB in the
* config table that covers the faulting address.
*/
R0 = I0; /* Our faulting address */
P2.L = _dpdt_table;
P2.H = _dpdt_table;
#ifdef CONFIG_CPLB_INFO
P3.L = _dpdt_swapcount_table;
P3.H = _dpdt_swapcount_table;
P3 += -8;
#endif
P1.L = _page_size_table;
P1.H = _page_size_table;
/* An extraction pattern, to retrieve bits 17:16.*/
R1 = (16<<8)|2;
.Ldnext: R4 = [P2++]; /* address */
R2 = [P2++]; /* data */
#ifdef CONFIG_CPLB_INFO
P3 += 8;
#endif
CC = R4 == -1;
IF CC JUMP .Lno_page_in_table;
/* See if failed address > start address */
CC = R4 <= R0(IU);
IF !CC JUMP .Ldnext;
/* extract page size (17:16)*/
R3 = EXTRACT(R2, R1.L) (Z);
/* add page size to addr to get range */
P5 = R3;
P5 = P1 + (P5 << 2);
R3 = [P5];
R3 = R3 + R4;
/* See if failed address < (start address + page size) */
CC = R0 < R3(IU);
IF !CC JUMP .Ldnext;
/* We've found the CPLB that should be installed, so
* write it into CPLB15, masking off any caching bits
* if necessary.
*/
P1.L = LO(DCPLB_DATA15);
P1.H = HI(DCPLB_DATA15);
/* If the DCPLB has cache bits set, but caching hasn't
* been enabled, then we want to mask off the cache-in-L1
* bit before installing. Moreover, if caching is off, we
* also want to ensure that the DCPLB has WT mode set, rather
* than WB, since WB pages still trigger first-write exceptions
* even when not caching is off, and the page isn't marked as
* cachable. Finally, we could mark the page as clean, not dirty,
* but we choose to leave that decision to the user; if the user
* chooses to have a CPLB pre-defined as dirty, then they always
* pay the cost of flushing during eviction, but don't pay the
* cost of first-write exceptions to mark the page as dirty.
*/
#ifdef CONFIG_BFIN_WT
BITSET(R6, 14); /* Set WT*/
#endif
[P1] = R2;
[P1-0x100] = R4;
#ifdef CONFIG_CPLB_INFO
R3 = [P3];
R3 += 1;
[P3] = R3;
#endif
/* We've installed the CPLB, so re-enable CPLBs. P4
* points to DMEM_CONTROL, and R5 is the value we
* last wrote to it, when we were disabling CPLBs.
*/
BITSET(R5,ENDCPLB_P);
CLI R2;
.align 8;
[P4] = R5;
SSYNC;
STI R2;
( R7:4,P5:3 ) = [SP++];
R0 = CPLB_RELOADED;
RTS;
ENDPROC(_cplb_mgr)
.data
.align 4;
_page_size_table:
.byte4 0x00000400; /* 1K */
.byte4 0x00001000; /* 4K */
.byte4 0x00100000; /* 1M */
.byte4 0x00400000; /* 4M */
.align 4;
_dcplb_preference:
.byte4 0x00000001; /* valid bit */
.byte4 0x00000002; /* lock bit */