patch-2.1.73 linux/arch/mips/mm/r4xx0.c

Next file: linux/arch/mips/mm/r6000.c
Previous file: linux/arch/mips/mm/r2300.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.72/linux/arch/mips/mm/r4xx0.c linux/arch/mips/mm/r4xx0.c
@@ -3,7 +3,13 @@
  *
  * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com)
  *
- * $Id: r4xx0.c,v 1.5 1997/08/08 18:13:07 miguel Exp $
+ * $Id: r4xx0.c,v 1.11 1997/12/02 06:33:53 ralf Exp $
+ *
+ * To do:
+ *
+ *  - this code is a overbloated pig
+ *  - many of the bug workarounds are not efficient at all, but at
+ *    least they are functional ...
  */
 #include <linux/config.h>
 
@@ -39,7 +45,17 @@
 #undef DEBUG_CACHE
 
 /*
- * Zero an entire page.
+ * On processors with QED R4600 style two set assosicative cache
+ * this is the bit which selects the way in the cache for the
+ * indexed cachops.
+ */
+#define icache_waybit (icache_size >> 1)
+#define dcache_waybit (dcache_size >> 1)
+
+/*
+ * Zero an entire page.  We have three flavours of the routine available.
+ * One for CPU with 16byte, with 32byte cachelines plus a special version
+ * with nops which handles the buggy R4600 v1.x.
  */
 
 static void r4k_clear_page_d16(unsigned long page)
@@ -108,7 +124,7 @@
  * IDT R4600 V1.7 errata:
  *
  *  18. The CACHE instructions Hit_Writeback_Invalidate_D, Hit_Writeback_D,
- *      Hit_Invalidate_D and Create_Dirty_Exclusive_D should only be
+ *      Hit_Invalidate_D and Create_Dirty_Excl_D should only be
  *      executed if there is no other dcache activity. If the dcache is
  *      accessed for another instruction immeidately preceding when these
  *      cache instructions are executing, it is possible that the dcache 
@@ -167,6 +183,44 @@
 }
 
 /*
+ * And this one is for the R4600 V2.0
+ */
+static void r4k_clear_page_r4600_v2(unsigned long page)
+{
+	unsigned int flags;
+
+	save_and_cli(flags);
+	*(volatile unsigned int *)KSEG1;
+	__asm__ __volatile__(
+		".set\tnoreorder\n\t"
+		".set\tnoat\n\t"
+		".set\tmips3\n\t"
+		"daddiu\t$1,%0,%2\n"
+		"1:\tcache\t%3,(%0)\n\t"
+		"sd\t$0,(%0)\n\t"
+		"sd\t$0,8(%0)\n\t"
+		"sd\t$0,16(%0)\n\t"
+		"sd\t$0,24(%0)\n\t"
+		"daddiu\t%0,64\n\t"
+		"cache\t%3,-32(%0)\n\t"
+		"sd\t$0,-32(%0)\n\t"
+		"sd\t$0,-24(%0)\n\t"
+		"sd\t$0,-16(%0)\n\t"
+		"bne\t$1,%0,1b\n\t"
+		"sd\t$0,-8(%0)\n\t"
+		".set\tmips0\n\t"
+		".set\tat\n\t"
+		".set\treorder"
+		:"=r" (page)
+		:"0" (page),
+		 "I" (PAGE_SIZE),
+		 "i" (Create_Dirty_Excl_D)
+		:"$1","memory");
+	restore_flags(flags);
+}
+
+
+/*
  * This is still inefficient.  We only can do better if we know the
  * virtual address where the copy will be accessed.
  */
@@ -355,6 +409,74 @@
 		 "i" (Create_Dirty_Excl_D));
 }
 
