]>
Commit | Line | Data |
---|---|---|
c88d67d0 SG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Bootmethod for ChromiumOS | |
4 | * | |
5 | * Copyright 2023 Google LLC | |
6 | * Written by Simon Glass <[email protected]> | |
7 | */ | |
8 | ||
9 | #define LOG_CATEGORY UCLASS_BOOTSTD | |
10 | ||
11 | #include <common.h> | |
12 | #include <blk.h> | |
13 | #include <bootdev.h> | |
14 | #include <bootflow.h> | |
1a081092 | 15 | #include <bootm.h> |
c88d67d0 | 16 | #include <bootmeth.h> |
4cfe4510 | 17 | #include <display_options.h> |
c88d67d0 | 18 | #include <dm.h> |
71f634b8 | 19 | #include <efi.h> |
c88d67d0 SG |
20 | #include <malloc.h> |
21 | #include <mapmem.h> | |
22 | #include <part.h> | |
c88d67d0 | 23 | #include <linux/sizes.h> |
de30aa9a | 24 | #include "bootmeth_cros.h" |
c88d67d0 | 25 | |
71f634b8 SG |
26 | static const efi_guid_t cros_kern_type = PARTITION_CROS_KERNEL; |
27 | ||
b7ed5386 SG |
28 | /* |
29 | * Layout of the ChromeOS kernel | |
30 | * | |
71f634b8 | 31 | * Partitions 2 and 4 contain kernels with type GUID_CROS_KERNEL |
b7ed5386 SG |
32 | * |
33 | * Contents are: | |
34 | * | |
35 | * Offset Contents | |
36 | * 0 struct vb2_keyblock | |
37 | * m struct vb2_kernel_preamble | |
38 | * m + n kernel buffer | |
39 | * | |
40 | * m is keyblock->keyblock_size | |
41 | * n is preamble->preamble_size | |
42 | * | |
43 | * The kernel buffer itself consists of various parts: | |
44 | * | |
45 | * Offset Contents | |
46 | * m + n kernel image (Flat vmlinux binary or FIT) | |
47 | * b - 8KB Command line text | |
48 | * b - 4KB X86 setup block (struct boot_params, extends for about 16KB) | |
49 | * b X86 bootloader (continuation of setup block) | |
50 | * b + 16KB X86 setup block (copy, used for hold data pointed to) | |
51 | * | |
52 | * b is m + n + preamble->bootloader_address - preamble->body_load_address | |
53 | * | |
54 | * Useful metadata extends from b - 8KB through to b + 32 KB | |
55 | */ | |
56 | ||
c88d67d0 | 57 | enum { |
5a8589eb SG |
58 | PROBE_SIZE = SZ_4K, /* initial bytes read from partition */ |
59 | ||
c5dca50b SG |
60 | X86_SETUP_OFFSET = -0x1000, /* setup offset relative to base */ |
61 | CMDLINE_OFFSET = -0x2000, /* cmdline offset relative to base */ | |
62 | X86_KERNEL_OFFSET = 0x4000, /* kernel offset relative to base */ | |
c88d67d0 SG |
63 | }; |
64 | ||
1d4bbdf3 SG |
65 | /** |
66 | * struct cros_priv - Private data | |
67 | * | |
68 | * This is read from the disk and recorded for use when the full kernel must | |
69 | * be loaded and booted | |
70 | * | |
71 | * @body_offset: Offset of kernel body from start of partition (in bytes) | |
72 | * @body_size: Size of kernel body in bytes | |
73 | * @part_start: Block offset of selected partition from the start of the disk | |
74 | * @body_load_address: Nominal load address for kernel body | |
75 | * @bootloader_address: Address of bootloader, after body is loaded at | |
76 | * body_load_address | |
77 | * @bootloader_size: Size of bootloader in bytes | |
f861b1ee | 78 | * @info_buf: Buffer containing ChromiumOS info |
1d4bbdf3 SG |
79 | */ |
80 | struct cros_priv { | |
81 | ulong body_offset; | |
82 | ulong body_size; | |
83 | lbaint_t part_start; | |
84 | ulong body_load_address; | |
85 | ulong bootloader_address; | |
86 | ulong bootloader_size; | |
f861b1ee | 87 | void *info_buf; |
1d4bbdf3 SG |
88 | }; |
89 | ||
c88d67d0 SG |
90 | static int cros_check(struct udevice *dev, struct bootflow_iter *iter) |
91 | { | |
92 | /* This only works on block and network devices */ | |
93 | if (bootflow_iter_check_blk(iter)) | |
94 | return log_msg_ret("blk", -ENOTSUPP); | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
99 | static int copy_cmdline(const char *from, const char *uuid, char **bufp) | |
100 | { | |
101 | const int maxlen = 2048; | |
102 | char buf[maxlen]; | |
103 | char *cmd, *to, *end; | |
104 | int len; | |
105 | ||
106 | /* Allow space for cmdline + UUID */ | |
107 | len = strnlen(from, sizeof(buf)); | |
108 | if (len >= maxlen) | |
109 | return -E2BIG; | |
110 | ||
111 | log_debug("uuid %d %s\n", uuid ? (int)strlen(uuid) : 0, uuid); | |
112 | for (to = buf, end = buf + maxlen - UUID_STR_LEN - 1; *from; from++) { | |
113 | if (to >= end) | |
114 | return -E2BIG; | |
115 | if (from[0] == '%' && from[1] == 'U' && uuid && | |
116 | strlen(uuid) == UUID_STR_LEN) { | |
117 | strcpy(to, uuid); | |
118 | to += UUID_STR_LEN; | |
119 | from++; | |
120 | } else { | |
121 | *to++ = *from; | |
122 | } | |
123 | } | |
124 | *to = '\0'; | |
125 | len = to - buf; | |
126 | cmd = strdup(buf); | |
127 | if (!cmd) | |
128 | return -ENOMEM; | |
129 | free(*bufp); | |
130 | *bufp = cmd; | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
5a8589eb SG |
135 | /** |
136 | * scan_part() - Scan a kernel partition to see if has a ChromeOS header | |
137 | * | |
de30aa9a SG |
138 | * This reads the first PROBE_SIZE of a partition, loookng for |
139 | * VB2_KEYBLOCK_MAGIC | |
5a8589eb SG |
140 | * |
141 | * @blk: Block device to scan | |
142 | * @partnum: Partition number to scan | |
143 | * @info: Please to put partition info | |
144 | * @hdrp: Return allocated keyblock header on success | |
145 | */ | |
146 | static int scan_part(struct udevice *blk, int partnum, | |
3257835e | 147 | struct disk_partition *info, struct vb2_keyblock **hdrp) |
5a8589eb SG |
148 | { |
149 | struct blk_desc *desc = dev_get_uclass_plat(blk); | |
150 | struct vb2_keyblock *hdr; | |
71f634b8 | 151 | struct uuid type; |
5a8589eb SG |
152 | ulong num_blks; |
153 | int ret; | |
154 | ||
71f634b8 SG |
155 | if (!partnum) |
156 | return log_msg_ret("efi", -ENOENT); | |
157 | ||
5a8589eb SG |
158 | ret = part_get_info(desc, partnum, info); |
159 | if (ret) | |
160 | return log_msg_ret("part", ret); | |
161 | ||
71f634b8 SG |
162 | /* Check for kernel partition type */ |
163 | log_debug("part %x: type=%s\n", partnum, info->type_guid); | |
164 | if (uuid_str_to_bin(info->type_guid, (u8 *)&type, UUID_STR_FORMAT_GUID)) | |
165 | return log_msg_ret("typ", -EINVAL); | |
166 | ||
167 | if (memcmp(&cros_kern_type, &type, sizeof(type))) | |
168 | return log_msg_ret("typ", -ENOEXEC); | |
169 | ||
5a8589eb SG |
170 | /* Make a buffer for the header information */ |
171 | num_blks = PROBE_SIZE >> desc->log2blksz; | |
172 | log_debug("Reading header, blk=%s, start=%lx, blocks=%lx\n", | |
173 | blk->name, (ulong)info->start, num_blks); | |
174 | hdr = memalign(SZ_1K, PROBE_SIZE); | |
175 | if (!hdr) | |
176 | return log_msg_ret("hdr", -ENOMEM); | |
177 | ret = blk_read(blk, info->start, num_blks, hdr); | |
178 | if (ret != num_blks) { | |
179 | free(hdr); | |
180 | return log_msg_ret("inf", -EIO); | |
181 | } | |
182 | ||
de30aa9a | 183 | if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) { |
5a8589eb | 184 | free(hdr); |
71f634b8 | 185 | log_debug("no magic\n"); |
5a8589eb SG |
186 | return -ENOENT; |
187 | } | |
188 | ||
189 | *hdrp = hdr; | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
f861b1ee SG |
194 | /** |
195 | * cros_read_buf() - Read information into a buf and parse it | |
196 | * | |
197 | * @bflow: Bootflow to update | |
198 | * @buf: Buffer to use | |
199 | * @size: Size of buffer and number of bytes to read thereinto | |
200 | * @start: Start offset to read from on disk | |
201 | * @before_base: Number of bytes to read before the bootloader base | |
202 | * @uuid: UUID string if supported, else NULL | |
203 | * Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure | |
204 | */ | |
205 | static int cros_read_buf(struct bootflow *bflow, void *buf, ulong size, | |
206 | loff_t start, ulong before_base, const char *uuid) | |
207 | { | |
208 | struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
209 | ulong base, setup, cmdline, kern_base; | |
210 | ulong num_blks; | |
211 | int ret; | |
212 | ||
213 | num_blks = size >> desc->log2blksz; | |
214 | log_debug("Reading info to %lx, blk=%s, size=%lx, blocks=%lx\n", | |
215 | (ulong)map_to_sysmem(buf), bflow->blk->name, size, num_blks); | |
216 | ret = blk_read(bflow->blk, start, num_blks, buf); | |
217 | if (ret != num_blks) | |
218 | return log_msg_ret("inf", -EIO); | |
219 | base = map_to_sysmem(buf) + before_base; | |
220 | ||
221 | setup = base + X86_SETUP_OFFSET; | |
222 | cmdline = base + CMDLINE_OFFSET; | |
223 | kern_base = base + X86_KERNEL_OFFSET; | |
224 | log_debug("base %lx setup %lx cmdline %lx kern_base %lx\n", base, | |
225 | setup, cmdline, kern_base); | |
226 | ||
227 | #ifdef CONFIG_X86 | |
228 | const char *version; | |
229 | ||
230 | version = zimage_get_kernel_version(map_sysmem(setup, 0), | |
231 | map_sysmem(kern_base, 0)); | |
232 | log_debug("version %s\n", version); | |
233 | if (version) | |
234 | bflow->name = strdup(version); | |
235 | #endif | |
236 | if (!bflow->name) | |
237 | bflow->name = strdup("ChromeOS"); | |
238 | if (!bflow->name) | |
239 | return log_msg_ret("nam", -ENOMEM); | |
240 | bflow->os_name = strdup("ChromeOS"); | |
241 | if (!bflow->os_name) | |
242 | return log_msg_ret("os", -ENOMEM); | |
243 | ||
244 | ret = copy_cmdline(map_sysmem(cmdline, 0), uuid, &bflow->cmdline); | |
245 | if (ret) | |
246 | return log_msg_ret("cmd", ret); | |
247 | bflow->x86_setup = map_sysmem(setup, 0); | |
248 | ||
249 | return 0; | |
250 | } | |
251 | ||
252 | /** | |
253 | * cros_read_info() - Read information and fill out the bootflow | |
254 | * | |
255 | * @bflow: Bootflow to update | |
256 | * @uuid: UUID string if supported, else NULL | |
257 | * @preamble: Kernel preamble information | |
258 | * Return: 0 if OK, -ENOMEM if out of memory, -EIO on read failure | |
259 | */ | |
260 | static int cros_read_info(struct bootflow *bflow, const char *uuid, | |
261 | const struct vb2_kernel_preamble *preamble) | |
262 | { | |
263 | struct cros_priv *priv = bflow->bootmeth_priv; | |
264 | struct udevice *blk = bflow->blk; | |
265 | struct blk_desc *desc = dev_get_uclass_plat(blk); | |
266 | ulong offset, size, before_base; | |
267 | void *buf; | |
268 | int ret; | |
269 | ||
270 | log_debug("Kernel preamble at %lx, version major %x, minor %x\n", | |
271 | (ulong)map_to_sysmem(preamble), | |
272 | preamble->header_version_major, | |
273 | preamble->header_version_minor); | |
274 | ||
275 | log_debug(" - load_address %lx, bl_addr %lx, bl_size %lx\n", | |
276 | (ulong)preamble->body_load_address, | |
277 | (ulong)preamble->bootloader_address, | |
278 | (ulong)preamble->bootloader_size); | |
279 | ||
280 | priv->body_size = preamble->body_signature.data_size; | |
281 | priv->body_load_address = preamble->body_load_address; | |
282 | priv->bootloader_address = preamble->bootloader_address; | |
283 | priv->bootloader_size = preamble->bootloader_size; | |
284 | log_debug("Kernel body at %lx size %lx\n", priv->body_offset, | |
285 | priv->body_size); | |
286 | ||
287 | /* Work out how many bytes to read before the bootloader base */ | |
288 | before_base = -CMDLINE_OFFSET; | |
289 | ||
290 | /* Read the cmdline through to the end of the bootloader */ | |
291 | size = priv->bootloader_size + before_base; | |
292 | offset = priv->body_offset + | |
293 | (priv->bootloader_address - priv->body_load_address) + | |
294 | CMDLINE_OFFSET; | |
295 | buf = malloc(size); | |
296 | if (!buf) | |
297 | return log_msg_ret("buf", -ENOMEM); | |
298 | ||
299 | ret = cros_read_buf(bflow, buf, size, | |
300 | priv->part_start + (offset >> desc->log2blksz), | |
301 | before_base, uuid); | |
302 | if (ret) { | |
303 | /* Clear this since the buffer is invalid */ | |
304 | bflow->x86_setup = NULL; | |
305 | free(buf); | |
306 | return log_msg_ret("pro", ret); | |
307 | } | |
308 | priv->info_buf = buf; | |
309 | ||
310 | return 0; | |
311 | } | |
312 | ||
074503c4 SG |
313 | static int cros_read_kernel(struct bootflow *bflow) |
314 | { | |
315 | struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); | |
316 | struct cros_priv *priv = bflow->bootmeth_priv; | |
317 | ulong base, setup; | |
318 | ulong num_blks; | |
319 | void *buf; | |
320 | int ret; | |
321 | ||
322 | bflow->size = priv->body_size; | |
323 | ||
324 | buf = memalign(SZ_1K, priv->body_size); | |
325 | if (!buf) | |
326 | return log_msg_ret("buf", -ENOMEM); | |
327 | ||
328 | /* Check that the header is not smaller than permitted */ | |
329 | if (priv->body_offset < PROBE_SIZE) | |
330 | return log_msg_ret("san", EFAULT); | |
331 | ||
332 | /* Read kernel body */ | |
333 | num_blks = priv->body_size >> desc->log2blksz; | |
334 | log_debug("Reading body to %lx, blk=%s, size=%lx, blocks=%lx\n", | |
335 | (ulong)map_to_sysmem(buf), bflow->blk->name, priv->body_size, | |
336 | num_blks); | |
337 | ret = blk_read(bflow->blk, | |
338 | priv->part_start + (priv->body_offset >> desc->log2blksz), | |
339 | num_blks, buf); | |
340 | if (ret != num_blks) | |
341 | return log_msg_ret("inf", -EIO); | |
342 | base = map_to_sysmem(buf) + priv->bootloader_address - | |
343 | priv->body_load_address; | |
344 | setup = base + X86_SETUP_OFFSET; | |
345 | ||
346 | bflow->buf = buf; | |
347 | bflow->x86_setup = map_sysmem(setup, 0); | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
c88d67d0 SG |
352 | static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow) |
353 | { | |
3257835e | 354 | const struct vb2_kernel_preamble *preamble; |
c88d67d0 | 355 | struct disk_partition info; |
3257835e | 356 | struct vb2_keyblock *hdr; |
598dea97 SG |
357 | const char *uuid = NULL; |
358 | struct cros_priv *priv; | |
71f634b8 | 359 | int ret; |
c88d67d0 | 360 | |
71f634b8 | 361 | log_debug("starting, part=%x\n", bflow->part); |
c88d67d0 | 362 | |
71f634b8 SG |
363 | /* Check for kernel partitions */ |
364 | ret = scan_part(bflow->blk, bflow->part, &info, &hdr); | |
4cfe4510 | 365 | if (ret) { |
71f634b8 SG |
366 | log_debug("- scan failed: err=%d\n", ret); |
367 | return log_msg_ret("scan", ret); | |
4cfe4510 | 368 | } |
c88d67d0 | 369 | |
598dea97 SG |
370 | priv = malloc(sizeof(struct cros_priv)); |
371 | if (!priv) { | |
372 | free(hdr); | |
373 | return log_msg_ret("buf", -ENOMEM); | |
374 | } | |
375 | bflow->bootmeth_priv = priv; | |
3257835e | 376 | |
71f634b8 SG |
377 | log_debug("Selected partition %d, header at %lx\n", bflow->part, |
378 | (ulong)map_to_sysmem(hdr)); | |
3257835e | 379 | |
598dea97 SG |
380 | /* Grab a few things from the preamble */ |
381 | preamble = (void *)hdr + hdr->keyblock_size; | |
1d4bbdf3 SG |
382 | priv->body_offset = hdr->keyblock_size + preamble->preamble_size; |
383 | priv->part_start = info.start; | |
c88d67d0 | 384 | |
598dea97 | 385 | /* Now read everything we can learn about kernel */ |
c88d67d0 SG |
386 | #if CONFIG_IS_ENABLED(PARTITION_UUIDS) |
387 | uuid = info.uuid; | |
388 | #endif | |
598dea97 SG |
389 | ret = cros_read_info(bflow, uuid, preamble); |
390 | preamble = NULL; | |
391 | free(hdr); | |
71f634b8 SG |
392 | if (ret) { |
393 | free(priv->info_buf); | |
394 | free(priv); | |
598dea97 | 395 | return log_msg_ret("inf", ret); |
71f634b8 | 396 | } |
598dea97 | 397 | bflow->size = priv->body_size; |
c88d67d0 | 398 | bflow->state = BOOTFLOWST_READY; |
c88d67d0 SG |
399 | |
400 | return 0; | |
401 | } | |
402 | ||
403 | static int cros_read_file(struct udevice *dev, struct bootflow *bflow, | |
404 | const char *file_path, ulong addr, ulong *sizep) | |
405 | { | |
406 | return -ENOSYS; | |
407 | } | |
408 | ||
a831d113 | 409 | #if CONFIG_IS_ENABLED(BOOTSTD_FULL) |
c279224e | 410 | static int cros_read_all(struct udevice *dev, struct bootflow *bflow) |
c88d67d0 | 411 | { |
598dea97 SG |
412 | int ret; |
413 | ||
c279224e SG |
414 | if (bflow->buf) |
415 | return log_msg_ret("ld", -EALREADY); | |
598dea97 SG |
416 | ret = cros_read_kernel(bflow); |
417 | if (ret) | |
418 | return log_msg_ret("rd", ret); | |
c279224e SG |
419 | |
420 | return 0; | |
421 | } | |
a831d113 | 422 | #endif /* BOOTSTD_FULL */ |
c279224e SG |
423 | |
424 | static int cros_boot(struct udevice *dev, struct bootflow *bflow) | |
425 | { | |
426 | int ret; | |
427 | ||
428 | if (!bflow->buf) { | |
429 | ret = cros_read_kernel(bflow); | |
430 | if (ret) | |
431 | return log_msg_ret("rd", ret); | |
432 | } | |
c88d67d0 | 433 | |
daffb0be SG |
434 | if (IS_ENABLED(CONFIG_X86)) { |
435 | ret = zboot_start(map_to_sysmem(bflow->buf), bflow->size, 0, 0, | |
436 | map_to_sysmem(bflow->x86_setup), | |
437 | bflow->cmdline); | |
438 | } else { | |
439 | ret = bootm_boot_start(map_to_sysmem(bflow->buf), | |
440 | bflow->cmdline); | |
441 | } | |
442 | ||
443 | return log_msg_ret("go", ret); | |
c88d67d0 SG |
444 | } |
445 | ||
446 | static int cros_bootmeth_bind(struct udevice *dev) | |
447 | { | |
448 | struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); | |
449 | ||
450 | plat->desc = "ChromiumOS boot"; | |
71f634b8 | 451 | plat->flags = BOOTMETHF_ANY_PART; |
c88d67d0 SG |
452 | |
453 | return 0; | |
454 | } | |
455 | ||
456 | static struct bootmeth_ops cros_bootmeth_ops = { | |
457 | .check = cros_check, | |
458 | .read_bootflow = cros_read_bootflow, | |
459 | .read_file = cros_read_file, | |
460 | .boot = cros_boot, | |
a831d113 | 461 | #if CONFIG_IS_ENABLED(BOOTSTD_FULL) |
c279224e | 462 | .read_all = cros_read_all, |
a831d113 | 463 | #endif /* BOOTSTD_FULL */ |
c88d67d0 SG |
464 | }; |
465 | ||
466 | static const struct udevice_id cros_bootmeth_ids[] = { | |
467 | { .compatible = "u-boot,cros" }, | |
468 | { } | |
469 | }; | |
470 | ||
471 | U_BOOT_DRIVER(bootmeth_cros) = { | |
472 | .name = "bootmeth_cros", | |
473 | .id = UCLASS_BOOTMETH, | |
474 | .of_match = cros_bootmeth_ids, | |
475 | .ops = &cros_bootmeth_ops, | |
476 | .bind = cros_bootmeth_bind, | |
477 | }; |