[RFC] arm: memtest

From: Alexander Holler
Date: Thu Nov 08 2012 - 15:48:29 EST


Hello,

I've recently discovered the lack of the command line parameter memtest for ARM. So I've made a patch.

But I have some questions:

1. arch/x86/mm/memtest.c looks platform independ.
The only thing why I don't use it for arm, is because it uses 64bit pointers. Maybe it could be moved to mm/memtest.c. If so, the memtest32.c I'm using (basically a copy of memtest.c) could be moved there too.

2. Because the below memtest32.c is basically a copy of arch/x86/mm/memtest.c, I'm not sure if the mapping from physical to virtual locations there does fit (always) for ARM too. I know almost as much about the in-kernel memory organization on x86 as on ARM, which is not really that much (some theory about TLBs, some source code explorations, ..., but I'm working on it). ;)

3. I've just implemented a test for all the memory which is marked as free, leaving all reserved memory untested. But even if a full memory test could only be done in the boot-loader, I think at least some of the memory the kernel reserves for itself (e.g. for modules) could be tested too. I just haven't searched how/where this could be done. Maybe someone else has a hint or even a patch for the below patch.

4. I don't have an ARM box with bad memory. So my tests are a bit limited. Maybe someone else could do a test with real bad memory.

Anyway, I would still prefer to have at least the possibility to test some of the memory using the kernel instead of none at all. So if nobody offers a better solution, I would be glad if the below patch would find some friends. ;)

Regards,

Alexander


Here is how dmesg does look like (memtest=4):

--------- no error ---------
[ 0.000000] Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] 0000000000 - 0000004000 pattern ffffffff
[ 0.000000] 0000000000 - 0000004000 pattern 55555555
[ 0.000000] 0000000000 - 0000004000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 000054c000 - 0007ffb000 pattern 00000000
[ 0.000000] 000054c000 - 0007ffb000 pattern ffffffff
[ 0.000000] 000054c000 - 0007ffb000 pattern 55555555
[ 0.000000] 000054c000 - 0007ffb000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 000054c000 - 0007ffb000 pattern 00000000
[ 0.000000] Memory: 128MB = 128MB total
[ 0.000000] Memory: 125648k/125648k available, 5424k reserved, 0K highmem
--------- no error ---------

--------- error inected (by sw) ---------
[ 0.000000] Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] 0000000000 - 0000004000 pattern ffffffff
[ 0.000000] 0000000000 - 0000004000 pattern 55555555
[ 0.000000] 0000000000 - 0000004000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 000054c000 - 0007ffb000 pattern 00000000
[ 0.000000] 00000000 bad mem addr 0000600000 - 0000600014 reserved
[ 0.000000] 0000600014 - 0007ffb000 pattern 00000000
[ 0.000000] 000054c000 - 0000600000 pattern ffffffff
[ 0.000000] 0000600014 - 0007ffb000 pattern ffffffff
[ 0.000000] 000054c000 - 0000600000 pattern 55555555
[ 0.000000] 0000600014 - 0007ffb000 pattern 55555555
[ 0.000000] 000054c000 - 0000600000 pattern aaaaaaaa
[ 0.000000] 0000600014 - 0007ffb000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 000054c000 - 0000600000 pattern 00000000
[ 0.000000] 0000600014 - 0007ffb000 pattern 00000000
[ 0.000000] Memory: 128MB = 128MB total
[ 0.000000] Memory: 125648k/125648k available, 5424k reserved, 0K highmem
--------- error inected (by sw) ---------

--------- with hole (mem=99M@0x0 mem=28M@0x6400000) ---------
[ 0.000000] Inode-cache hash table entries: 8192 (order: 3, 32768 bytes)
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] 0000000000 - 0000004000 pattern ffffffff
[ 0.000000] 0000000000 - 0000004000 pattern 55555555
[ 0.000000] 0000000000 - 0000004000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 0000000000 - 0000004000 pattern 00000000
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 000054c000 - 0006300000 pattern 00000000
[ 0.000000] 000054c000 - 0006300000 pattern ffffffff
[ 0.000000] 000054c000 - 0006300000 pattern 55555555
[ 0.000000] 000054c000 - 0006300000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 000054c000 - 0006300000 pattern 00000000
[ 0.000000] early_memtest: # of tests: 4
[ 0.000000] 0006400000 - 0007ffb000 pattern 00000000
[ 0.000000] 0006400000 - 0007ffb000 pattern ffffffff
[ 0.000000] 0006400000 - 0007ffb000 pattern 55555555
[ 0.000000] 0006400000 - 0007ffb000 pattern aaaaaaaa
[ 0.000000] early_memtest: wipe out test pattern from memory
[ 0.000000] 0006400000 - 0007ffb000 pattern 00000000
[ 0.000000] Memory: 99MB 28MB = 127MB total
[ 0.000000] Memory: 124624k/124624k available, 5424k reserved, 0K highmem
--------- with hole (mem=99M@0x0 mem=28M@0x6400000) ---------


