|
@@ -21,6 +21,7 @@
|
|
|
#include <linux/types.h>
|
|
|
#include <linux/bootmem.h>
|
|
|
#include <linux/slab.h>
|
|
|
+#include <linux/mm.h>
|
|
|
|
|
|
/*
|
|
|
* Data types ------------------------------------------------------------------
|
|
@@ -52,6 +53,9 @@ static ssize_t start_show(struct firmware_map_entry *entry, char *buf);
|
|
|
static ssize_t end_show(struct firmware_map_entry *entry, char *buf);
|
|
|
static ssize_t type_show(struct firmware_map_entry *entry, char *buf);
|
|
|
|
|
|
+static struct firmware_map_entry * __meminit
|
|
|
+firmware_map_find_entry(u64 start, u64 end, const char *type);
|
|
|
+
|
|
|
/*
|
|
|
* Static data -----------------------------------------------------------------
|
|
|
*/
|
|
@@ -79,7 +83,52 @@ static const struct sysfs_ops memmap_attr_ops = {
|
|
|
.show = memmap_attr_show,
|
|
|
};
|
|
|
|
|
|
-static struct kobj_type memmap_ktype = {
|
|
|
+/* Firmware memory map entries. */
|
|
|
+static LIST_HEAD(map_entries);
|
|
|
+static DEFINE_SPINLOCK(map_entries_lock);
|
|
|
+
|
|
|
+/*
|
|
|
+ * For memory hotplug, there is no way to free memory map entries allocated
|
|
|
+ * by boot mem after the system is up. So when we hot-remove memory whose
|
|
|
+ * map entry is allocated by bootmem, we need to remember the storage and
|
|
|
+ * reuse it when the memory is hot-added again.
|
|
|
+ */
|
|
|
+static LIST_HEAD(map_entries_bootmem);
|
|
|
+static DEFINE_SPINLOCK(map_entries_bootmem_lock);
|
|
|
+
|
|
|
+
|
|
|
+static inline struct firmware_map_entry *
|
|
|
+to_memmap_entry(struct kobject *kobj)
|
|
|
+{
|
|
|
+ return container_of(kobj, struct firmware_map_entry, kobj);
|
|
|
+}
|
|
|
+
|
|
|
+static void __meminit release_firmware_map_entry(struct kobject *kobj)
|
|
|
+{
|
|
|
+ struct firmware_map_entry *entry = to_memmap_entry(kobj);
|
|
|
+
|
|
|
+ if (PageReserved(virt_to_page(entry))) {
|
|
|
+ /*
|
|
|
+ * Remember the storage allocated by bootmem, and reuse it when
|
|
|
+ * the memory is hot-added again. The entry will be added to
|
|
|
+ * map_entries_bootmem here, and deleted from &map_entries in
|
|
|
+ * firmware_map_remove_entry().
|
|
|
+ */
|
|
|
+ if (firmware_map_find_entry(entry->start, entry->end,
|
|
|
+ entry->type)) {
|
|
|
+ spin_lock(&map_entries_bootmem_lock);
|
|
|
+ list_add(&entry->list, &map_entries_bootmem);
|
|
|
+ spin_unlock(&map_entries_bootmem_lock);
|
|
|
+ }
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(entry);
|
|
|
+}
|
|
|
+
|
|
|
+static struct kobj_type __refdata memmap_ktype = {
|
|
|
+ .release = release_firmware_map_entry,
|
|
|
.sysfs_ops = &memmap_attr_ops,
|
|
|
.default_attrs = def_attrs,
|
|
|
};
|
|
@@ -88,13 +137,6 @@ static struct kobj_type memmap_ktype = {
|
|
|
* Registration functions ------------------------------------------------------
|
|
|
*/
|
|
|
|
|
|
-/*
|
|
|
- * Firmware memory map entries. No locking is needed because the
|
|
|
- * firmware_map_add() and firmware_map_add_early() functions are called
|
|
|
- * in firmware initialisation code in one single thread of execution.
|
|
|
- */
|
|
|
-static LIST_HEAD(map_entries);
|
|
|
-
|
|
|
/**
|
|
|
* firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
|
|
|
* @start: Start of the memory range.
|
|
@@ -118,11 +160,25 @@ static int firmware_map_add_entry(u64 start, u64 end,
|
|
|
INIT_LIST_HEAD(&entry->list);
|
|
|
kobject_init(&entry->kobj, &memmap_ktype);
|
|
|
|
|
|
+ spin_lock(&map_entries_lock);
|
|
|
list_add_tail(&entry->list, &map_entries);
|
|
|
+ spin_unlock(&map_entries_lock);
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * firmware_map_remove_entry() - Does the real work to remove a firmware
|
|
|
+ * memmap entry.
|
|
|
+ * @entry: removed entry.
|
|
|
+ *
|
|
|
+ * The caller must hold map_entries_lock, and release it properly.
|
|
|
+ **/
|
|
|
+static inline void firmware_map_remove_entry(struct firmware_map_entry *entry)
|
|
|
+{
|
|
|
+ list_del(&entry->list);
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Add memmap entry on sysfs
|
|
|
*/
|
|
@@ -144,6 +200,78 @@ static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Remove memmap entry on sysfs
|
|
|
+ */
|
|
|
+static inline void remove_sysfs_fw_map_entry(struct firmware_map_entry *entry)
|
|
|
+{
|
|
|
+ kobject_put(&entry->kobj);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * firmware_map_find_entry_in_list() - Search memmap entry in a given list.
|
|
|
+ * @start: Start of the memory range.
|
|
|
+ * @end: End of the memory range (exclusive).
|
|
|
+ * @type: Type of the memory range.
|
|
|
+ * @list: In which to find the entry.
|
|
|
+ *
|
|
|
+ * This function is to find the memmap entey of a given memory range in a
|
|
|
+ * given list. The caller must hold map_entries_lock, and must not release
|
|
|
+ * the lock until the processing of the returned entry has completed.
|
|
|
+ *
|
|
|
+ * Return: Pointer to the entry to be found on success, or NULL on failure.
|
|
|
+ */
|
|
|
+static struct firmware_map_entry * __meminit
|
|
|
+firmware_map_find_entry_in_list(u64 start, u64 end, const char *type,
|
|
|
+ struct list_head *list)
|
|
|
+{
|
|
|
+ struct firmware_map_entry *entry;
|
|
|
+
|
|
|
+ list_for_each_entry(entry, list, list)
|
|
|
+ if ((entry->start == start) && (entry->end == end) &&
|
|
|
+ (!strcmp(entry->type, type))) {
|
|
|
+ return entry;
|
|
|
+ }
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * firmware_map_find_entry() - Search memmap entry in map_entries.
|
|
|
+ * @start: Start of the memory range.
|
|
|
+ * @end: End of the memory range (exclusive).
|
|
|
+ * @type: Type of the memory range.
|
|
|
+ *
|
|
|
+ * This function is to find the memmap entey of a given memory range.
|
|
|
+ * The caller must hold map_entries_lock, and must not release the lock
|
|
|
+ * until the processing of the returned entry has completed.
|
|
|
+ *
|
|
|
+ * Return: Pointer to the entry to be found on success, or NULL on failure.
|
|
|
+ */
|
|
|
+static struct firmware_map_entry * __meminit
|
|
|
+firmware_map_find_entry(u64 start, u64 end, const char *type)
|
|
|
+{
|
|
|
+ return firmware_map_find_entry_in_list(start, end, type, &map_entries);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * firmware_map_find_entry_bootmem() - Search memmap entry in map_entries_bootmem.
|
|
|
+ * @start: Start of the memory range.
|
|
|
+ * @end: End of the memory range (exclusive).
|
|
|
+ * @type: Type of the memory range.
|
|
|
+ *
|
|
|
+ * This function is similar to firmware_map_find_entry except that it find the
|
|
|
+ * given entry in map_entries_bootmem.
|
|
|
+ *
|
|
|
+ * Return: Pointer to the entry to be found on success, or NULL on failure.
|
|
|
+ */
|
|
|
+static struct firmware_map_entry * __meminit
|
|
|
+firmware_map_find_entry_bootmem(u64 start, u64 end, const char *type)
|
|
|
+{
|
|
|
+ return firmware_map_find_entry_in_list(start, end, type,
|
|
|
+ &map_entries_bootmem);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
|
|
|
* memory hotplug.
|
|
@@ -161,9 +289,19 @@ int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
|
|
|
{
|
|
|
struct firmware_map_entry *entry;
|
|
|
|
|
|
- entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC);
|
|
|
- if (!entry)
|
|
|
- return -ENOMEM;
|
|
|
+ entry = firmware_map_find_entry_bootmem(start, end, type);
|
|
|
+ if (!entry) {
|
|
|
+ entry = kzalloc(sizeof(struct firmware_map_entry), GFP_ATOMIC);
|
|
|
+ if (!entry)
|
|
|
+ return -ENOMEM;
|
|
|
+ } else {
|
|
|
+ /* Reuse storage allocated by bootmem. */
|
|
|
+ spin_lock(&map_entries_bootmem_lock);
|
|
|
+ list_del(&entry->list);
|
|
|
+ spin_unlock(&map_entries_bootmem_lock);
|
|
|
+
|
|
|
+ memset(entry, 0, sizeof(*entry));
|
|
|
+ }
|
|
|
|
|
|
firmware_map_add_entry(start, end, type, entry);
|
|
|
/* create the memmap entry */
|
|
@@ -196,6 +334,36 @@ int __init firmware_map_add_early(u64 start, u64 end, const char *type)
|
|
|
return firmware_map_add_entry(start, end, type, entry);
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * firmware_map_remove() - remove a firmware mapping entry
|
|
|
+ * @start: Start of the memory range.
|
|
|
+ * @end: End of the memory range.
|
|
|
+ * @type: Type of the memory range.
|
|
|
+ *
|
|
|
+ * removes a firmware mapping entry.
|
|
|
+ *
|
|
|
+ * Returns 0 on success, or -EINVAL if no entry.
|
|
|
+ **/
|
|
|
+int __meminit firmware_map_remove(u64 start, u64 end, const char *type)
|
|
|
+{
|
|
|
+ struct firmware_map_entry *entry;
|
|
|
+
|
|
|
+ spin_lock(&map_entries_lock);
|
|
|
+ entry = firmware_map_find_entry(start, end - 1, type);
|
|
|
+ if (!entry) {
|
|
|
+ spin_unlock(&map_entries_lock);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ firmware_map_remove_entry(entry);
|
|
|
+ spin_unlock(&map_entries_lock);
|
|
|
+
|
|
|
+ /* remove the memmap entry */
|
|
|
+ remove_sysfs_fw_map_entry(entry);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Sysfs functions -------------------------------------------------------------
|
|
|
*/
|
|
@@ -217,8 +385,10 @@ static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", entry->type);
|
|
|
}
|
|
|
|
|
|
-#define to_memmap_attr(_attr) container_of(_attr, struct memmap_attribute, attr)
|
|
|
-#define to_memmap_entry(obj) container_of(obj, struct firmware_map_entry, kobj)
|
|
|
+static inline struct memmap_attribute *to_memmap_attr(struct attribute *attr)
|
|
|
+{
|
|
|
+ return container_of(attr, struct memmap_attribute, attr);
|
|
|
+}
|
|
|
|
|
|
static ssize_t memmap_attr_show(struct kobject *kobj,
|
|
|
struct attribute *attr, char *buf)
|