8777aa80bb
Currently, we just print the pagealloc corruption warnings and proceed. Sometimes, we are getting multiple errors printed down the line. It will be good to get the device state as early as possible when we get the first pagealloc error. Change-Id: I79155ac8a039b30a3a98d5dd1384d3923082712f Signed-off-by: Subbaraman Narayanamurthy <subbaram@codeaurora.org> Signed-off-by: Prasad Sodagudi <psodagud@codeaurora.org> Signed-off-by: Patrick Daly <pdaly@codeaurora.org> Signed-off-by: Sudarshan Rajagopalan <sudaraja@codeaurora.org> Signed-off-by: Isaac J. Manjarres <isaacm@codeaurora.org>
140 lines
3.2 KiB
C
140 lines
3.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/page_ext.h>
|
|
#include <linux/poison.h>
|
|
#include <linux/ratelimit.h>
|
|
#include <linux/kasan.h>
|
|
|
|
static bool want_page_poisoning __read_mostly
|
|
= IS_ENABLED(CONFIG_PAGE_POISONING_ENABLE_DEFAULT);
|
|
|
|
static int __init early_page_poison_param(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
return strtobool(buf, &want_page_poisoning);
|
|
}
|
|
early_param("page_poison", early_page_poison_param);
|
|
|
|
/**
|
|
* page_poisoning_enabled - check if page poisoning is enabled
|
|
*
|
|
* Return true if page poisoning is enabled, or false if not.
|
|
*/
|
|
bool page_poisoning_enabled(void)
|
|
{
|
|
/*
|
|
* Assumes that debug_pagealloc_enabled is set before
|
|
* memblock_free_all.
|
|
* Page poisoning is debug page alloc for some arches. If
|
|
* either of those options are enabled, enable poisoning.
|
|
*/
|
|
return (want_page_poisoning ||
|
|
(!IS_ENABLED(CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC) &&
|
|
debug_pagealloc_enabled()));
|
|
}
|
|
EXPORT_SYMBOL_GPL(page_poisoning_enabled);
|
|
|
|
static void poison_page(struct page *page)
|
|
{
|
|
void *addr = kmap_atomic(page);
|
|
|
|
/* KASAN still think the page is in-use, so skip it. */
|
|
kasan_disable_current();
|
|
memset(addr, PAGE_POISON, PAGE_SIZE);
|
|
kasan_enable_current();
|
|
kunmap_atomic(addr);
|
|
}
|
|
|
|
static void poison_pages(struct page *page, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
poison_page(page + i);
|
|
}
|
|
|
|
static bool single_bit_flip(unsigned char a, unsigned char b)
|
|
{
|
|
unsigned char error = a ^ b;
|
|
|
|
return error && !(error & (error - 1));
|
|
}
|
|
|
|
static void check_poison_mem(struct page *page,
|
|
unsigned char *mem, size_t bytes)
|
|
{
|
|
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
|
|
unsigned char *start;
|
|
unsigned char *end;
|
|
|
|
if (IS_ENABLED(CONFIG_PAGE_POISONING_NO_SANITY))
|
|
return;
|
|
|
|
start = memchr_inv(mem, PAGE_POISON, bytes);
|
|
if (!start)
|
|
return;
|
|
|
|
for (end = mem + bytes - 1; end > start; end--) {
|
|
if (*end != PAGE_POISON)
|
|
break;
|
|
}
|
|
|
|
if (!__ratelimit(&ratelimit))
|
|
return;
|
|
else if (start == end && single_bit_flip(*start, PAGE_POISON))
|
|
pr_err("pagealloc: single bit error on page with phys start 0x%lx\n",
|
|
(unsigned long)page_to_phys(page));
|
|
else
|
|
pr_err("pagealloc: memory corruption on page with phys start 0x%lx\n",
|
|
(unsigned long)page_to_phys(page));
|
|
|
|
print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start,
|
|
end - start + 1, 1);
|
|
BUG_ON(PANIC_CORRUPTION);
|
|
dump_stack();
|
|
}
|
|
|
|
static void unpoison_page(struct page *page)
|
|
{
|
|
void *addr;
|
|
|
|
addr = kmap_atomic(page);
|
|
/*
|
|
* Page poisoning when enabled poisons each and every page
|
|
* that is freed to buddy. Thus no extra check is done to
|
|
* see if a page was poisoned.
|
|
*/
|
|
check_poison_mem(page, addr, PAGE_SIZE);
|
|
kunmap_atomic(addr);
|
|
}
|
|
|
|
static void unpoison_pages(struct page *page, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
unpoison_page(page + i);
|
|
}
|
|
|
|
void kernel_poison_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
if (!page_poisoning_enabled())
|
|
return;
|
|
|
|
if (enable)
|
|
unpoison_pages(page, numpages);
|
|
else
|
|
poison_pages(page, numpages);
|
|
}
|
|
|
|
#ifndef CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC
|
|
void __kernel_map_pages(struct page *page, int numpages, int enable)
|
|
{
|
|
/* This function does nothing, all work is done via poison pages */
|
|
}
|
|
#endif
|