summaryrefslogtreecommitdiffstats
path: root/arch/arm64/kernel/efi.c
blob: 95c49ebc660dd85864981bfdfa13bc1f5066d76f (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
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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
/*
 * Extensible Firmware Interface
 *
 * Based on Extensible Firmware Interface Specification version 2.4
 *
 * Copyright (C) 2013, 2014 Linaro Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/efi.h>
#include <linux/export.h>
#include <linux/memblock.h>
#include <linux/bootmem.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/sched.h>
#include <linux/slab.h>

#include <asm/cacheflush.h>
#include <asm/efi.h>
#include <asm/tlbflush.h>
#include <asm/mmu_context.h>

struct efi_memory_map memmap;

static efi_runtime_services_t *runtime;

static u64 efi_system_table;

static int uefi_debug __initdata;
static int __init uefi_debug_setup(char *str)
{
	uefi_debug = 1;

	return 0;
}
early_param("uefi_debug", uefi_debug_setup);

static int __init is_normal_ram(efi_memory_desc_t *md)
{
	if (md->attribute & EFI_MEMORY_WB)
		return 1;
	return 0;
}

static void __init efi_setup_idmap(void)
{
	struct memblock_region *r;
	efi_memory_desc_t *md;
	u64 paddr, npages, size;

	for_each_memblock(memory, r)
		create_id_mapping(r->base, r->size, 0);

	/* map runtime io spaces */
	for_each_efi_memory_desc(&memmap, md) {
		if (!(md->attribute & EFI_MEMORY_RUNTIME) || is_normal_ram(md))
			continue;
		paddr = md->phys_addr;
		npages = md->num_pages;
		memrange_efi_to_native(&paddr, &npages);
		size = npages << PAGE_SHIFT;
		create_id_mapping(paddr, size, 1);
	}
}

static int __init uefi_init(void)
{
	efi_char16_t *c16;
	char vendor[100] = "unknown";
	int i, retval;

	efi.systab = early_memremap(efi_system_table,
				    sizeof(efi_system_table_t));
	if (efi.systab == NULL) {
		pr_warn("Unable to map EFI system table.\n");
		return -ENOMEM;
	}

	set_bit(EFI_BOOT, &efi.flags);
	set_bit(EFI_64BIT, &efi.flags);

	/*
	 * Verify the EFI Table
	 */
	if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) {
		pr_err("System table signature incorrect\n");
		retval = -EINVAL;
		goto out;
	}
	if ((efi.systab->hdr.revision >> 16) < 2)
		pr_warn("Warning: EFI system table version %d.%02d, expected 2.00 or greater\n",
			efi.systab->hdr.revision >> 16,
			efi.systab->hdr.revision & 0xffff);

	/* Show what we know for posterity */
	c16 = early_memremap(efi.systab->fw_vendor,
			     sizeof(vendor));
	if (c16) {
		for (i = 0; i < (int) sizeof(vendor) - 1 && *c16; ++i)
			vendor[i] = c16[i];
		vendor[i] = '\0';
		early_memunmap(c16, sizeof(vendor));
	}

	pr_info("EFI v%u.%.02u by %s\n",
		efi.systab->hdr.revision >> 16,
		efi.systab->hdr.revision & 0xffff, vendor);

	retval = efi_config_init(NULL);
	if (retval == 0)
		set_bit(EFI_CONFIG_TABLES, &efi.flags);

out:
	early_memunmap(efi.systab,  sizeof(efi_system_table_t));
	return retval;
}

/*
 * Return true for RAM regions we want to permanently reserve.
 */
static __init int is_reserve_region(efi_memory_desc_t *md)
{
	if (!is_normal_ram(md))
		return 0;

	if (md->attribute & EFI_MEMORY_RUNTIME)
		return 1;

	if (md->type == EFI_ACPI_RECLAIM_MEMORY ||
	    md->type == EFI_RESERVED_TYPE)
		return 1;

	return 0;
}

static __init void reserve_regions(void)
{
	efi_memory_desc_t *md;
	u64 paddr, npages, size;

	if (uefi_debug)
		pr_info("Processing EFI memory map:\n");

	for_each_efi_memory_desc(&memmap, md) {
		paddr = md->phys_addr;
		npages = md->num_pages;

		if (uefi_debug) {
			char buf[64];

			pr_info("  0x%012llx-0x%012llx %s",
				paddr, paddr + (npages << EFI_PAGE_SHIFT) - 1,
				efi_md_typeattr_format(buf, sizeof(buf), md));
		}

		memrange_efi_to_native(&paddr, &npages);
		size = npages << PAGE_SHIFT;

		if (is_normal_ram(md))
			early_init_dt_add_memory_arch(paddr, size);

		if (is_reserve_region(md) ||
		    md->type == EFI_BOOT_SERVICES_CODE ||
		    md->type == EFI_BOOT_SERVICES_DATA) {
			memblock_reserve(paddr, size);
			if (uefi_debug)
				pr_cont("*");
		}

		if (uefi_debug)
			pr_cont("\n");
	}

	set_bit(EFI_MEMMAP, &efi.flags);
}


