/*
 * 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>
#include <asm/asm-offsets.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];
	GET_PDA(P2, R2);
	P3 = [P2 + PDA_IPDT_SWAPCOUNT];
	P2 = [P2 + PDA_IPDT];
	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.
 	 */

	GET_PDA(P3, R0);
	P2 = [P3 + PDA_IPDT];
#ifdef	CONFIG_CPLB_INFO
	P3 = [P3 + PDA_IPDT_SWAPCOUNT];
	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];
	GET_PDA(P2, R2);
	P3 = [P2 + PDA_DPDT_SWAPCOUNT];
	P2 = [P2 + PDA_DPDT];
	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 */

	GET_PDA(P3, R1);
	P2 = [P3 + PDA_DPDT];
#ifdef	CONFIG_CPLB_INFO
	P3 = [P3 + PDA_DPDT_SWAPCOUNT];
	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)

#ifdef CONFIG_CPLB_SWITCH_TAB_L1
.section .l1.data
#else
.data
#endif

ENTRY(_page_size_table)
.byte4	0x00000400;	/* 1K */
.byte4	0x00001000;	/* 4K */
.byte4	0x00100000;	/* 1M */
.byte4	0x00400000;	/* 4M */
END(_page_size_table)

ENTRY(_dcplb_preference)
.byte4	0x00000001;	/* valid bit */
.byte4	0x00000002;	/* lock bit */
END(_dcplb_preference)
