#include <linux/suspend.h>
#include <linux/init.h>
#include <linux/crc32.h>
+#include <linux/highmem.h>
#include <linux/ioport.h>
+#include <asm/memory.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <plat/pm.h>
* and reducing the size will cause the CRC save area to grow
*/
-#define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024)
+static bool pm_check_use_xor = 1;
+static bool pm_check_enabled = 1;
+static bool pm_check_should_panic = 1;
+static bool pm_check_print_skips;
+static bool pm_check_print_timings;
+static int pm_check_chunksize = CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024;
static u32 crc_size; /* size needed for the crc block */
static u32 *crcs; /* allocated over suspend/resume */
+static u32 crc_err_cnt; /* number of errors found this resume */
+
typedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg);
+module_param(pm_check_use_xor, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_use_xor, "Use XOR checks instead of CRC");
+module_param(pm_check_enabled, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_enabled,
+ "Enable memory validation on suspend/resume");
+module_param(pm_check_should_panic, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_should_panic, "Panic on CRC errors");
+module_param(pm_check_print_skips, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_print_skips, "Print skipped regions");
+module_param(pm_check_print_timings, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_print_timings,
+ "Print time to compute checks (causes runtime warnings)");
+module_param(pm_check_chunksize, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pm_check_chunksize,
+ "Size of blocks for CRCs, shold be 1K multiples");
+
+/**
+ * s3c_pm_xor_mem() - XOR all the words in the memory range passed
+ * @val: Initial value to start the XOR from
+ * @ptr: Pointer to the start of the memory range
+ * @len: Length (in bytes) of the memory range
+ *
+ */
+static u32 s3c_pm_xor_mem(u32 val, const u32 *ptr, size_t len)
+{
+ int i;
+
+ len >>= 2; /* get words, not bytes */
+ for (i = 0; i < len; i++, ptr++)
+ val ^= *ptr;
+ return val;
+}
+
+/**
+ * s3c_pm_process_mem() - Process one memory chunk for checksum
+ * @val: Starting point for checksum function
+ * @addr: Pointer to the physical memory range. This must be page-aligned.
+ * @len: Length (in bytes) of the memory range
+ *
+ * Loop through the chunk in PAGE_SIZE blocks, ensuring each
+ * page is mapped. Use either crc32 or xor checksum and return
+ * back.
+ */
+static inline u32 s3c_pm_process_mem(u32 val, u32 addr, size_t len)
+{
+ size_t processed = 0;
+
+ if (unlikely(addr & (PAGE_SIZE - 1))) {
+ printk(KERN_ERR "s3c_pm_check: Unaligned address: 0x%x\n",
+ addr);
+ return val;
+ }
+ while (processed < len) {
+ int pfn = __phys_to_pfn(addr + processed);
+ unsigned long left = len - processed;
+ void *virt;
+
+ if (left > PAGE_SIZE)
+ left = PAGE_SIZE;
+ virt = kmap_atomic(pfn_to_page(pfn));
+ if (!virt) {
+ printk(KERN_ERR "s3c_pm_check: Could not map "
+ "page for pfn %d with addr 0x%x\n",
+ pfn, addr + len - processed);
+ return val;
+ }
+ if (pm_check_use_xor)
+ val = s3c_pm_xor_mem(val, virt, left);
+ else
+ val = crc32_le(val, virt, left);
+ kunmap_atomic(virt);
+ processed += left;
+ }
+ return val;
+}
+
/* s3c_pm_run_res
*
* go through the given resource list, and look for system ram
if ((ptr->flags & IORESOURCE_MEM) &&
strcmp(ptr->name, "System RAM") == 0) {
- S3C_PMDBG("Found system RAM at %08lx..%08lx\n",
+ S3C_PMDBG("s3c_pm_check: Found system RAM at "
+ "%08lx..%08lx\n",
(unsigned long)ptr->start,
(unsigned long)ptr->end);
arg = (fn)(ptr, arg);
{
u32 size = (u32)resource_size(res);
- size += CHECK_CHUNKSIZE-1;
- size /= CHECK_CHUNKSIZE;
+ size = DIV_ROUND_UP(size, pm_check_chunksize);
- S3C_PMDBG("Area %08lx..%08lx, %d blocks\n",
+ S3C_PMDBG("s3c_pm_check: Area %08lx..%08lx, %d blocks\n",
(unsigned long)res->start, (unsigned long)res->end, size);
*val += size * sizeof(u32);
* allocating, and thus touching bits of the kernel we do not
* know about.
*/
-
void s3c_pm_check_prepare(void)
{
- crc_size = 0;
+ ktime_t start, stop, delta;
+ if (!pm_check_enabled)
+ return;
+
+ crc_size = 0;
+ /* Timing code generates warnings at this point in suspend */
+ if (pm_check_print_timings)
+ start = ktime_get();
s3c_pm_run_sysram(s3c_pm_countram, &crc_size);
+ if (pm_check_print_timings) {
+ stop = ktime_get();
+ delta = ktime_sub(stop, start);
+ S3C_PMDBG("s3c_pm_check: Suspend prescan took %lld usecs\n",
+ (unsigned long long)ktime_to_ns(delta) >> 10);
+ }
- S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n", crc_size);
+ S3C_PMDBG("s3c_pm_check: Chunk size: %d, %u bytes for checks needed\n",
+ pm_check_chunksize, crc_size);
crcs = kmalloc(crc_size+4, GFP_KERNEL);
if (crcs == NULL)
{
unsigned long addr, left;
- for (addr = res->start; addr < res->end;
- addr += CHECK_CHUNKSIZE) {
- left = res->end - addr;
+ for (addr = res->start; addr <= res->end;
+ addr += pm_check_chunksize) {
+ left = res->end - addr + 1;
+ if (left > pm_check_chunksize)
+ left = pm_check_chunksize;
- if (left > CHECK_CHUNKSIZE)
- left = CHECK_CHUNKSIZE;
-
- *val = crc32_le(~0, phys_to_virt(addr), left);
+ *val = s3c_pm_process_mem(~0, addr, left);
val++;
}
void s3c_pm_check_store(void)
{
- if (crcs != NULL)
- s3c_pm_run_sysram(s3c_pm_makecheck, crcs);
+ ktime_t start, stop, delta;
+
+ if (crcs == NULL)
+ return;
+
+ if (pm_check_print_timings)
+ start = ktime_get();
+ s3c_pm_run_sysram(s3c_pm_makecheck, crcs);
+ if (pm_check_print_timings) {
+ stop = ktime_get();
+ delta = ktime_sub(stop, start);
+ S3C_PMDBG("s3c_pm_check: Suspend memory scan took %lld usecs\n",
+ (unsigned long long)ktime_to_ns(delta) >> 10);
+ }
}
/* in_region
return 1;
}
+static inline void s3c_pm_printskip(char *desc, unsigned long addr)
+{
+ if (!pm_check_print_skips)
+ return;
+ S3C_PMDBG("s3c_pm_check: skipping %08lx, has %s in\n", addr, desc);
+}
+
+/* externs from printk.c and sleep.S */
+extern u32 *sleep_save_sp;
+extern char *pm_check_log_buf;
+extern int *pm_check_log_buf_len;
+extern unsigned *pm_check_logged_chars;
+
/**
* s3c_pm_runcheck() - helper to check a resource on restore.
* @res: The resource to check
{
unsigned long addr;
unsigned long left;
- void *stkpage;
- void *ptr;
+ void *stkbase;
+ void *virt;
u32 calc;
- stkpage = (void *)((u32)&calc & ~PAGE_MASK);
-
- for (addr = res->start; addr < res->end;
- addr += CHECK_CHUNKSIZE) {
- left = res->end - addr;
-
- if (left > CHECK_CHUNKSIZE)
- left = CHECK_CHUNKSIZE;
+ stkbase = current_thread_info();
+ for (addr = res->start; addr <= res->end;
+ addr += pm_check_chunksize) {
+ virt = phys_to_virt(addr);
+ left = res->end - addr + 1;
- ptr = phys_to_virt(addr);
+ if (left > pm_check_chunksize)
+ left = pm_check_chunksize;
- if (in_region(ptr, left, stkpage, 4096)) {
- S3C_PMDBG("skipping %08lx, has stack in\n", addr);
+ if (in_region(virt, left, stkbase, THREAD_SIZE)) {
+ s3c_pm_printskip("stack", addr);
goto skip_check;
}
-
- if (in_region(ptr, left, crcs, crc_size)) {
- S3C_PMDBG("skipping %08lx, has crc block in\n", addr);
+ if (in_region(virt, left, crcs, crc_size)) {
+ s3c_pm_printskip("crc block", addr);
+ goto skip_check;
+ }
+ if (in_region(virt, left, &crc_err_cnt, sizeof(crc_err_cnt))) {
+ s3c_pm_printskip("crc_err_cnt", addr);
+ goto skip_check;
+ }
+ if (in_region(virt, left, pm_check_logged_chars,
+ sizeof(unsigned))) {
+ s3c_pm_printskip("logged_chars", addr);
+ goto skip_check;
+ }
+ if (in_region(virt, left, pm_check_log_buf,
+ *pm_check_log_buf_len)) {
+ s3c_pm_printskip("log_buf", addr);
+ goto skip_check;
+ }
+ if (in_region(virt, left, &sleep_save_sp,
+ sizeof(u32 *))) {
+ s3c_pm_printskip("sleep_save_sp", addr);
+ goto skip_check;
+ }
+ if (in_region(virt, left,
+ sleep_save_sp - (PAGE_SIZE / sizeof(u32)),
+ PAGE_SIZE * 2)) {
+ s3c_pm_printskip("*sleep_save_sp", addr);
goto skip_check;
}
/* calculate and check the checksum */
- calc = crc32_le(~0, ptr, left);
+ calc = s3c_pm_process_mem(~0, addr, left);
if (calc != *val) {
- printk(KERN_ERR "Restore CRC error at "
- "%08lx (%08x vs %08x)\n", addr, calc, *val);
-
- S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n",
- addr, calc, *val);
+ crc_err_cnt++;
+ S3C_PMDBG("s3c_pm_check: Restore CRC error at %08lx "
+ "(%08x vs %08x)\n",
+ addr, calc, *val);
}
skip_check:
*/
void s3c_pm_check_restore(void)
{
- if (crcs != NULL)
- s3c_pm_run_sysram(s3c_pm_runcheck, crcs);
+ ktime_t start, stop, delta;
+
+ crc_err_cnt = 0;
+ if (pm_check_print_timings)
+ start = ktime_get();
+ s3c_pm_run_sysram(s3c_pm_runcheck, crcs);
+ if (pm_check_print_timings) {
+ stop = ktime_get();
+ delta = ktime_sub(stop, start);
+ S3C_PMDBG("s3c_pm_check: Resume memory scan took %lld usecs\n",
+ (unsigned long long)ktime_to_ns(delta) >> 10);
+ }
+ if (crc_err_cnt) {
+ S3C_PMDBG("s3c_pm_check: %d CRC errors\n", crc_err_cnt);
+ if (pm_check_should_panic)
+ panic("%d CRC errors\n", crc_err_cnt);
+ }
}
/**
kfree(crcs);
crcs = NULL;
}
-