+static void r4k_copy_page_r4600_v2(unsigned long to, unsigned long from)
+{
+	unsigned long dummy1, dummy2;
+	unsigned long reg1, reg2, reg3, reg4;
+	unsigned int flags;
+
+	__save_and_cli(flags);
+	__asm__ __volatile__(
+		".set\tnoreorder\n\t"
+		".set\tnoat\n\t"
+		".set\tmips3\n\t"
+		"daddiu\t$1,%0,%8\n"
+		"1:\tnop\n\t"
+		"nop\n\t"
+		"nop\n\t"
+		"nop\n\t"
+		"\tcache\t%9,(%0)\n\t"
+		"lw\t%2,(%1)\n\t"
+		"lw\t%3,4(%1)\n\t"
+		"lw\t%4,8(%1)\n\t"
+		"lw\t%5,12(%1)\n\t"
+		"sw\t%2,(%0)\n\t"
+		"sw\t%3,4(%0)\n\t"
+		"sw\t%4,8(%0)\n\t"
+		"sw\t%5,12(%0)\n\t"
+		"lw\t%2,16(%1)\n\t"
+		"lw\t%3,20(%1)\n\t"
+		"lw\t%4,24(%1)\n\t"
+		"lw\t%5,28(%1)\n\t"
+		"sw\t%2,16(%0)\n\t"
+		"sw\t%3,20(%0)\n\t"
+		"sw\t%4,24(%0)\n\t"
+		"sw\t%5,28(%0)\n\t"
+		"nop\n\t"
+		"nop\n\t"
+		"nop\n\t"
+		"nop\n\t"
+		"cache\t%9,32(%0)\n\t"
+		"daddiu\t%0,64\n\t"
+		"daddiu\t%1,64\n\t"
+		"lw\t%2,-32(%1)\n\t"
+		"lw\t%3,-28(%1)\n\t"
+		"lw\t%4,-24(%1)\n\t"
+		"lw\t%5,-20(%1)\n\t"
+		"sw\t%2,-32(%0)\n\t"
+		"sw\t%3,-28(%0)\n\t"
+		"sw\t%4,-24(%0)\n\t"
+		"sw\t%5,-20(%0)\n\t"
+		"lw\t%2,-16(%1)\n\t"
+		"lw\t%3,-12(%1)\n\t"
+		"lw\t%4,-8(%1)\n\t"
+		"lw\t%5,-4(%1)\n\t"
+		"sw\t%2,-16(%0)\n\t"
+		"sw\t%3,-12(%0)\n\t"
+		"sw\t%4,-8(%0)\n\t"
+		"bne\t$1,%0,1b\n\t"
+		"sw\t%5,-4(%0)\n\t"
+		".set\tmips0\n\t"
+		".set\tat\n\t"
+		".set\treorder"
+		:"=r" (dummy1), "=r" (dummy2),
+		 "=&r" (reg1), "=&r" (reg2), "=&r" (reg3), "=&r" (reg4)
+		:"0" (to), "1" (from),
+		 "I" (PAGE_SIZE),
+		 "i" (Create_Dirty_Excl_D));
+	restore_flags(flags);
+}
+
 /*
  * If you think for one second that this stuff coming up is a lot
  * of bulky code eating too many kernel cache lines.  Think _again_.
@@ -1427,7 +1549,8 @@
 	pte_t *ptep;
 	int text;
 
-	/* If ownes no valid ASID yet, cannot possibly have gotten
+	/*
+	 * If ownes no valid ASID yet, cannot possibly have gotten
 	 * this page into the cache.
 	 */
 	if(mm->context == 0)
@@ -1465,11 +1588,8 @@
 		 */
 		page = (KSEG0 + (page & (dcache_size - 1)));
 		blast_dcache16_page_indexed(page);
-		blast_dcache16_page_indexed(page ^ 0x2000);
-		if(text) {
+		if(text)
 			blast_icache16_page_indexed(page);
-			blast_icache16_page_indexed(page ^ 0x2000);
-		}
 	}
 out:
 	restore_flags(flags);
@@ -1583,10 +1703,10 @@
 		 */
 		page = (KSEG0 + (page & (dcache_size - 1)));
 		blast_dcache32_page_indexed(page);
-		blast_dcache32_page_indexed(page ^ 0x2000);
+		blast_dcache32_page_indexed(page ^ dcache_waybit);
 		if(text) {
 			blast_icache32_page_indexed(page);
-			blast_icache32_page_indexed(page ^ 0x2000);
+			blast_icache32_page_indexed(page ^ icache_waybit);
 		}
 	}
 out:
