blob: 1776fb1838c6c81e27ace2d452469830620eec03 [file] [log] [blame]
Simon Glassd74e62a2023-07-12 09:04:45 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Bootmethod for ChromiumOS
4 *
5 * Copyright 2023 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
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>
Simon Glass4493d612023-07-30 11:16:53 -060015#include <bootm.h>
Simon Glassd74e62a2023-07-12 09:04:45 -060016#include <bootmeth.h>
Simon Glass3e62d802023-07-30 11:16:50 -060017#include <display_options.h>
Simon Glassd74e62a2023-07-12 09:04:45 -060018#include <dm.h>
19#include <malloc.h>
20#include <mapmem.h>
21#include <part.h>
Simon Glassd74e62a2023-07-12 09:04:45 -060022#include <linux/sizes.h>
Simon Glass7c6ab822023-07-30 11:16:49 -060023#include "bootmeth_cros.h"
Simon Glassd74e62a2023-07-12 09:04:45 -060024
Simon Glassd5308922023-07-30 11:16:54 -060025/*
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
Simon Glassd74e62a2023-07-12 09:04:45 -060054enum {
Simon Glass09ebe822023-07-30 11:16:48 -060055 PROBE_SIZE = SZ_4K, /* initial bytes read from partition */
56
Simon Glass016dc602023-07-30 11:16:52 -060057 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 */
Simon Glassd74e62a2023-07-12 09:04:45 -060060};
61
Simon Glass71138d32023-07-30 11:16:55 -060062/**
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
Simon Glass37b61c12023-07-30 11:16:57 -060075 * @info_buf: Buffer containing ChromiumOS info
Simon Glass71138d32023-07-30 11:16:55 -060076 */
77struct 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;
Simon Glass37b61c12023-07-30 11:16:57 -060084 void *info_buf;
Simon Glass71138d32023-07-30 11:16:55 -060085};
86
Simon Glassd74e62a2023-07-12 09:04:45 -060087static 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
96static 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
Simon Glass09ebe822023-07-30 11:16:48 -0600132/**
133 * scan_part() - Scan a kernel partition to see if has a ChromeOS header
134 *
Simon Glass7c6ab822023-07-30 11:16:49 -0600135 * This reads the first PROBE_SIZE of a partition, loookng for
136 * VB2_KEYBLOCK_MAGIC
Simon Glass09ebe822023-07-30 11:16:48 -0600137 *
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 */
143static int scan_part(struct udevice *blk, int partnum,
Simon Glass05cfcd02023-07-30 11:16:51 -0600144 struct disk_partition *info, struct vb2_keyblock **hdrp)
Simon Glass09ebe822023-07-30 11:16:48 -0600145{
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
Simon Glass7c6ab822023-07-30 11:16:49 -0600168 if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) {
Simon Glass09ebe822023-07-30 11:16:48 -0600169 free(hdr);
170 return -ENOENT;
171 }
172
173 *hdrp = hdr;
174
175 return 0;
176}
177
Simon Glass37b61c12023-07-30 11:16:57 -0600178/**
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 */
189static 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 */
244static 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
Simon Glass26279262023-07-30 11:16:58 -0600297static 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
Simon Glassd74e62a2023-07-12 09:04:45 -0600336static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow)
337{
Simon Glass05cfcd02023-07-30 11:16:51 -0600338 const struct vb2_kernel_preamble *preamble;
Simon Glassd74e62a2023-07-12 09:04:45 -0600339 struct disk_partition info;
Simon Glass05cfcd02023-07-30 11:16:51 -0600340 struct vb2_keyblock *hdr;
Simon Glass4fde2062023-07-30 11:16:59 -0600341 const char *uuid = NULL;
342 struct cros_priv *priv;
Simon Glass3e62d802023-07-30 11:16:50 -0600343 int part, ret;
Simon Glassd74e62a2023-07-12 09:04:45 -0600344
345 log_debug("starting, part=%d\n", bflow->part);
346
347 /* We consider the whole disk, not any one partition */
348 if (bflow->part)
349 return log_msg_ret("max", -ENOENT);
350
Simon Glass3e62d802023-07-30 11:16:50 -0600351 /* Check partition 2 then 4 */
352 part = 2;
353 ret = scan_part(bflow->blk, part, &info, &hdr);
354 if (ret) {
355 part = 4;
356 ret = scan_part(bflow->blk, part, &info, &hdr);
357 if (ret)
358 return log_msg_ret("scan", ret);
359 }
360 bflow->part = part;
Simon Glassd74e62a2023-07-12 09:04:45 -0600361
Simon Glass4fde2062023-07-30 11:16:59 -0600362 priv = malloc(sizeof(struct cros_priv));
363 if (!priv) {
364 free(hdr);
365 return log_msg_ret("buf", -ENOMEM);
366 }
367 bflow->bootmeth_priv = priv;
Simon Glass05cfcd02023-07-30 11:16:51 -0600368
Simon Glass4fde2062023-07-30 11:16:59 -0600369 log_info("Selected partition %d, header at %lx\n", bflow->part,
370 (ulong)map_to_sysmem(hdr));
Simon Glassd74e62a2023-07-12 09:04:45 -0600371
Simon Glass4fde2062023-07-30 11:16:59 -0600372 /* Grab a few things from the preamble */
373 preamble = (void *)hdr + hdr->keyblock_size;
Simon Glass71138d32023-07-30 11:16:55 -0600374 priv->body_offset = hdr->keyblock_size + preamble->preamble_size;
375 priv->part_start = info.start;
Simon Glass05cfcd02023-07-30 11:16:51 -0600376
Simon Glass4fde2062023-07-30 11:16:59 -0600377 /* Now read everything we can learn about kernel */
Simon Glassd74e62a2023-07-12 09:04:45 -0600378#if CONFIG_IS_ENABLED(PARTITION_UUIDS)
379 uuid = info.uuid;
380#endif
Simon Glass4fde2062023-07-30 11:16:59 -0600381 ret = cros_read_info(bflow, uuid, preamble);
382 preamble = NULL;
383 free(hdr);
Simon Glassd74e62a2023-07-12 09:04:45 -0600384 if (ret)
Simon Glass4fde2062023-07-30 11:16:59 -0600385 return log_msg_ret("inf", ret);
386 bflow->size = priv->body_size;
Simon Glassd74e62a2023-07-12 09:04:45 -0600387 bflow->state = BOOTFLOWST_READY;
Simon Glassd74e62a2023-07-12 09:04:45 -0600388
389 return 0;
390}
391
392static int cros_read_file(struct udevice *dev, struct bootflow *bflow,
393 const char *file_path, ulong addr, ulong *sizep)
394{
395 return -ENOSYS;
396}
397
Simon Glass6d8f95b2023-08-10 19:33:18 -0600398#if CONFIG_IS_ENABLED(BOOSTD_FULL)
399static int cros_read_all(struct udevice *dev, struct bootflow *bflow)
Simon Glassd74e62a2023-07-12 09:04:45 -0600400{
Simon Glass4fde2062023-07-30 11:16:59 -0600401 int ret;
402
Simon Glass6d8f95b2023-08-10 19:33:18 -0600403 if (bflow->buf)
404 return log_msg_ret("ld", -EALREADY);
Simon Glass4fde2062023-07-30 11:16:59 -0600405 ret = cros_read_kernel(bflow);
406 if (ret)
407 return log_msg_ret("rd", ret);
Simon Glass6d8f95b2023-08-10 19:33:18 -0600408
409 return 0;
410}
411#endif /* BOOSTD_FULL */
412
413static int cros_boot(struct udevice *dev, struct bootflow *bflow)
414{
415 int ret;
416
417 if (!bflow->buf) {
418 ret = cros_read_kernel(bflow);
419 if (ret)
420 return log_msg_ret("rd", ret);
421 }
Simon Glass18700262023-07-30 11:17:02 -0600422
423 if (IS_ENABLED(CONFIG_X86)) {
424 ret = zboot_start(map_to_sysmem(bflow->buf), bflow->size, 0, 0,
425 map_to_sysmem(bflow->x86_setup),
426 bflow->cmdline);
427 } else {
428 ret = bootm_boot_start(map_to_sysmem(bflow->buf),
429 bflow->cmdline);
430 }
Simon Glassd74e62a2023-07-12 09:04:45 -0600431
Simon Glass18700262023-07-30 11:17:02 -0600432 return log_msg_ret("go", ret);
Simon Glassd74e62a2023-07-12 09:04:45 -0600433}
434
435static int cros_bootmeth_bind(struct udevice *dev)
436{
437 struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
438
439 plat->desc = "ChromiumOS boot";
440
441 return 0;
442}
443
444static struct bootmeth_ops cros_bootmeth_ops = {
445 .check = cros_check,
446 .read_bootflow = cros_read_bootflow,
447 .read_file = cros_read_file,
448 .boot = cros_boot,
Simon Glass6d8f95b2023-08-10 19:33:18 -0600449#if CONFIG_IS_ENABLED(BOOSTD_FULL)
450 .read_all = cros_read_all,
451#endif /* BOOSTD_FULL */
Simon Glassd74e62a2023-07-12 09:04:45 -0600452};
453
454static const struct udevice_id cros_bootmeth_ids[] = {
455 { .compatible = "u-boot,cros" },
456 { }
457};
458
459U_BOOT_DRIVER(bootmeth_cros) = {
460 .name = "bootmeth_cros",
461 .id = UCLASS_BOOTMETH,
462 .of_match = cros_bootmeth_ids,
463 .ops = &cros_bootmeth_ops,
464 .bind = cros_bootmeth_bind,
465};