PM / Sleep: Add the concept of suspend_volatile
authorDoug Anderson <dianders@chromium.org>
Mon, 5 Nov 2012 18:18:36 +0000 (10:18 -0800)
committerGerrit <chrome-bot@google.com>
Wed, 7 Nov 2012 19:43:22 +0000 (11:43 -0800)
In order to catch suspend/resume problems we would like to run a CRC
over most of memory before and after sleep and compare the results.
To do this we need to know which memory regions the suspend/resume
code touches and avoid running the CRC over those regions.

We will consider those regions to be "suspend volatile".  The CRC code
will skip chunks of memory tagged as suspend volatile to avoid falsely
reporting CRC errors.

We add the ability to mark global variables as suspend volatile
(keeping them in a special section) and also the ability to register
dynamic chunks of memory as volatile.

BUG=chrome-os-partner:15914
TEST=suspend_stress_test

Change-Id: I7964f5886d6b62fbca8d82986db20eae83ad0918
Signed-off-by: Doug Anderson <dianders@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/37385
Reviewed-by: Michael Spang <spang@chromium.org>
Reviewed-by: Jon Kliegman <kliegs@chromium.org>
arch/x86/kernel/vmlinux.lds.S
drivers/base/power/Makefile
drivers/base/power/suspend_volatile.c [new file with mode: 0644]
include/asm-generic/vmlinux.lds.h
include/linux/pm.h

index 0f703f1..3656d1d 100644 (file)
@@ -317,6 +317,10 @@ SECTIONS
                *(.bss..page_aligned)
                *(.bss)
                . = ALIGN(PAGE_SIZE);
+               VMLINUX_SYMBOL(__start_suspend_volatile_bss) = .;
+               *(.bss.suspend_volatile)
+               . = ALIGN(PAGE_SIZE);
+               VMLINUX_SYMBOL(__stop_suspend_volatile_bss) = .;
                __bss_stop = .;
        }
 
@@ -373,4 +377,3 @@ INIT_PER_CPU(irq_stack_union);
 . = ASSERT(kexec_control_code_size <= KEXEC_CONTROL_CODE_MAX_SIZE,
            "kexec control code size is too big");
 #endif
-
index 2e58ebb..33b55f7 100644 (file)
@@ -1,5 +1,5 @@
 obj-$(CONFIG_PM)       += sysfs.o generic_ops.o common.o qos.o
-obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
+obj-$(CONFIG_PM_SLEEP) += main.o suspend_volatile.o wakeup.o
 obj-$(CONFIG_PM_RUNTIME)       += runtime.o
 obj-$(CONFIG_PM_TRACE_RTC)     += trace.o
 obj-$(CONFIG_PM_OPP)   += opp.o
diff --git a/drivers/base/power/suspend_volatile.c b/drivers/base/power/suspend_volatile.c
new file mode 100644 (file)
index 0000000..9210f49
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Utils for keeping track of which areas of RAM change during suspend code.
+ *
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+
+#include <linux/addr_overlap.h>
+#include <linux/io.h>
+#include <linux/pm.h>
+
+static LIST_HEAD(volatile_chunks);
+static DEFINE_SPINLOCK(volatile_chunks_lock);
+
+/**
+ * Register a range of memory as volatile.
+ *
+ * @chunk: The chunk to register; memory for this structure must remain
+ *     allocated until the chunk is unregistered.
+ */
+void pm_register_suspend_volatile(struct pm_suspend_volatile_chunk *chunk)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&volatile_chunks_lock, flags);
+       list_add_tail(&chunk->list, &volatile_chunks);
+       spin_unlock_irqrestore(&volatile_chunks_lock, flags);
+}
+
+/**
+ * Unregister a range of memory as volatile.
+ *
+ * @chunk: The chunk to unregister.  Should be exact pointer passed to register.
+ */
+void pm_unregister_suspend_volatile(struct pm_suspend_volatile_chunk *chunk)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&volatile_chunks_lock, flags);
+       list_del(&chunk->list);
+       spin_unlock_irqrestore(&volatile_chunks_lock, flags);
+}
+
+/**
+ * Return true if the chunk overlaps memory that is touched by suspend process.
+ *
+ * Checks both memory that was registered with pm_register_suspend_volatile()
+ * and memory that was tagged with __suspend_volatile.
+ *
+ * @start: Physical start address of chunk to check.
+ * @num_bytes: Size of the chunk to check in bytes.
+ */
+bool pm_does_overlap_suspend_volatile(phys_addr_t start, size_t num_bytes)
+{
+       struct pm_suspend_volatile_chunk *chunk;
+       unsigned long flags;
+       bool is_volatile = false;
+
+       /* Bits marked at compile time as suspend volatile */
+       if (phys_addrs_overlap(start, num_bytes,
+                              virt_to_phys(__start_suspend_volatile_bss),
+                              __stop_suspend_volatile_bss -
+                              __start_suspend_volatile_bss))
+               return true;
+
+       /* Bits registered dynamically */
+       spin_lock_irqsave(&volatile_chunks_lock, flags);
+       list_for_each_entry(chunk, &volatile_chunks, list) {
+               if (phys_addrs_overlap(start, num_bytes,
+                                      chunk->start, chunk->num_bytes)) {
+                       is_volatile = true;
+                       break;
+               }
+       }
+       spin_unlock_irqrestore(&volatile_chunks_lock, flags);
+
+       return is_volatile;
+}
index 8aeadf6..5542c1e 100644 (file)
                *(.dynbss)                                              \
                *(.bss)                                                 \
                *(COMMON)                                               \
+               . = ALIGN(8192);                                        \
+               VMLINUX_SYMBOL(__start_suspend_volatile_bss) = .;       \
+               *(.bss.suspend_volatile)                                \
+               . = ALIGN(8192);                                        \
+               VMLINUX_SYMBOL(__stop_suspend_volatile_bss) = .;        \
        }
 
 /*
index 715305e..e430f49 100644 (file)
@@ -35,6 +35,33 @@ extern void (*pm_idle)(void);
 extern void (*pm_power_off)(void);
 extern void (*pm_power_off_prepare)(void);
 
+/*
+ * Globals that might change at suspend time should be stored in a special
+ * section.  This allows us to detect memory corruption by doing a checksum
+ * of all other memory.
+ */
+#define __suspend_volatile_bss __section(.bss.suspend_volatile)
+
+extern u8 __start_suspend_volatile_bss[];
+extern u8 __stop_suspend_volatile_bss[];
+
+/*
+ * Data that can't be marked __suspend_volatile because it's allocated on
+ * a dynamic heap should be registered with pm_register_suspend_volatile().
+ */
+struct pm_suspend_volatile_chunk {
+       phys_addr_t start;
+       size_t num_bytes;
+       struct list_head list;
+};
+
+extern void pm_register_suspend_volatile(
+       struct pm_suspend_volatile_chunk *chunk);
+extern void pm_unregister_suspend_volatile(
+       struct pm_suspend_volatile_chunk *chunk);
+extern bool pm_does_overlap_suspend_volatile(phys_addr_t start,
+                                            size_t num_bytes);
+
 /*
  * Device power management
  */