summaryrefslogtreecommitdiff
path: root/lib/chromeos/load_kernel_helper.c
blob: 78c6ba285995b9a8d2f83408d49d2dc0d5d84cb6 (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
/*
 * Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 */

#include <common.h>
#include <chromeos/common.h>
#include <chromeos/crossystem_data.h>
#include <chromeos/load_kernel_helper.h>
#include <chromeos/preboot_fdt_update.h>
#include <chromeos/os_storage.h>
#include <chromeos/vboot_nvstorage_helper.h>

#include <load_kernel_fw.h>

#define PREFIX "load_kernel_helper: "

/* defined in common/cmd_bootm.c */
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);

/**
 * This is a wrapper of LoadKernel, which verifies the kernel image specified
 * by set_bootdev. The caller of this functions must have called set_bootdev
 * first.
 *
 * @param oss is the OS storage interface, for reading from the boot disk
 * @param boot_flags are bitwise-or'ed of flags in load_kernel_fw.h
 * @param gbb_data points to a GBB blob
 * @param gbb_size is the size of the GBB blob
 * @param vbshared_data points to VbSharedData blob
 * @param vbshared_size is the size of the VbSharedData blob
 * @param nvcxt points to a VbNvContext object
 * @return LoadKernel's return value
 */
static int load_kernel_wrapper(struct os_storage *oss,
		LoadKernelParams *params, uint64_t boot_flags,
		void *gbb_data, uint32_t gbb_size,
		void *vbshared_data, uint32_t vbshared_size,
		VbNvContext *nvcxt)
{
	int status = LOAD_KERNEL_NOT_FOUND;

	memset(params, '\0', sizeof(*params));

	params->boot_flags = boot_flags;

	params->gbb_data = gbb_data;
	params->gbb_size = gbb_size;

	params->shared_data_blob = vbshared_data;
	params->shared_data_size = vbshared_size;

	params->bytes_per_lba = os_storage_get_bytes_per_lba(oss);
	params->ending_lba = os_storage_get_ending_lba(oss);

	params->kernel_buffer = (void*)CONFIG_CHROMEOS_KERNEL_LOADADDR;
	params->kernel_buffer_size = CONFIG_CHROMEOS_KERNEL_BUFSIZE;

	params->nv_context = nvcxt;

	VBDEBUG(PREFIX "call LoadKernel() with parameters...\n");
	VBDEBUG(PREFIX "shared_data_blob:     0x%p\n",
			params->shared_data_blob);
	VBDEBUG(PREFIX "bytes_per_lba:        %d\n",
			(int) params->bytes_per_lba);
	VBDEBUG(PREFIX "ending_lba:           0x%08x\n",
			(int) params->ending_lba);
	VBDEBUG(PREFIX "kernel_buffer:        0x%p\n",
			params->kernel_buffer);
	VBDEBUG(PREFIX "kernel_buffer_size:   0x%08x\n",
			(int) params->kernel_buffer_size);
	VBDEBUG(PREFIX "boot_flags:           0x%08x\n",
			(int) params->boot_flags);

	status = LoadKernel(params);

	VBDEBUG(PREFIX "LoadKernel status: %d\n", status);
#ifdef VBOOT_DEBUG
	if (status == LOAD_KERNEL_SUCCESS) {
		VBDEBUG(PREFIX "partition_number:   0x%08x\n",
				(int) params->partition_number);
		VBDEBUG(PREFIX "bootloader_address: 0x%08x\n",
				(int) params->bootloader_address);
		VBDEBUG(PREFIX "bootloader_size:    0x%08x\n",
				(int) params->bootloader_size);
	}
#endif /* VBOOT_DEBUG */

	return status;
}

/* Maximum kernel command-line size */
#define CROS_CONFIG_SIZE 4096

/* Size of the x86 zeropage table */
#define CROS_PARAMS_SIZE 4096

/**
 * This loads kernel command line from the buffer that holds the loaded kernel
 * image. This function calculates the address of the command line from the
 * bootloader address.
 *
 * @param bootloader_address is the address of the bootloader in the buffer
 * @return kernel config address
 */