From 3034a0d2fc71c8edef43d0d04b3be0ffad484fca Mon Sep 17 00:00:00 2001
From: Alexander Holler <holler@xxxxxxxxxxxxx>
Date: Wed, 31 Oct 2012 22:24:04 +0100
Subject: [PATCH] arm: add memtest

Signed-off-by: Alexander Holler <holler@xxxxxxxxxxxxx>
---
arch/arm/mm/Kconfig | 11 ++++
arch/arm/mm/Makefile | 2 +
arch/arm/mm/init.c | 36 +++++++++++++-
arch/arm/mm/memtest32.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 173 insertions(+), 2 deletions(-)
create mode 100644 arch/arm/mm/memtest32.c

diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig
index 101b968..b14941f 100644
--- a/arch/arm/mm/Kconfig
+++ b/arch/arm/mm/Kconfig
@@ -874,3 +874,14 @@ config ARCH_HAS_BARRIERS
help
This option allows the use of custom mandatory barriers
included via the mach/barriers.h file.
+
+config MEMTEST
+ bool "Memtest"
+ ---help---
+ This option adds a kernel parameter 'memtest', which allows memtest
+ to be set.
+ memtest=0, mean disabled; -- default
+ memtest=1, mean do 1 test pattern;
+ ...
+ memtest=4, mean do 4 test patterns.
+ If you are unsure how to answer this question, answer N.
diff --git a/arch/arm/mm/Makefile b/arch/arm/mm/Makefile
index 8a9c4cb..8cbfda1 100644
--- a/arch/arm/mm/Makefile
+++ b/arch/arm/mm/Makefile
@@ -96,3 +96,5 @@ obj-$(CONFIG_CACHE_FEROCEON_L2) += cache-feroceon-l2.o
obj-$(CONFIG_CACHE_L2X0) += cache-l2x0.o
obj-$(CONFIG_CACHE_XSC3L2) += cache-xsc3l2.o
obj-$(CONFIG_CACHE_TAUROS2) += cache-tauros2.o
+
+obj-$(CONFIG_MEMTEST) += memtest32.o
diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c
index 9aec41f..0941946 100644
--- a/arch/arm/mm/init.c
+++ b/arch/arm/mm/init.c
@@ -584,6 +584,10 @@ static void __init free_highpages(void)
#endif
}

+#ifdef CONFIG_MEMTEST
+extern void early_memtest32(unsigned long start, unsigned long end);
+#endif
+
/*
* mem_init() marks the free areas in the mem_map and tells us how much
* memory is free. This is done after various parts of the system have
@@ -618,6 +622,9 @@ void __init mem_init(void)
reserved_pages = free_pages = 0;

for_each_bank(i, &meminfo) {
+#ifdef CONFIG_MEMTEST
+ phys_addr_t memtest_start = 0xffffffff, memtest_end;
+#endif
struct membank *bank = &meminfo.bank[i];
unsigned int pfn1, pfn2;
struct page *page, *end;
@@ -629,12 +636,37 @@ void __init mem_init(void)
end = pfn_to_page(pfn2 - 1) + 1;

do {
- if (PageReserved(page))
+ if (PageReserved(page)) {
reserved_pages++;
- else if (!page_count(page))
+#ifdef CONFIG_MEMTEST
+ /* something has cut a hole */
+ if (memtest_start != 0xffffffff) {
+ early_memtest32(memtest_start, memtest_end);
+ memtest_start = 0xffffffff;
+ }
+#endif
+ } else if (!page_count(page)) {
free_pages++;
+#ifdef CONFIG_MEMTEST
+ if (memtest_start == 0xffffffff) {
+ /* start of a block for memtest */
+ memtest_start = page_to_phys(page);
+ } else if (memtest_end != page_to_phys(page)) {
+ /* hole detected, call memtest */
+ early_memtest32(memtest_start, memtest_end);
+ /* and start with new values */
+ memtest_start = page_to_phys(page);
+ }
+ memtest_end = page_to_phys(page)+PAGE_SIZE;
+#endif
+ }
page++;
} while (page < end);
+#ifdef CONFIG_MEMTEST
+ if (memtest_start != 0xffffffff)
+ early_memtest32(memtest_start, memtest_end);
+ /* if bad memory was found, reserved_pages is wrong (without bad mem) */
+#endif
}

