aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/boot/compressed/efi-stub.c
blob: e639c3b1b15443f4a06706d32374781cc22ffd07 (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
/*
 * linux/arch/arm/boot/compressed/efi-stub.c
 *
 * Copyright (C) 2013 Linaro Ltd;  <roy.franz@linaro.org>
 *
 * This file implements the EFI boot stub for the ARM kernel
 *
 * 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 <libfdt.h>
#include "efi-stub.h"

/* EFI function call wrappers.  These are not required for
 * ARM, but wrappers are required for X86 to convert between
 * ABIs.  These wrappers are provided to allow code sharing
 * between X86 and ARM.  Since these wrappers directly invoke the
 * EFI function pointer, the function pointer type must be properly
 * defined, which is not the case for X86  One advantage of this is
 * it allows for type checking of arguments, which is not
 * possible with the X86 wrappers.
 */
#define efi_call_phys0(f)			f()
#define efi_call_phys1(f, a1)			f(a1)
#define efi_call_phys2(f, a1, a2)		f(a1, a2)
#define efi_call_phys3(f, a1, a2, a3)		f(a1, a2, a3)
#define efi_call_phys4(f, a1, a2, a3, a4)	f(a1, a2, a3, a4)
#define efi_call_phys5(f, a1, a2, a3, a4, a5)	f(a1, a2, a3, a4, a5)

/* The maximum uncompressed kernel size is 32 MBytes, so we will reserve
 * that for the decompressed kernel.  We have no easy way to tell what
 * the actuall size of code + data the uncompressed kernel will use.
 */
#define MAX_UNCOMP_KERNEL_SIZE	0x02000000

/* The kernel zImage should be located between 32 Mbytes
 * and 128 MBytes from the base of DRAM.  The min
 * address leaves space for a maximal size uncompressed image,
 * and the max address is due to how the zImage decompressor
 * picks a destination address.
 */
#define ZIMAGE_OFFSET_LIMIT	0x08000000
#define MIN_ZIMAGE_OFFSET	MAX_UNCOMP_KERNEL_SIZE

#define PRINTK_PREFIX		"EFIstub: "

struct fdt_region {
	u64 base;
	u64 size;
};


/* Include shared EFI stub code */
#include "../../../../drivers/firmware/efi/efi-stub-helper.c"

static int relocate_kernel(efi_system_table_t *sys_table,
			   unsigned long *zimage_addr,
			   unsigned long zimage_size,
			   unsigned long min_addr, unsigned long max_addr)
{
	unsigned long cur_zimage_addr;
	unsigned long new_addr = 0;

	efi_status_t status;

	if (!zimage_addr || !zimage_size)
		return EFI_INVALID_PARAMETER;

	cur_zimage_addr = *zimage_addr;

	if (cur_zimage_addr > min_addr
	    && (cur_zimage_addr + zimage_size) < max_addr) {
		/* We don't need to do anything, as kernel is at an
		 * acceptable address already.
		 */
		return EFI_SUCCESS;
	}
	/*
	 * The EFI firmware loader could have placed the kernel image
	 * anywhere in memory, but the kernel has restrictions on the
	 * min and max physical address it can run at.
	 */
	status = efi_low_alloc(sys_table, zimage_size, 0,
			   &new_addr, min_addr);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
		return status;
	}

	if (new_addr > (max_addr - zimage_size)) {
		efi_free(sys_table, zimage_size, new_addr);
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to allocate usable memory for kernel.\n");
		return EFI_INVALID_PARAMETER;
	}

	/* We know source/dest won't overlap since both memory ranges
	 * have been allocated by UEFI, so we can safely use memcpy.
	 */
	memcpy((void *)new_addr, (void *)(unsigned long)cur_zimage_addr,
	       zimage_size);

	/* Return the load address  */
	*zimage_addr = new_addr;

	return status;
}


/* Convert the unicode UEFI command line to ASCII to pass to kernel.
 * Size of memory allocated return in *cmd_line_len.
 * Returns NULL on error.
 */