static u64 __init free_one_region(u64 start, u64 end)
{
	u64 size = end - start;

	if (uefi_debug)
		pr_info("  EFI freeing: 0x%012llx-0x%012llx\n",	start, end - 1);

	free_bootmem_late(start, size);
	return size;
}

static u64 __init free_region(u64 start, u64 end)
{
	u64 map_start, map_end, total = 0;

	if (end <= start)
		return total;

	map_start = (u64)memmap.phys_map;
	map_end = PAGE_ALIGN(map_start + (memmap.map_end - memmap.map));
	map_start &= PAGE_MASK;

	if (start < map_end && end > map_start) {
		/* region overlaps UEFI memmap */
		if (start < map_start)
			total += free_one_region(start, map_start);

		if (map_end < end)
			total += free_one_region(map_end, end);
	} else
		total += free_one_region(start, end);

	return total;
}

static void __init free_boot_services(void)
{
	u64 total_freed = 0;
	u64 keep_end, free_start, free_end;
	efi_memory_desc_t *md;

	/*
	 * If kernel uses larger pages than UEFI, we have to be careful
	 * not to inadvertantly free memory we want to keep if there is
	 * overlap at the kernel page size alignment. We do not want to
	 * free is_reserve_region() memory nor the UEFI memmap itself.
	 *
	 * The memory map is sorted, so we keep track of the end of
	 * any previous region we want to keep, remember any region
	 * we want to free and defer freeing it until we encounter
	 * the next region we want to keep. This way, before freeing
	 * it, we can clip it as needed to avoid freeing memory we
	 * want to keep for UEFI.
	 */

	keep_end = 0;
	free_start = 0;

	for_each_efi_memory_desc(&memmap, md) {
		u64 paddr, npages, size;

		if (is_reserve_region(md)) {
			/*
			 * We don't want to free any memory from this region.
			 */
			if (free_start) {
				/* adjust free_end then free region */
				if (free_end > md->phys_addr)
					free_end -= PAGE_SIZE;
				total_freed += free_region(free_start, free_end);
				free_start = 0;
			}
			keep_end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT);
			continue;
		}

		if (md->type != EFI_BOOT_SERVICES_CODE &&
		    md->type != EFI_BOOT_SERVICES_DATA) {
			/* no need to free this region */
			continue;
		}

		/*
		 * We want to free memory from this region.
		 */
		paddr = md->phys_addr;
		npages = md->num_pages;
		memrange_efi_to_native(&paddr, &npages);
		size = npages << PAGE_SHIFT;

		if (free_start) {
			if (paddr <= free_end)
				free_end = paddr + size;
			else {
				total_freed += free_region(free_start, free_end);
				free_start = paddr;
				free_end = paddr + size;
			}
		} else {
			free_start = paddr;
			free_end = paddr + size;
		}
		if (free_start < keep_end) {
			free_start += PAGE_SIZE;
			if (free_start >= free_end)
				free_start = 0;
		}
	}
	if (free_start)
		total_freed += free_region(free_start, free_end);

	if (total_freed)
		pr_info("Freed 0x%llx bytes of EFI boot services memory",
			total_freed);
}

void __init efi_init(void)
{
	struct efi_fdt_params params;

	/* Grab UEFI information placed in FDT by stub */
	if (!efi_get_fdt_params(&params, uefi_debug))
		return;

	efi_system_table = params.system_table;

	memblock_reserve(params.mmap & PAGE_MASK,
			 PAGE_ALIGN(params.mmap_size + (params.mmap & ~PAGE_MASK)));
	memmap.phys_map = (void *)params.mmap;
	memmap.map = early_memremap(params.mmap, params.mmap_size);
	memmap.map_end = memmap.map + params.mmap_size;
	memmap.desc_size = params.desc_size;
	memmap.desc_version = params.desc_ver;

	if (uefi_init() < 0)
		return;

	reserve_regions();
}

void __init efi_idmap_init(void)
{
	if (!efi_enabled(EFI_BOOT))
		return;

	/* boot time idmap_pg_dir is incomplete, so fill in missing parts */
	efi_setup_idmap();
}