static char *get_kernel_config(char *bootloader_address)
{
	/* Use the bootloader address to find the kernel config location. */
	return bootloader_address - CROS_PARAMS_SIZE - CROS_CONFIG_SIZE;
}

/* assert(0 <= val && val < 99); sprintf(dst, "%u", val); */
static char *itoa(char *dst, int val)
{
	if (val > 9)
		*dst++ = '0' + val / 10;
	*dst++ = '0' + val % 10;
	return dst;
}

/* copied from x86 bootstub code; sprintf(dst, "%02x", val) */
static void one_byte(char *dst, uint8_t val)
{
	dst[0] = "0123456789abcdef"[(val >> 4) & 0x0F];
	dst[1] = "0123456789abcdef"[val & 0x0F];
}

/* copied from x86 bootstub code; display a GUID in canonical form */
static char *emit_guid(char *dst, uint8_t *guid)
{
	one_byte(dst, guid[3]); dst += 2;
	one_byte(dst, guid[2]); dst += 2;
	one_byte(dst, guid[1]); dst += 2;
	one_byte(dst, guid[0]); dst += 2;
	*dst++ = '-';
	one_byte(dst, guid[5]); dst += 2;
	one_byte(dst, guid[4]); dst += 2;
	*dst++ = '-';
	one_byte(dst, guid[7]); dst += 2;
	one_byte(dst, guid[6]); dst += 2;
	*dst++ = '-';
	one_byte(dst, guid[8]); dst += 2;
	one_byte(dst, guid[9]); dst += 2;
	*dst++ = '-';
	one_byte(dst, guid[10]); dst += 2;
	one_byte(dst, guid[11]); dst += 2;
	one_byte(dst, guid[12]); dst += 2;
	one_byte(dst, guid[13]); dst += 2;
	one_byte(dst, guid[14]); dst += 2;
	one_byte(dst, guid[15]); dst += 2;
	return dst;
}

/**
 * This replaces:
 *   %D -> device number
 *   %P -> partition number
 *   %U -> GUID
 * in kernel command line.
 *
 * For example:
 *   ("root=/dev/sd%D%P", 2, 3)      -> "root=/dev/sdc3"
 *   ("root=/dev/mmcblk%Dp%P", 0, 5) -> "root=/dev/mmcblk0p5".
 *
 * @param src - input string
 * @param devnum - device number of the storage device we will mount
 * @param partnum - partition number of the root file system we will mount
 * @param guid - guid of the kernel partition
 * @param dst - output string; a copy of [src] with special characters replaced
 */
static void update_cmdline(char *src, int devnum, int partnum, uint8_t *guid,
		char *dst)
{
	int c;

	// sanity check on inputs
	if (devnum < 0 || devnum > 25 || partnum < 1 || partnum > 99) {
		VBDEBUG(PREFIX "insane input: %d, %d\n", devnum, partnum);
		devnum = 0;
		partnum = 3;
	}

	while ((c = *src++)) {
		if (c != '%') {
			*dst++ = c;
			continue;
		}

		switch ((c = *src++)) {
		case '\0':
			/* input ends in '%'; is it not well-formed? */
			src--;
			break;
		case 'D':
			/*
			 * TODO: Do we have any better way to know whether %D
			 * is replaced by a letter or digits? So far, this is
			 * done by a rule of thumb that if %D is followed by a
			 * 'p' character, then it is replaced by digits.
			 */
			if (*src == 'p')
				dst = itoa(dst, devnum);
			else
				*dst++ = 'a' + devnum;
			break;
		case 'P':
			dst = itoa(dst, devnum);
			break;
		case 'U':
			dst = emit_guid(dst, guid);
			break;
		default:
			*dst++ = '%';
			*dst++ = c;
			break;
		}
	}

	*dst = '\0';
}

