--- /dev/null
+/*
+ * 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;
+}
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
*/