static int __init remap_region(efi_memory_desc_t *md, void **new)
{
	u64 paddr, vaddr, npages, size;

	paddr = md->phys_addr;
	npages = md->num_pages;
	memrange_efi_to_native(&paddr, &npages);
	size = npages << PAGE_SHIFT;

	if (is_normal_ram(md))
		vaddr = (__force u64)ioremap_cache(paddr, size);
	else
		vaddr = (__force u64)ioremap(paddr, size);

	if (!vaddr) {
		pr_err("Unable to remap 0x%llx pages @ %p\n",
		       npages, (void *)paddr);
		return 0;
	}

	/* adjust for any rounding when EFI and system pagesize differs */
	md->virt_addr = vaddr + (md->phys_addr - paddr);

	if (uefi_debug)
		pr_info("  EFI remap 0x%012llx => %p\n",
			md->phys_addr, (void *)md->virt_addr);

	memcpy(*new, md, memmap.desc_size);
	*new += memmap.desc_size;

	return 1;
}

/*
 * Switch UEFI from an identity map to a kernel virtual map
 */
static int __init arm64_enter_virtual_mode(void)
{
	efi_memory_desc_t *md;
	phys_addr_t virtmap_phys;
	void *virtmap, *virt_md;
	efi_status_t status;
	u64 mapsize;
	int count = 0;
	unsigned long flags;

	if (!efi_enabled(EFI_BOOT)) {
		pr_info("EFI services will not be available.\n");
		return -1;
	}

	mapsize = memmap.map_end - memmap.map;
	early_memunmap(memmap.map, mapsize);

	if (efi_runtime_disabled()) {
		pr_info("EFI runtime services will be disabled.\n");
		return -1;
	}

	pr_info("Remapping and enabling EFI services.\n");
	/* replace early memmap mapping with permanent mapping */
	memmap.map = (__force void *)ioremap_cache((phys_addr_t)memmap.phys_map,
						   mapsize);
	memmap.map_end = memmap.map + mapsize;

	efi.memmap = &memmap;

	/* Map the runtime regions */
	virtmap = kmalloc(mapsize, GFP_KERNEL);
	if (!virtmap) {
		pr_err("Failed to allocate EFI virtual memmap\n");
		return -1;
	}
	virtmap_phys = virt_to_phys(virtmap);
	virt_md = virtmap;

	for_each_efi_memory_desc(&memmap, md) {
		if (!(md->attribute & EFI_MEMORY_RUNTIME))
			continue;
		if (!remap_region(md, &virt_md))
			goto err_unmap;
		++count;
	}

	efi.systab = (__force void *)efi_lookup_mapped_addr(efi_system_table);
	if (!efi.systab) {
		/*
		 * If we have no virtual mapping for the System Table at this
		 * point, the memory map doesn't cover the physical offset where
		 * it resides. This means the System Table will be inaccessible
		 * to Runtime Services themselves once the virtual mapping is
		 * installed.
		 */
		pr_err("Failed to remap EFI System Table -- buggy firmware?\n");
		goto err_unmap;
	}
	set_bit(EFI_SYSTEM_TABLES, &efi.flags);

	local_irq_save(flags);
	cpu_switch_mm(idmap_pg_dir, &init_mm);

	/* Call SetVirtualAddressMap with the physical address of the map */
	runtime = efi.systab->runtime;
	efi.set_virtual_address_map = runtime->set_virtual_address_map;

	status = efi.set_virtual_address_map(count * memmap.desc_size,
					     memmap.desc_size,
					     memmap.desc_version,
					     (efi_memory_desc_t *)virtmap_phys);
	cpu_set_reserved_ttbr0();
	flush_tlb_all();
	local_irq_restore(flags);

	kfree(virtmap);

	free_boot_services();

	if (status != EFI_SUCCESS) {
		pr_err("Failed to set EFI virtual address map! [%lx]\n",
			status);
		return -1;
	}

	/* Set up runtime services function pointers */
	runtime = efi.systab->runtime;
	efi_native_runtime_setup();
	set_bit(EFI_RUNTIME_SERVICES, &efi.flags);

	efi.runtime_version = efi.systab->hdr.revision;

	return 0;

err_unmap:
	/* unmap all mappings that succeeded: there are 'count' of those */
	for (virt_md = virtmap; count--; virt_md += memmap.desc_size) {
		md = virt_md;
		iounmap((__force void __iomem *)md->virt_addr);
	}
	kfree(virtmap);
	return -1;
}
early_initcall(arm64_enter_virtual_mode);
OpenPOWER on IntegriCloud