@@ -1761,7 +1881,9 @@
 }
 
 /*
- * R4600 v2.0 bug: "The CACHE instructions Hit_Writeback_Invalidate_D,
+ * Writeback and invalidate the primary cache dcache before DMA.
+ *
+ * R4600 v2.0 bug: "The CACHE instructions Hit_Writeback_Inv_D,
  * Hit_Writeback_D, Hit_Invalidate_D and Create_Dirty_Exclusive_D will only
  * operate correctly if the internal data cache refill buffer is empty.  These
  * CACHE instructions should be separated from any potential data cache miss
@@ -1769,6 +1891,104 @@
  * (Revision 2.0 device errata from IDT available on http://www.idt.com/
  * in .pdf format.)
  */
+static void
+r4k_flush_cache_pre_dma_out_pc(unsigned long addr, unsigned long size)
+{
+	unsigned long end, a;
+	unsigned int cmode, flags;
+
+	cmode = read_32bit_cp0_register(CP0_CONFIG) & CONFIG_CM_CMASK;
+	if (cmode == CONFIG_CM_CACHABLE_WA ||
+	    cmode == CONFIG_CM_CACHABLE_NO_WA) {
+		/* primary dcache is writethrough, therefore memory
+		   is already consistent with the caches.  */
+		return;
+	}
+
+	if (size >= dcache_size) {
+		flush_cache_all();
+		return;
+	}
+
+	/* Workaround for R4600 bug.  See comment above. */
+	save_and_cli(flags);
+	*(volatile unsigned long *)KSEG1;
+
+	a = addr & ~(dc_lsize - 1);
+	end = (addr + size) & ~(dc_lsize - 1);
+	while (1) {
+		flush_dcache_line(a); /* Hit_Writeback_Inv_D */
+		if (a == end) break;
+		a += dc_lsize;
+	}
+	restore_flags(flags);
+}
+
+static void
+r4k_flush_cache_pre_dma_out_sc(unsigned long addr, unsigned long size)
+{
+	unsigned long end;
+	unsigned long a;
+
+	if (size >= scache_size) {
+		flush_cache_all();
+		return;
+	}
+
+	a = addr & ~(sc_lsize - 1);
+	end = (addr + size) & ~(sc_lsize - 1);
+	while (1) {
+		flush_scache_line(addr); /* Hit_Writeback_Inv_SD */
+		if (addr == end) break;
+		addr += sc_lsize;
+	}
+	r4k_flush_cache_pre_dma_out_pc(addr, size);
+}
+
+static void
+r4k_flush_cache_post_dma_in_pc(unsigned long addr, unsigned long size)
+{
+	unsigned long end;
+	unsigned long a;
+
+	if (size >= dcache_size) {
+		flush_cache_all();
+		return;
+	}
+
+	/* Workaround for R4600 bug.  See comment above. */
+	*(volatile unsigned long *)KSEG1;
+
+	a = addr & ~(dc_lsize - 1);
+	end = (addr + size) & ~(dc_lsize - 1);
+	while (1) {
+		invalidate_dcache_line(a); /* Hit_Invalidate_D */
+		if (a == end) break;
+		a += dc_lsize;
+	}
+}
+
+static void
+r4k_flush_cache_post_dma_in_sc(unsigned long addr, unsigned long size)
+{
+	unsigned long end;
+	unsigned long a;
+
+	if (size >= scache_size) {
+		flush_cache_all();
+		return;
+	}
+
+	a = addr & ~(sc_lsize - 1);
+	end = (addr + size) & ~(sc_lsize - 1);
+	while (1) {
+		invalidate_scache_line(addr); /* Hit_Invalidate_SD */
+		if (addr == end) break;
+		addr += sc_lsize;
+	}
+	r4k_flush_cache_pre_dma_out_pc(addr, size);
+}
+
 static void r4k_flush_page_to_ram_d32i32_r4600(unsigned long page)
 {
 	page &= PAGE_MASK;
@@ -1776,23 +1996,14 @@
 		unsigned long flags;
 
 #ifdef DEBUG_CACHE
-		/* #if 1 */
 		printk("r4600_cram[%08lx]", page);
 #endif
-		/*
-		 * Workaround for R4600 bug.  Explanation see above.
-		 */
-		*(volatile unsigned long *)KSEG1;
-
 		save_and_cli(flags);
 		blast_dcache32_page(page);
-		blast_dcache32_page(page ^ 0x2000);
 #ifdef CONFIG_SGI
 		{
 			unsigned long tmp1, tmp2;
-			/*
-			 * SGI goo.  Have to check this closer ...
-			 */
+
 			__asm__ __volatile__("
 			.set noreorder
 			.set mips3
@@ -1822,20 +2033,48 @@
 	}
 }
 
+/*
+ * While we're protected against bad userland addresses we don't care
+ * very much about what happens in that case.  Usually a segmentation
+ * fault will dump the process later on anyway ...
+ */
 static void r4k_flush_cache_sigtramp(unsigned long addr)
 {
-	addr &= ~(dc_lsize - 1);
-	__asm__ __volatile__("nop;nop;nop;nop");
-	flush_dcache_line(addr);
-	flush_dcache_line(addr + dc_lsize);
-	flush_icache_line(addr);
-	flush_icache_line(addr + dc_lsize);
+	unsigned long daddr, iaddr;
+
+	daddr = addr & ~(dc_lsize - 1);
+	__asm__ __volatile__("nop;nop;nop;nop");	/* R4600 V1.7 */
+	protected_writeback_dcache_line(daddr);
+	protected_writeback_dcache_line(daddr + dc_lsize);
+	iaddr = addr & ~(ic_lsize - 1);
+	protected_flush_icache_line(iaddr);
+	protected_flush_icache_line(iaddr + ic_lsize);
+}
+
+static void r4600v20k_flush_cache_sigtramp(unsigned long addr)
+{
+	unsigned long daddr, iaddr;
+	unsigned int flags;
+
+	daddr = addr & ~(dc_lsize - 1);
+	save_and_cli(flags);
+
+	/* Clear internal cache refill buffer */
+	*(volatile unsigned int *)KSEG1;
+
+	protected_writeback_dcache_line(daddr);
+	protected_writeback_dcache_line(daddr + dc_lsize);
+	iaddr = addr & ~(ic_lsize - 1);
+	protected_flush_icache_line(iaddr);
+	protected_flush_icache_line(iaddr + ic_lsize);
+	restore_flags(flags);
 }
 
 #undef DEBUG_TLB
 #undef DEBUG_TLBUPDATE
 
 #define NTLB_ENTRIES       48  /* Fixed on all R4XX0 variants... */
+
 #define NTLB_ENTRIES_HALF  24  /* Fixed on all R4XX0 variants... */
 
 static inline void r4k_flush_tlb_all(void)
@@ -1943,7 +2182,7 @@
 		int oldpid, newpid, idx;
 
 #ifdef DEBUG_TLB
-               printk("[tlbpage<%d,%08lx>]", vma->vm_mm->context, page);
+		printk("[tlbpage<%d,%08lx>]", vma->vm_mm->context, page);
 #endif
 		newpid = (vma->vm_mm->context & 0xff);
 		page &= (PAGE_MASK << 1);
@@ -2407,6 +2646,8 @@
 
 static void setup_noscache_funcs(void)
 {
+	unsigned int prid;
+
 	switch(dc_lsize) {
 	case 16:
 		clear_page = r4k_clear_page_d16;
@@ -2418,9 +2659,13 @@
 		flush_page_to_ram = r4k_flush_page_to_ram_d16i16;
 		break;
 	case 32:
-		if ((read_32bit_cp0_register(CP0_PRID) & 0xfff0) == 0x2010) {
+		prid = read_32bit_cp0_register(CP0_PRID) & 0xfff0;
+		if (prid == 0x2010) {			/* R4600 V1.7 */
 			clear_page = r4k_clear_page_r4600_v1;
 			copy_page = r4k_copy_page_r4600_v1;
+		} else if (prid == 0x2020) {		/* R4600 V2.0 */
+			clear_page = r4k_clear_page_r4600_v2;
+			copy_page = r4k_copy_page_r4600_v2;
 		} else {
 			clear_page = r4k_clear_page_d32;
 			copy_page = r4k_copy_page_d32;
@@ -2432,6 +2677,8 @@
 		flush_page_to_ram = r4k_flush_page_to_ram_d32i32;
 		break;
 	}
+	flush_cache_pre_dma_out = r4k_flush_cache_pre_dma_out_pc;
+	flush_cache_post_dma_in = r4k_flush_cache_post_dma_in_pc;
 }
 
 static void setup_scache_funcs(void)
@@ -2524,6 +2771,10 @@
 		};
 		break;
 	}
+
+	/* XXX Do these for Indy style caches also.  No need for now ... */
+	flush_cache_pre_dma_out = r4k_flush_cache_pre_dma_out_sc;
+	flush_cache_post_dma_in = r4k_flush_cache_post_dma_in_sc;
 }
 
 typedef int (*probe_func_t)(unsigned long);