/*
diff --git a/arch/arm/mm/memtest32.c b/arch/arm/mm/memtest32.c
new file mode 100644
index 0000000..1564b5b
--- /dev/null
+++ b/arch/arm/mm/memtest32.c
@@ -0,0 +1,126 @@
+/* This is just a checkpatch'ed copy of arch/x86/mm/memtest.c modified to use 32bit */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/smp.h>
+#include <linux/init.h>
+#include <linux/pfn.h>
+#include <linux/memblock.h>
+
+static u32 patterns[] __initdata = {
+ 0,
+ 0xffffffffUL,
+ 0x55555555UL,
+ 0xaaaaaaaaUL,
+ 0x11111111UL,
+ 0x22222222UL,
+ 0x44444444UL,
+ 0x88888888UL,
+ 0x33333333UL,
+ 0x66666666UL,
+ 0x99999999UL,
+ 0xccccccccUL,
+ 0x77777777UL,
+ 0xbbbbbbbbUL,
+ 0xddddddddUL,
+ 0xeeeeeeeeUL,
+ 0x7a6c7258UL, /* yeah ;-) */
+};
+
+static void __init reserve_bad_mem(u32 pattern, u32 start_bad, u32 end_bad)
+{
+ pr_info(" %08lx bad mem addr %010lx - %010lx reserved\n",
+ (unsigned long) pattern,
+ (unsigned long) start_bad,
+ (unsigned long) end_bad);
+ memblock_reserve(start_bad, end_bad - start_bad);
+}
+
+static void __init memtest(u32 pattern, u32 start_phys, u32 size)
+{
+ u32 *p, *start, *end;
+ u32 start_bad, last_bad;
+ u32 start_phys_aligned;
+ const size_t incr = sizeof(pattern);
+
+ start_phys_aligned = ALIGN(start_phys, incr);
+ start = __va(start_phys_aligned);
+ end = start + (size - (start_phys_aligned - start_phys)) / incr;
+ start_bad = 0;
+ last_bad = 0;
+
+ for (p = start; p < end; p++)
+ *p = pattern;
+
+ for (p = start; p < end; p++, start_phys_aligned += incr) {
+ if (*p == pattern)
+ continue;
+ if (start_phys_aligned == last_bad + incr) {
+ last_bad += incr;
+ continue;
+ }
+ if (start_bad)
+ reserve_bad_mem(pattern, start_bad, last_bad + incr);
+ start_bad = last_bad = start_phys_aligned;
+ }
+ if (start_bad)
+ reserve_bad_mem(pattern, start_bad, last_bad + incr);
+}
+
+static void __init do_one_pass(u32 pattern, u32 start, u32 end)
+{
+ u64 i;
+ phys_addr_t this_start, this_end;
+
+ for_each_free_mem_range(i, MAX_NUMNODES, &this_start, &this_end, NULL) {
+ this_start = clamp_t(phys_addr_t, this_start, start, end);
+ this_end = clamp_t(phys_addr_t, this_end, start, end);
+ if (this_start < this_end) {
+ pr_info(" %010lx - %010lx pattern %08lx\n",
+ (unsigned long)this_start,
+ (unsigned long)this_end,
+ (unsigned long)cpu_to_be32(pattern));
+ memtest(pattern, this_start, this_end - this_start);
+ }
+ }
+}
+
+/* default is disabled */
+static int memtest_pattern __initdata;
+
+static int __init parse_memtest(char *arg)
+{
+ ssize_t ret __always_unused;
+
+ if (arg)
+ ret = kstrtoint(arg, 0, &memtest_pattern);
+ else
+ memtest_pattern = ARRAY_SIZE(patterns);
+
+ return 0;
+}
+
+early_param("memtest", parse_memtest);
+
+void __init early_memtest32(unsigned long start, unsigned long end)
+{
+ unsigned int i;
+ unsigned int idx = 0;
+
+ if (!memtest_pattern)
+ return;
+
+ pr_info("early_memtest: # of tests: %d\n", memtest_pattern);
+ for (i = 0; i < memtest_pattern; i++) {
+ idx = i % ARRAY_SIZE(patterns);
+ do_one_pass(patterns[idx], start, end);
+ }
+
+ if (idx > 0) {
+ pr_info("early_memtest: wipe out test pattern from memory\n");
+ /* additional test with pattern 0 will do this */
+ do_one_pass(0, start, end);
+ }
+}
--
1.7.8.6


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/