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