@@ -2534,7 +2785,11 @@
 	unsigned long cfg = read_32bit_cp0_register(CP0_CONFIG);
 	int sc_present = 0;
 
-	printk("CPU REVISION IS: %08x\n", read_32bit_cp0_register(CP0_PRID));
+	printk("CPU revision is: %08x\n", read_32bit_cp0_register(CP0_PRID));
+
+	set_cp0_config(CONFIG_CM_CMASK, CONFIG_CM_CACHABLE_NONCOHERENT);
+//set_cp0_config(CONFIG_CM_CMASK, CONFIG_CM_CACHABLE_WA);
+//set_cp0_config(CONFIG_CM_CMASK, CONFIG_CM_CACHABLE_NO_WA);
 
 	probe_icache(cfg);
 	probe_dcache(cfg);
@@ -2546,7 +2801,6 @@
 	case CPU_R4400PC:
 	case CPU_R4400SC:
 	case CPU_R4400MC:
-try_again:
 		probe_scache_kseg1 = (probe_func_t) (KSEG1ADDR(&probe_scache));
 		sc_present = probe_scache_kseg1(cfg);
 		break;
@@ -2554,39 +2808,44 @@
 	case CPU_R4600:
 	case CPU_R4640:
 	case CPU_R4700:
-	case CPU_R5000:
+	case CPU_R5000:	/* XXX: We don't handle the true R5000 SCACHE */
+	case CPU_NEVADA:
 		probe_scache_kseg1 = (probe_func_t)
 			(KSEG1ADDR(&probe_scache_eeprom));
 		sc_present = probe_scache_eeprom(cfg);
 