static char *convert_cmdline_to_ascii(efi_system_table_t *sys_table,
				      efi_loaded_image_t *image,
				      unsigned long *cmd_line_len,
				      u32 max_addr)
{
	u16 *s2;
	u8 *s1 = NULL;
	unsigned long cmdline_addr = 0;
	int load_options_size = image->load_options_size / 2; /* ASCII */
	void *options = image->load_options;
	int options_size = 0;
	int status;
	int i;
	u16 zero = 0;

	if (options) {
		s2 = options;
		while (*s2 && *s2 != '\n' && options_size < load_options_size) {
			s2++;
			options_size++;
		}
	}

	if (options_size == 0) {
		/* No command line options, so return empty string*/
		options_size = 1;
		options = &zero;
	}

	options_size++;  /* NUL termination */

	status = efi_high_alloc(sys_table, options_size, 0,
			    &cmdline_addr, max_addr);
	if (status != EFI_SUCCESS)
		return NULL;

	s1 = (u8 *)cmdline_addr;
	s2 = (u16 *)options;

	for (i = 0; i < options_size - 1; i++)
		*s1++ = *s2++;

	*s1 = '\0';

	*cmd_line_len = options_size;
	return (char *)cmdline_addr;
}


static int update_fdt(efi_system_table_t *sys_table, void *orig_fdt, void *fdt,
		      int new_fdt_size, char *cmdline_ptr, u64 initrd_addr,
		      u64 initrd_size, efi_memory_desc_t *memory_map,
		      int map_size, int desc_size)
{
	int node;
	int status;
	unsigned long fdt_val;

	status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
	if (status != 0)
		goto fdt_set_fail;

	node = fdt_subnode_offset(fdt, 0, "chosen");
	if (node < 0) {
		node = fdt_add_subnode(fdt, 0, "chosen");
		if (node < 0) {
			status = node; /* node is error code when negative */
			goto fdt_set_fail;
		}
	}

	if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
		status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
				     strlen(cmdline_ptr) + 1);
		if (status)
			goto fdt_set_fail;
	}

	/* Set intird address/end in device tree, if present */
	if (initrd_size != 0) {
		u64 initrd_image_end;
		u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
		status = fdt_setprop(fdt, node, "linux,initrd-start",
				     &initrd_image_start, sizeof(u64));
		if (status)
			goto fdt_set_fail;
		initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
		status = fdt_setprop(fdt, node, "linux,initrd-end",
				     &initrd_image_end, sizeof(u64));
		if (status)
			goto fdt_set_fail;
	}

	/* Add FDT entries for EFI runtime services in chosen node. */
	node = fdt_subnode_offset(fdt, 0, "chosen");
	fdt_val = cpu_to_fdt32((unsigned long)sys_table);
	status = fdt_setprop(fdt, node, "linux,efi-system-table",
			     &fdt_val, sizeof(fdt_val));
	if (status)
		goto fdt_set_fail;

	fdt_val = cpu_to_fdt32(desc_size);
	status = fdt_setprop(fdt, node, "linux,efi-mmap-desc-size",
			     &fdt_val, sizeof(fdt_val));
	if (status)
		goto fdt_set_fail;

	fdt_val = cpu_to_fdt32(map_size);
	status = fdt_setprop(fdt, node, "linux,efi-runtime-mmap-size",
			     &fdt_val, sizeof(fdt_val));
	if (status)
		goto fdt_set_fail;

        /* Stuff the whole memory map into FDT */
	status = fdt_setprop(fdt, node, "linux,efi-mmap",
			     memory_map, map_size);

	if (status)
		goto fdt_set_fail;

	return EFI_SUCCESS;

fdt_set_fail:
	if (status == -FDT_ERR_NOSPACE)
		return EFI_BUFFER_TOO_SMALL;

	return EFI_LOAD_ERROR;
}



int efi_entry(void *handle, efi_system_table_t *sys_table,
	      unsigned long *zimage_addr)
{
	efi_loaded_image_t *image;
	int status;
	unsigned long nr_pages;
	const struct fdt_region *region;

	void *fdt;
	int err;
	int node;
	unsigned long zimage_size = 0;
	unsigned long dram_base;
	/* addr/point and size pairs for memory management*/
	u64 initrd_addr;
	u64 initrd_size = 0;
	u64 fdt_addr;
	u64 fdt_size = 0;
	u64 kernel_reserve_addr;
	u64 kernel_reserve_size = 0;
	char *cmdline_ptr;
	unsigned long cmdline_size = 0;

	unsigned long map_size, desc_size;
	unsigned long mmap_key;
	efi_memory_desc_t *memory_map;

	unsigned long new_fdt_size;
	unsigned long new_fdt_addr;

	efi_guid_t proto = LOADED_IMAGE_PROTOCOL_GUID;

	/* Check if we were booted by the EFI firmware */
	if (sys_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
		goto fail;

	efi_printk(sys_table, PRINTK_PREFIX"Booting Linux using EFI stub.\n");


	/* get the command line from EFI, using the LOADED_IMAGE protocol */
	status = efi_call_phys3(sys_table->boottime->handle_protocol,
				handle, &proto, (void *)&image);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to get handle for LOADED_IMAGE_PROTOCOL\n");
		goto fail;
	}

	/* We are going to copy this into device tree, so we don't care where in
	 * memory it is.
	 */
	cmdline_ptr = convert_cmdline_to_ascii(sys_table, image,
					       &cmdline_size, 0xFFFFFFFF);
	if (!cmdline_ptr) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for command line.\n");
		goto fail;
	}

#if 1
	/* HACK, so we can boot from boot manager.  If we are passed a real
	 * command line, use it.
	 *
	 * TODO_RFRANZ - for debug only, not for real patch series
	 */
	if (!cmdline_ptr[0]) {
		cmdline_ptr = "initrd=initrd dtb=dtb console=ttyAMA0 earlyprintk=keep";
		efi_printk(sys_table, PRINTK_PREFIX"Booted from boot manager, forcing command line to: ");
		efi_printk(sys_table, cmdline_ptr);
		efi_printk(sys_table, "\n");
	}
#endif

	/* We first load the device tree, as we need to get the base address of
	 * DRAM from the device tree.  The zImage, device tree, and initrd
	 * have address restrictions that are relative to the base of DRAM.
	 */
	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "dtb=",
				      0xffffffff, &fdt_addr, &fdt_size);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load device tree blob.\n");
		goto fail_free_cmdline;
	}

	err = fdt_check_header((void *)(unsigned long)fdt_addr);
	if (err != 0) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Device tree header not valid\n");
		goto fail_free_fdt;
	}
	if (fdt_totalsize((void *)(unsigned long)fdt_addr) > fdt_size) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Incomplete device tree.\n");
		goto fail_free_fdt;

	}


        /* Look up the base of DRAM from the device tree. fdt_addr is
         * a 64 bit value (efi_phys_addr_t), but EFI guarantees that
         * all addresses are 32 bit for ARM32. */
	fdt = (void *)(u32)fdt_addr;
	node = fdt_subnode_offset(fdt, 0, "memory");
	region = fdt_getprop(fdt, node, "reg", NULL);
	if (region) {
		dram_base = fdt64_to_cpu(region->base);
	} else {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: No 'memory' node in device tree.\n");
		goto fail_free_fdt;
	}

	/* Reserve memory for the uncompressed kernel image. */
	kernel_reserve_addr = dram_base;
	kernel_reserve_size = MAX_UNCOMP_KERNEL_SIZE;
	nr_pages = round_up(kernel_reserve_size, EFI_PAGE_SIZE) / EFI_PAGE_SIZE;
	status = efi_call_phys4(sys_table->boottime->allocate_pages,
				EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
				nr_pages, &kernel_reserve_addr);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for uncompressed kernel.\n");
		goto fail_free_fdt;
	}

	/* Relocate the zImage, if required. */
	zimage_size = image->image_size;
	status = relocate_kernel(sys_table, zimage_addr, zimage_size,
				 dram_base + MIN_ZIMAGE_OFFSET,
				 dram_base + ZIMAGE_OFFSET_LIMIT);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Failed to relocate kernel\n");
		goto fail_free_kernel_reserve;
	}

	status = handle_cmdline_files(sys_table, image, cmdline_ptr, "initrd=",
				      dram_base + ZIMAGE_OFFSET_LIMIT,
				      &initrd_addr, &initrd_size);
	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to load initrd\n");
		goto fail_free_zimage;
	}

	/* Estimate size of new FDT, and allocate memory for it. We
	 * will allocate a bigger buffer if this ends up being too
	 * small, so a rough guess is OK here.*/
	new_fdt_size = fdt_size + cmdline_size + 0x200 + 0x1000;
	while (1) {
		status = efi_high_alloc(sys_table, new_fdt_size, 0,
					&new_fdt_addr,
					dram_base + ZIMAGE_OFFSET_LIMIT);
		if (status != EFI_SUCCESS) {
			efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to allocate memory for new device tree.\n");
			goto fail_free_initrd;
		}

		/* Now that we have done our final memory allocation (and free)
		 * we can get the memory map key needed
		 * forexit_boot_services.*/
		status = efi_get_memory_map(sys_table, &memory_map, &map_size,
					    &desc_size, &mmap_key);
		if (status != EFI_SUCCESS)
			goto fail_free_new_fdt;

		status = update_fdt(sys_table,
				    fdt, (void *)new_fdt_addr, new_fdt_size,
				    cmdline_ptr,
				    initrd_addr, initrd_size,
				    memory_map, map_size, desc_size);

		/* Succeeding the first time is the expected case. */
		if (status == EFI_SUCCESS)
			break;

		if (status == EFI_BUFFER_TOO_SMALL) {
			/* We need to allocate more space for the new
			 * device tree, so free existing buffer that is
			 * too small.  Also free memory map, as we will need
			 * to get new one that reflects the free/alloc we do
			 * on the device tree buffer. */
			efi_free(sys_table, new_fdt_size, new_fdt_addr);
			efi_call_phys1(sys_table->boottime->free_pool,
				       memory_map);
			new_fdt_size += new_fdt_size/4;
		}
		else
		{
			efi_printk(sys_table, PRINTK_PREFIX"ERROR: Unable to constuct new device tree.\n");
			goto fail_free_mmap;
		}
	}

	/* Now we are ready to exit_boot_services.*/
	status = efi_call_phys2(sys_table->boottime->exit_boot_services,
				handle, mmap_key);

	if (status != EFI_SUCCESS) {
		efi_printk(sys_table, PRINTK_PREFIX"ERROR: Exit boot services failed.\n");
		goto fail_free_mmap;
	}


	/* Now we need to return the FDT address to the calling
	 * assembly to this can be used as part of normal boot.
	 */
	return new_fdt_addr;

fail_free_mmap:
	efi_call_phys1(sys_table->boottime->free_pool, memory_map);

fail_free_new_fdt:
	efi_free(sys_table, new_fdt_size, new_fdt_addr);

fail_free_initrd:
	efi_free(sys_table, initrd_size, initrd_addr);

fail_free_zimage:
	efi_free(sys_table, zimage_size, *zimage_addr);

fail_free_kernel_reserve:
	efi_free(sys_table, kernel_reserve_size, kernel_reserve_addr);

fail_free_fdt:
	efi_free(sys_table, fdt_size, fdt_addr);

fail_free_cmdline:
	efi_free(sys_table, cmdline_size, (u32)cmdline_ptr);

fail:
	return EFI_STUB_ERROR;
}