summaryrefslogtreecommitdiff
path: root/kexec/arch/ppc/kexec-uImage-ppc.c
blob: 5eec6e418a586a1bb4a48c6b5d903334fe045f50 (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
/*
 * uImage support for PowerPC
 */
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <image.h>
#include <getopt.h>
#include <arch/options.h>
#include "../../kexec.h"
#include "../../kexec-syscall.h"
#include "kexec-ppc.h"
#include "fixup_dtb.h"
#include <kexec-uImage.h>
#include "crashdump-powerpc.h"
#include <limits.h>

int create_flatten_tree(struct kexec_info *, unsigned char **, unsigned long *,
			char *);

/* See options.h -- add any more there, too. */
static const struct option options[] = {
	KEXEC_ARCH_OPTIONS
	{"command-line",	1, 0, OPT_APPEND},
	{"append",	1, 0, OPT_APPEND},
	{"ramdisk",	1, 0, OPT_RAMDISK},
	{"initrd",	1, 0, OPT_RAMDISK},
	{"dtb",		1, 0, OPT_DTB},
	{"reuse-node",	1, 0, OPT_NODES},
	{0, 0, 0, 0},
};
static const char short_options[] = KEXEC_ARCH_OPT_STR;

void uImage_ppc_usage(void)
{
	printf(
			"    --command-line=STRING Set the kernel command line to STRING.\n"
			"    --append=STRING       Set the kernel command line to STRING.\n"
			"    --ramdisk=<filename>  Initial RAM disk.\n"
			"    --initrd=<filename>   same as --ramdisk\n"
			"    --dtb=<filename>      Specify device tree blob file.\n"
			"    --reuse-node=node     Specify nodes which should be taken from /proc/device-tree.\n"
			"                          Can be set multiple times.\n"
	);
}

/*
 * Load the ramdisk into buffer.
 *  If the supplied image is in uImage format use
 *  uImage_load() to read the payload from the image.
 */
char *slurp_ramdisk_ppc(const char *filename, off_t *r_size)
{
	struct Image_info img;
	off_t size;
	const char *buf = slurp_file(filename, &size);
	int rc;

	/* Check if this is a uImage RAMDisk */
	if (!buf)
		return buf;
	rc = uImage_probe_ramdisk(buf, size, IH_ARCH_PPC); 
	if (rc < 0)
		die("uImage: Corrupted ramdisk file %s\n", filename);
	else if (rc == 0) {
		if (uImage_load(buf, size, &img) != 0)
			die("uImage: Reading %ld bytes from %s failed\n",
				size, filename);
		buf = img.buf;
		size = img.len;
	}

	*r_size = size;
	return buf;
}
	
int uImage_ppc_probe(const char *buf, off_t len)
{
	return uImage_probe_kernel(buf, len, IH_ARCH_PPC);
}

static int ppc_load_bare_bits(int argc, char **argv, const char *buf,
		off_t len, struct kexec_info *info, unsigned int load_addr,
		unsigned int ep)
{
	char *command_line, *cmdline_buf, *crash_cmdline;
	char *tmp_cmdline;
	int command_line_len, crash_cmdline_len;
	char *dtb;
	unsigned int addr;
	unsigned long dtb_addr;
	unsigned long dtb_addr_actual;
#define FIXUP_ENTRYS    (20)
	char *fixup_nodes[FIXUP_ENTRYS + 1];
	int cur_fixup = 0;
	int opt;
	int ret = 0;
	char *seg_buf = NULL;
	off_t seg_size = 0;
	unsigned long long hole_addr;
	unsigned long max_addr;
	char *blob_buf = NULL;
	off_t blob_size = 0;
	char *error_msg = NULL;

	cmdline_buf = NULL;
	command_line = NULL;
	tmp_cmdline = NULL;
	dtb = NULL;
	max_addr = LONG_MAX;

	while ((opt = getopt_long(argc, argv, short_options, options, 0)) != -1) {
		switch (opt) {
		default:
			/* Ignore core options */
			if (opt < OPT_ARCH_MAX) {
				break;
			}
		case OPT_APPEND:
			tmp_cmdline = optarg;
			break;

		case OPT_RAMDISK:
			ramdisk = optarg;
			break;

		case OPT_DTB:
			dtb = optarg;
			break;

		case OPT_NODES:
			if (cur_fixup >= FIXUP_ENTRYS) {
				die("The number of entries for the fixup is too large\n");
			}
			fixup_nodes[cur_fixup] = optarg;
			cur_fixup++;
			break;
		}
	}

	if (ramdisk && reuse_initrd)
		die("Can't specify --ramdisk or --initrd with --reuseinitrd\n");

	command_line_len = 0;
	if (tmp_cmdline) {
		command_line = tmp_cmdline;
	} else {
		command_line = get_command_line();
	}
	command_line_len = strlen(command_line) + 1;

	fixup_nodes[cur_fixup] = NULL;

	/*
	 * len contains the length of the whole kernel image except the bss
	 * section. The 1 MiB should cover it. The purgatory and the dtb are
	 * allocated from memtop down towards zero so we should never get too
	 * close to the bss :)
	 */
#define _1MiB	(1 * 1024 * 1024)

	/*
	 * If the provided load_addr cannot be allocated, find a new
	 * area. Rebase the entry point based on the new load_addr.
	 */
	if (!valid_memory_range(info, load_addr, load_addr + (len + _1MiB))) {
		int ep_offset = ep - load_addr;

		load_addr = locate_hole(info, len + _1MiB, 0, 0, max_addr, 1);
		if (load_addr == ULONG_MAX) {
			printf("Can't allocate memory for kernel of len %ld\n",
					len + _1MiB);
			return -1;
		}

		ep = load_addr + ep_offset;
	}

	add_segment(info, buf, len, load_addr, len + _1MiB);


	if (info->kexec_flags & KEXEC_ON_CRASH) {
                crash_cmdline = xmalloc(COMMAND_LINE_SIZE);
                memset((void *)crash_cmdline, 0, COMMAND_LINE_SIZE);
		ret = load_crashdump_segments(info, crash_cmdline,
						max_addr, 0);
		if (ret < 0) {
			ret = -1;
			goto out;
		}
		crash_cmdline_len = strlen(crash_cmdline);
	} else {
		crash_cmdline = NULL;
		crash_cmdline_len = 0;
	}

	if (crash_cmdline_len + command_line_len + 1 > COMMAND_LINE_SIZE) {
		printf("Kernel command line exceeds maximum possible length\n");
		return -1;
	}

	cmdline_buf = xmalloc(COMMAND_LINE_SIZE);
	memset((void *)cmdline_buf, 0, COMMAND_LINE_SIZE);

	if (command_line)
		strcpy(cmdline_buf, command_line);
	if (crash_cmdline)
		strncat(cmdline_buf, crash_cmdline, crash_cmdline_len);

	elf_rel_build_load(info, &info->rhdr, (const char *)purgatory,
				purgatory_size, 0, -1, -1, 0);

	/* Here we need to initialize the device tree, and find out where
	 * it is going to live so we can place it directly after the
	 * kernel image */
	if (dtb) {
		/* Grab device tree from buffer */
		blob_buf = slurp_file(dtb, &blob_size);
	} else {
		create_flatten_tree(info, (unsigned char **)&blob_buf,
				(unsigned long *)&blob_size, cmdline_buf);
	}
	if (!blob_buf || !blob_size) {
		error_msg = "Device tree seems to be an empty file.\n";
		goto out2;
	}

	/* initial fixup for device tree */
	blob_buf = fixup_dtb_init(info, blob_buf, &blob_size, load_addr, &dtb_addr);

	if (ramdisk) {
		seg_buf = slurp_ramdisk_ppc(ramdisk, &seg_size);
		/* Load ramdisk at top of memory */
		hole_addr = add_buffer(info, seg_buf, seg_size, seg_size,
				0, dtb_addr + blob_size, max_addr, -1);
		ramdisk_base = hole_addr;
		ramdisk_size = seg_size;
	}
	if (reuse_initrd) {
		ramdisk_base = initrd_base;
		ramdisk_size = initrd_size;
	}

	if (info->kexec_flags & KEXEC_ON_CRASH && ramdisk_base != 0) {
		if ( (ramdisk_base < crash_base) ||
		     (ramdisk_base > crash_base + crash_size) ) {
			printf("WARNING: ramdisk is above crashkernel region!\n");
		}
		else if (ramdisk_base + ramdisk_size > crash_base + crash_size) {
			printf("WARNING: ramdisk overflows crashkernel region!\n");
		}
	}

	/* Perform final fixup on devie tree, i.e. everything beside what
	 * was done above */
	fixup_dtb_finalize(info, blob_buf, &blob_size, fixup_nodes,
			cmdline_buf);
	dtb_addr_actual = add_buffer(info, blob_buf, blob_size, blob_size, 0, dtb_addr,
			load_addr + KERNEL_ACCESS_TOP, 1);
	if (dtb_addr_actual != dtb_addr) {
		printf("dtb_addr_actual: %lx, dtb_addr: %lx\n", dtb_addr_actual, dtb_addr);
		error_msg = "Error device tree not loadded to address it was expecting to be loaded too!\n";
		goto out2;
	}

	/* set various variables for the purgatory */
	addr = ep;
	elf_rel_set_symbol(&info->rhdr, "kernel", &addr, sizeof(addr));

	addr = dtb_addr;
	elf_rel_set_symbol(&info->rhdr, "dt_offset", &addr, sizeof(addr));

#define PUL_STACK_SIZE  (16 * 1024)
	addr = locate_hole(info, PUL_STACK_SIZE, 0, 0, -1, 1);
	addr += PUL_STACK_SIZE;
	elf_rel_set_symbol(&info->rhdr, "stack", &addr, sizeof(addr));
	/* No allocation past here in order not to overwrite the stack */
#undef PUL_STACK_SIZE

	/*
	 * Fixup ThreadPointer(r2) for purgatory.
	 * PPC32 ELF ABI expects :
	 * ThreadPointer (TP) = TCB + 0x7000
	 * We manually allocate a TCB space and set the TP
	 * accordingly.
	 */
#define TCB_SIZE 	1024
#define TCB_TP_OFFSET 	0x7000	/* PPC32 ELF ABI */
	addr = locate_hole(info, TCB_SIZE, 0, 0,
				((unsigned long)-1 - TCB_TP_OFFSET),
				1);
	addr += TCB_SIZE + TCB_TP_OFFSET;
	elf_rel_set_symbol(&info->rhdr, "my_thread_ptr", &addr, sizeof(addr));
#undef TCB_TP_OFFSET
#undef TCB_SIZE

	addr = elf_rel_get_addr(&info->rhdr, "purgatory_start");
	info->entry = (void *)addr;

out2:
	free(cmdline_buf);
out:
	free(crash_cmdline);
	if (!tmp_cmdline)
		free(command_line);
	if (error_msg)
		die(error_msg);
	return ret;
}

int uImage_ppc_load(int argc, char **argv, const char *buf, off_t len,
		struct kexec_info *info)
{
	struct Image_info img;
	int ret;

	ret = uImage_load(buf, len, &img);
	if (ret)
		return ret;

	return	ppc_load_bare_bits(argc, argv, img.buf, img.len, info,
				img.base, img.ep);
}