Blame view

drivers/firmware/memmap.c 11.8 KB
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
1
2
3
  /*
   * linux/drivers/firmware/memmap.c
   *  Copyright (C) 2008 SUSE LINUX Products GmbH
97bef7dd0   Bernhard Walle   Bernhard has moved
4
   *  by Bernhard Walle <bernhard.walle@gmx.de>
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License v2.0 as published by
   * the Free Software Foundation
   *
   * 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/string.h>
  #include <linux/firmware-map.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/types.h>
  #include <linux/bootmem.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
23
  #include <linux/slab.h>
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
24
  #include <linux/mm.h>
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
25
26
27
28
29
30
31
32
33
34
35
  
  /*
   * 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 {
3b0fde0fa   Yinghai Lu   firmware_map: fix...
36
37
38
39
40
41
  	/*
  	 * 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.) */
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
42
43
44
45
46
47
48
49
50
51
52
53
54
  	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);
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
55
56
  static struct firmware_map_entry * __meminit
  firmware_map_find_entry(u64 start, u64 end, const char *type);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
57
58
59
60
61
62
63
64
  /*
   * Static data -----------------------------------------------------------------
   */
  
  struct memmap_attribute {
  	struct attribute attr;
  	ssize_t (*show)(struct firmware_map_entry *entry, char *buf);
  };
da2bdf9a6   Roel Kluin   Make various thin...
65
66
67
  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);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
68
69
70
71
72
73
74
75
76
77
  
  /*
   * 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
  };
52cf25d0a   Emese Revfy   Driver core: Cons...
78
  static const struct sysfs_ops memmap_attr_ops = {
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
79
80
  	.show = memmap_attr_show,
  };
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
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
  /* 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().
  		 */
7a6f93b0a   Yasuaki Ishimatsu   firmware, memmap:...
112
113
114
  		spin_lock(&map_entries_bootmem_lock);
  		list_add(&entry->list, &map_entries_bootmem);
  		spin_unlock(&map_entries_bootmem_lock);
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
115
116
117
118
119
120
121
122
123
  
  		return;
  	}
  
  	kfree(entry);
  }
  
  static struct kobj_type __refdata memmap_ktype = {
  	.release	= release_firmware_map_entry,
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
124
125
126
127
128
129
130
  	.sysfs_ops	= &memmap_attr_ops,
  	.default_attrs	= def_attrs,
  };
  
  /*
   * Registration functions ------------------------------------------------------
   */
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
131
  /**
31bad9246   Bernhard Walle   firmware/memmap: ...
132
   * firmware_map_add_entry() - Does the real work to add a firmware memmap entry.
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
133
   * @start: Start of the memory range.
4ed940d4c   Yasuaki Ishimatsu   firmware_map: mak...
134
   * @end:   End of the memory range (exclusive).
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
135
136
137
   * @type:  Type of the memory range.
   * @entry: Pre-allocated (either kmalloc() or bootmem allocator), uninitialised
   *         entry.
31bad9246   Bernhard Walle   firmware/memmap: ...
138
139
140
141
   *
   * Common implementation of firmware_map_add() and firmware_map_add_early()
   * which expects a pre-allocated struct firmware_map_entry.
   **/
3b0fde0fa   Yinghai Lu   firmware_map: fix...
142
  static int firmware_map_add_entry(u64 start, u64 end,
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
143
144
145
146
147
148
  				  const char *type,
  				  struct firmware_map_entry *entry)
  {
  	BUG_ON(start > end);
  
  	entry->start = start;
4ed940d4c   Yasuaki Ishimatsu   firmware_map: mak...
149
  	entry->end = end - 1;
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
150
151
152
  	entry->type = type;
  	INIT_LIST_HEAD(&entry->list);
  	kobject_init(&entry->kobj, &memmap_ktype);
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
153
  	spin_lock(&map_entries_lock);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
154
  	list_add_tail(&entry->list, &map_entries);
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
155
  	spin_unlock(&map_entries_lock);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
156
157
158
  
  	return 0;
  }
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
159
160
161
162
163
164
165
166
167
168
169
  /**
   * 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);
  }
d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
  /*
   * 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 (!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;
  }
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  /*
   * 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);
  }
31bad9246   Bernhard Walle   firmware/memmap: ...
261
  /**
d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
262
263
   * firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
   * memory hotplug.
31bad9246   Bernhard Walle   firmware/memmap: ...
264
   * @start: Start of the memory range.
4ed940d4c   Yasuaki Ishimatsu   firmware_map: mak...
265
   * @end:   End of the memory range (exclusive)
31bad9246   Bernhard Walle   firmware/memmap: ...
266
267
   * @type:  Type of the memory range.
   *
d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
268
269
270
   * 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.
31bad9246   Bernhard Walle   firmware/memmap: ...
271
272
273
   *
   * Returns 0 on success, or -ENOMEM if no memory could be allocated.
   **/