-		/* Try using tags if eeprom give us bogus data. */
-		if(sc_present == -1)
-			goto try_again;
+		/* Try using tags if eeprom gives us bogus data. */
+		if(sc_present == -1) {
+			probe_scache_kseg1 =
+				(probe_func_t) (KSEG1ADDR(&probe_scache));
+			sc_present = probe_scache_kseg1(cfg);
+		}
 		break;
 	};
 
-	if(!sc_present) {
-		/* Lacks secondary cache. */
-		setup_noscache_funcs();
+	if(sc_present == 1
+	   && (mips_cputype == CPU_R4000SC
+               || mips_cputype == CPU_R4000MC
+               || mips_cputype == CPU_R4400SC
+               || mips_cputype == CPU_R4400MC)) {
+		/* Has a true secondary cache. */
+		setup_scache_funcs();
 	} else {
-		/* Has a secondary cache. */
-		if(mips_cputype != CPU_R4600 &&
-		   mips_cputype != CPU_R4640 &&
-		   mips_cputype != CPU_R4700 &&
-		   mips_cputype != CPU_R5000) {
-			setup_scache_funcs();
-		} else {
-			setup_noscache_funcs();
-			if((mips_cputype != CPU_R5000)) {
-				flush_cache_page =
-					r4k_flush_cache_page_d32i32_r4600;
-				flush_page_to_ram =
-					r4k_flush_page_to_ram_d32i32_r4600;
-			}
+		/* Lacks true secondary cache. */
+		setup_noscache_funcs();
+		if((mips_cputype != CPU_R5000)) { /* XXX */
+			flush_cache_page =
+				r4k_flush_cache_page_d32i32_r4600;
+			flush_page_to_ram =
+				r4k_flush_page_to_ram_d32i32_r4600;
 		}
 	}
 
+	/* XXX Handle true second level cache w/ split I/D */
 	flush_cache_sigtramp = r4k_flush_cache_sigtramp;
+	if ((read_32bit_cp0_register(CP0_PRID) & 0xfff0) == 0x2020) {
+		flush_cache_sigtramp = r4600v20k_flush_cache_sigtramp;
+	}
 
 	flush_tlb_all = r4k_flush_tlb_all;
 	flush_tlb_mm = r4k_flush_tlb_mm;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov