summaryrefslogtreecommitdiffstats
path: root/drivers/firmware/memmap.c
blob: 24945e2da77bc6de0487fc1d17ff5ac27dfafea0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
 * resource cgroups
 *
 * Copyright 2007 OpenVZ SWsoft Inc
 *
 * Author: Pavel Emelianov <xemul@openvz.org>
 *
 */

#include <linux/types.h>
#include <linux/parser.h>
#include <linux/fs.h>
#include <linux/res_counter.h>
#include <linux/uaccess.h>
#include <linux/mm.h>

void res_counter_init(struct res_counter *counter, struct res_counter *parent)
{
	spin_lock_init(&counter->lock);
	counter->limit = RESOURCE_MAX;
	counter->soft_limit = RESOURCE_MAX;
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
// SPDX-License-Identifier: GPL-2.0-only
/*
 * linux/drivers/firmware/memmap.c
 *  Copyright (C) 2008 SUSE LINUX Products GmbH
 *  by Bernhard Walle <bernhard.walle@gmx.de>
 */

#include <linux/string.h>
#include <linux/firmware-map.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/mm.h>

/*
 * Data types ------------------------------------------------------------------
 */

/*
 * Firmware map entry. Because firmware memory maps are flat and not
 * hierarchical, it's ok to organise them in a linked list. No parent
 * information is necessary as for the resource tree.
 */
struct firmware_map_entry {
	/*
	 * start and end must be u64 rather than resource_size_t, because e820
	 * resources can lie at addresses above 4G.
	 */
	u64			start;	/* start of the memory range */
	u64			end;	/* end of the memory range (incl.) */
	const char		*type;	/* type of the memory range */
	struct list_head	list;	/* entry for the linked list */
	struct kobject		kobj;   /* kobject for each entry */
};

/*
 * Forward declarations --------------------------------------------------------
 */
static ssize_t memmap_attr_show(struct kobject *kobj,
				struct attribute *attr, char *buf);
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 -----------------------------------------------------------------
 */

struct memmap_attribute {
	struct attribute attr;
	ssize_t (*show)(struct firmware_map_entry *entry, char *buf);
};

static struct memmap_attribute memmap_start_attr = __ATTR_RO(start);
static struct memmap_attribute memmap_end_attr   = __ATTR_RO(end);
static struct memmap_attribute memmap_type_attr  = __ATTR_RO(type);

/*
 * These are default attributes that are added for every memmap entry.
 */
static struct attribute *def_attrs[] = {
	&memmap_start_attr.attr,
	&memmap_end_attr.attr,
	&memmap_type_attr.attr,
	NULL
};

static const struct sysfs_ops memmap_attr_ops = {
	.show = memmap_attr_show,
};

/* 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().
		 */
		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,
};

/*
 * Registration functions ------------------------------------------------------
 */

/**
 * firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
 * @start: Start of the memory range.
 * @end:   End of the memory range (exclusive).
 * @type:  Type of the memory range.
 * @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised
 *         entry.
 *
 * Common implementation of firmware_map_add() and firmware_map_add_early()
 * which expects a pre-allocated struct firmware_map_entry.
 *
 * Return: 0 always
 */
static int firmware_map_add_entry(u64 start, u64 end,
				  const char *type,
				  struct firmware_map_entry *entry)
{
	BUG_ON(start > end);

	entry->start = start;
	entry->end = end - 1;
	entry->type = type;
	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
 */
static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
{
	static int map_entries_nr;
	static struct kset *mmap_kset;

	if (entry->kobj.state_in_sysfs)
		return -EEXIST;

	if (!mmap_kset) {
		mmap_kset = kset_create_and_add("memmap", NULL, firmware_kobj);
		if (!mmap_kset)
			return -ENOMEM;
	}

	entry->kobj.kset = mmap_kset;
	if (kobject_add(&entry->kobj, NULL, "%d", map_entries_nr++))
		kobject_put(&entry->kobj);

	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.
 * @start: Start of the memory range.
 * @end:   End of the memory range (exclusive)
 * @type:  Type of the memory range.
 *
 * Adds a firmware mapping entry. This function is for memory hotplug, it is
 * similar to function firmware_map_add_early(). The only difference is that
 * it will create the syfs entry dynamically.
 *
 * Return: 0 on success, or -ENOMEM if no memory could be allocated.
 */
int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
{
	struct firmware_map_entry *entry;

	entry = firmware_map_find_entry(start, end - 1, type);
	if (entry)
		return 0;

	entry = firmware_map_find_entry_bootmem(start, end - 1, 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 */
	add_sysfs_fw_map_entry(entry);

	return 0;
}

/**
 * firmware_map_add_early() - Adds a firmware mapping entry.
 * @start: Start of the memory range.
 * @end:   End of the memory range.
 * @type:  Type of the memory range.
 *
 * Adds a firmware mapping entry. This function uses the bootmem allocator
 * for memory allocation.
 *
 * That function must be called before late_initcall.
 *
 * Return: 0 on success, or -ENOMEM if no memory could be allocated.
 */
int __init firmware_map_add_early(u64 start, u64 end, const char *type)
{
	struct firmware_map_entry *entry;

	entry = memblock_alloc(sizeof(struct firmware_map_entry),
			       SMP_CACHE_BYTES);
	if (WARN_ON(!entry))
		return -ENOMEM;

	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.
 *
 * Return: 0 on success, or -EINVAL if no entr