d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
274
  int __meminit firmware_map_add_hotplug(u64 start, u64 end, const char *type)
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
275
276
  {
  	struct firmware_map_entry *entry;
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
277
278
279
280
281
282
283
284
285
286
287
288
289
  	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));
  	}
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
290

d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
291
292
293
294
295
  	firmware_map_add_entry(start, end, type, entry);
  	/* create the memmap entry */
  	add_sysfs_fw_map_entry(entry);
  
  	return 0;
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
296
  }
31bad9246   Bernhard Walle   firmware/memmap: ...
297
298
299
  /**
   * firmware_map_add_early() - Adds a firmware mapping entry.
   * @start: Start of the memory range.
4ed940d4c   Yasuaki Ishimatsu   firmware_map: mak...
300
   * @end:   End of the memory range.
31bad9246   Bernhard Walle   firmware/memmap: ...
301
302
303
   * @type:  Type of the memory range.
   *
   * Adds a firmware mapping entry. This function uses the bootmem allocator
d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
304
   * for memory allocation.
31bad9246   Bernhard Walle   firmware/memmap: ...
305
306
307
308
309
   *
   * That function must be called before late_initcall.
   *
   * Returns 0 on success, or -ENOMEM if no memory could be allocated.
   **/
3b0fde0fa   Yinghai Lu   firmware_map: fix...
310
  int __init firmware_map_add_early(u64 start, u64 end, const char *type)
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
311
312
  {
  	struct firmware_map_entry *entry;
4fc0bc58c   Santosh Shilimkar   drivers/firmware/...
313
  	entry = memblock_virt_alloc(sizeof(struct firmware_map_entry), 0);
31bad9246   Bernhard Walle   firmware/memmap: ...
314
  	if (WARN_ON(!entry))
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
315
316
317
318
  		return -ENOMEM;
  
  	return firmware_map_add_entry(start, end, type, entry);
  }
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
  /**
   * 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;
  }
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
348
349
350
351
352
353
  /*
   * Sysfs functions -------------------------------------------------------------
   */
  
  static ssize_t start_show(struct firmware_map_entry *entry, char *buf)
  {
78681ac08   Randy Dunlap   firmware: fix mem...
354
355
356
  	return snprintf(buf, PAGE_SIZE, "0x%llx
  ",
  		(unsigned long long)entry->start);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
357
358
359
360
  }
  
  static ssize_t end_show(struct firmware_map_entry *entry, char *buf)
  {
78681ac08   Randy Dunlap   firmware: fix mem...
361
362
363
  	return snprintf(buf, PAGE_SIZE, "0x%llx
  ",
  		(unsigned long long)entry->end);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
364
365
366
367
368
369
370
  }
  
  static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
  {
  	return snprintf(buf, PAGE_SIZE, "%s
  ", entry->type);
  }
46c66c4b7   Yasuaki Ishimatsu   memory-hotplug: r...
371
372
373
374
  static inline struct memmap_attribute *to_memmap_attr(struct attribute *attr)
  {
  	return container_of(attr, struct memmap_attribute, attr);
  }
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
375
376
377
378
379
380
381
382
383
384
385
386
387
  
  static ssize_t memmap_attr_show(struct kobject *kobj,
  				struct attribute *attr, char *buf)
  {
  	struct firmware_map_entry *entry = to_memmap_entry(kobj);
  	struct memmap_attribute *memmap_attr = to_memmap_attr(attr);
  
  	return memmap_attr->show(entry, buf);
  }
  
  /*
   * Initialises stuff and adds the entries in the map_entries list to
   * sysfs. Important is that firmware_map_add() and firmware_map_add_early()
31bad9246   Bernhard Walle   firmware/memmap: ...
388
389
390
391
   * must be called before late_initcall. That's just because that function
   * is called as late_initcall() function, which means that if you call
   * firmware_map_add() or firmware_map_add_early() afterwards, the entries
   * are not added to sysfs.
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
392
   */
bac716966   Fengguang Wu   firmware/memmap: ...
393
  static int __init firmware_memmap_init(void)
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
394
  {
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
395
  	struct firmware_map_entry *entry;
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
396

d96ae5309   akpm@linux-foundation.org   memory-hotplug: c...
397
398
  	list_for_each_entry(entry, &map_entries, list)
  		add_sysfs_fw_map_entry(entry);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
399
400
401
  
  	return 0;
  }
bac716966   Fengguang Wu   firmware/memmap: ...
402
  late_initcall(firmware_memmap_init);
69ac9cd62   Bernhard Walle   sysfs: add /sys/f...
403