/**
 * This sets up bootargs environment variable.
 *
 * @param oss is the OS storage interface, for reading from the boot disk
 * @param params that specifies where to boot from
 * @return LOAD_KERNEL_INVALID if it fails to boot; otherwise it never returns
 *         to its caller
 */
static int setup_kernel_command_line(struct os_storage *oss,
		LoadKernelParams *params)
{
	enum { EXTRA_BUFFER = 4096 };

	char cmdline_buf[CROS_CONFIG_SIZE + EXTRA_BUFFER];
	char cmdline_out[CROS_CONFIG_SIZE + EXTRA_BUFFER];
	char *cmdline;

	/*
	 * casting bootloader_address of uint64_t type to uint32_t before
	 * further casting it to char * to avoid compiler warning
	 * "cast to pointer from integer of different size"
	 */
	cmdline = get_kernel_config((char *)
			(uint32_t)params->bootloader_address);
	strncpy(cmdline_buf, cmdline, CROS_CONFIG_SIZE);

	/* if we have init bootargs, append it */
	if ((cmdline = getenv("bootargs"))) {
		strncat(cmdline_buf, " ", EXTRA_BUFFER);
		strncat(cmdline_buf, cmdline, EXTRA_BUFFER - 1);
	}

	VBDEBUG(PREFIX "cmdline before update: %s\n", cmdline_buf);
	update_cmdline(cmdline_buf, os_storage_get_device_number(oss),
			params->partition_number + 1, params->partition_guid,
			cmdline_out);

	setenv("bootargs", cmdline_out);
	VBDEBUG(PREFIX "cmdline after update:  %s\n", getenv("bootargs"));

	return 0;
}

/**
 * This boots kernel specified in [parmas].
 *
 * @param oss		OS storage interface
 * @param params	Specifies where to boot from
 * @param cdata		kernel shared data pointer
 * @return LOAD_KERNEL_INVALID if it fails to boot; otherwise it never returns
 *         to its caller
 */
static int boot_kernel_helper(struct os_storage *oss, LoadKernelParams *params,
		crossystem_data_t *cdata)
{
	char load_address[32];
	char *argv[2] = { "bootm", load_address };

	VBDEBUG(PREFIX "boot_kernel\n");
	VBDEBUG(PREFIX "kernel_buffer:      0x%p\n",
			params->kernel_buffer);
	VBDEBUG(PREFIX "bootloader_address: 0x%08x\n",
			(int) params->bootloader_address);

	if (setup_kernel_command_line(oss, params)) {
		VBDEBUG(PREFIX "failed to set up kernel command-line\n");
		return LOAD_KERNEL_INVALID;
	}

	set_crossystem_data(cdata);

	sprintf(load_address, "0x%p", params->kernel_buffer);
	do_bootm(NULL, 0, sizeof(argv)/sizeof(*argv), argv);

	VBDEBUG(PREFIX "failed to boot; is kernel broken?\n");
	return LOAD_KERNEL_INVALID;
}

int boot_kernel(struct os_storage *oss, uint64_t boot_flags,
		void *gbb_data, uint32_t gbb_size,
		void *vbshared_data, uint32_t vbshared_size,
		VbNvContext *nvcxt, crossystem_data_t *cdata)
{
	LoadKernelParams params;
	int status;

	status = load_kernel_wrapper(oss, &params, boot_flags, gbb_data,
			gbb_size, vbshared_data, vbshared_size, nvcxt);

	VBDEBUG(PREFIX "load_kernel_wrapper returns %d\n", status);

	/*
	 * Failure of tearing down or writing back VbNvContext is non-fatal. So
	 * we just complain about it, but don't return error code for it.
	 */
	if (VbNvTeardown(nvcxt))
		VBDEBUG(PREFIX "fail to tear down VbNvContext\n");
	else if (nvcxt->raw_changed && write_nvcontext(nvcxt))
		VBDEBUG(PREFIX "fail to write back nvcontext\n");

	if (status == LOAD_KERNEL_SUCCESS)
		return boot_kernel_helper(oss, &params, cdata);

	return status;
}