metag: add boot time LNKGET/LNKSET check
authorJames Hogan <james.hogan@imgtec.com>
Thu, 31 Jan 2013 11:06:03 +0000 (11:06 +0000)
committerJames Hogan <james.hogan@imgtec.com>
Sat, 2 Mar 2013 20:09:56 +0000 (20:09 +0000)
Add boot time check for whether LNKGET/LNKSET go through or around the
cache. Depending on the configuration an info message (no harm), warning
(technically wrong but no harm), or big WARN (expect failure in either
kernel or userland) may be emitted if the behaviour is not as expected:

Configuration                                Hardware   Response
------------------------------------------   --------   --------
AROUND_CACHE                                 through    pr_info
!AROUND_CACHE && ATOMICITY_LNKGET            around     WARN (kernel)
     "        && !ATOMICITY_LNKGET && SMP    around     WARN (user)
     "                   "         && !SMP   around     pr_warn

Signed-off-by: James Hogan <james.hogan@imgtec.com>
arch/metag/mm/cache.c

index 89da74a80ea728a549ccefd0c2ae1af502d324c7..4dd96e457fa2574355d9539894bb3f8fcef86abf 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/io.h>
 #include <asm/cacheflush.h>
 #include <asm/core_reg.h>
+#include <asm/global_lock.h>
 #include <asm/metag_isa.h>
 #include <asm/metag_mem.h>
 #include <asm/metag_regs.h>
@@ -36,6 +37,82 @@ static int icache_set_shift = METAG_TBI_CACHE_SIZE_BASE_LOG2
 static unsigned char dcache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2;
 static unsigned char icache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2;
 
+#ifndef CONFIG_METAG_META12
+/**
+ * metag_lnkget_probe() - Probe whether lnkget/lnkset go around the cache
+ */
+static volatile u32 lnkget_testdata[16] __initdata __aligned(64);
+
+#define LNKGET_CONSTANT 0xdeadbeef
+
+void __init metag_lnkget_probe(void)
+{
+       int temp;
+       long flags;
+
+       /*
+        * It's conceivable the user has configured a globally coherent cache
+        * shared with non-Linux hardware threads, so use LOCK2 to prevent them
+        * from executing and causing cache eviction during the test.
+        */
+       __global_lock2(flags);
+
+       /* read a value to bring it into the cache */
+       (void)lnkget_testdata[0];
+       lnkget_testdata[0] = 0;
+
+       /* lnkget/lnkset it to modify it */
+       asm volatile(
+               "1:     LNKGETD %0, [%1]\n"
+               "       LNKSETD [%1], %2\n"
+               "       DEFR    %0, TXSTAT\n"
+               "       ANDT    %0, %0, #HI(0x3f000000)\n"
+               "       CMPT    %0, #HI(0x02000000)\n"
+               "       BNZ     1b\n"
+               : "=&d" (temp)
+               : "da" (&lnkget_testdata[0]), "bd" (LNKGET_CONSTANT)
+               : "cc");
+
+       /* re-read it to see if the cached value changed */
+       temp = lnkget_testdata[0];
+
+       __global_unlock2(flags);
+
+       /* flush the cache line to fix any incoherency */
+       __builtin_dcache_flush((void *)&lnkget_testdata[0]);
+
+#if defined(CONFIG_METAG_LNKGET_AROUND_CACHE)
+       /* if the cache is right, LNKGET_AROUND_CACHE is unnecessary */
+       if (temp == LNKGET_CONSTANT)
+               pr_info("LNKGET/SET go through cache but CONFIG_METAG_LNKGET_AROUND_CACHE=y\n");
+#elif defined(CONFIG_METAG_ATOMICITY_LNKGET)
+       /*
+        * if the cache is wrong, LNKGET_AROUND_CACHE is really necessary
+        * because the kernel is configured to use LNKGET/SET for atomicity
+        */
+       WARN(temp != LNKGET_CONSTANT,
+            "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n"
+            "Expect kernel failure as it's used for atomicity primitives\n");
+#elif defined(CONFIG_SMP)
+       /*
+        * if the cache is wrong, LNKGET_AROUND_CACHE should be used or the
+        * gateway page won't flush and userland could break.
+        */
+       WARN(temp != LNKGET_CONSTANT,
+            "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n"
+            "Expect userland failure as it's used for user gateway page\n");
+#else
+       /*
+        * if the cache is wrong, LNKGET_AROUND_CACHE is set wrong, but it
+        * doesn't actually matter as it doesn't have any effect on !SMP &&
+        * !ATOMICITY_LNKGET.
+        */
+       if (temp != LNKGET_CONSTANT)
+               pr_warn("LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n");
+#endif
+}
+#endif /* !CONFIG_METAG_META12 */
+
 /**
  * metag_cache_probe() - Probe L1 cache configuration.
  *
@@ -68,6 +145,8 @@ void __init metag_cache_probe(void)
        dcache_set_shift += (config & METAC_CORECFG2_DCSZ_BITS)
                                >> METAC_CORECFG2_DCSZ_S;
        dcache_set_shift -= dcache_sets_log2;
+
+       metag_lnkget_probe();
 #else
        /* Extract cache sizes from global heap segment */
        unsigned long val, u;