doc: fix mistyped "env flags" command
[pandora-u-boot.git] / boot / bootmeth_cros.c
1 // 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>
15 #include <bootm.h>
16 #include <bootmeth.h>
17 #include <display_options.h>
18 #include <dm.h>
19 #include <efi.h>
20 #include <malloc.h>
21 #include <mapmem.h>
22 #include <part.h>
23 #include <linux/sizes.h>
24 #include "bootmeth_cros.h"
25
26 static const efi_guid_t cros_kern_type = PARTITION_CROS_KERNEL;
27
28 /*
29  * Layout of the ChromeOS kernel
30  *
31  * Partitions 2 and 4 contain kernels with type GUID_CROS_KERNEL
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
57 enum {
58         PROBE_SIZE      = SZ_4K,        /* initial bytes read from partition */
59
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 */
63 };
64
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
78  * @info_buf: Buffer containing ChromiumOS info
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;
87         void *info_buf;
88 };
89
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
135 /**
136  * scan_part() - Scan a kernel partition to see if has a ChromeOS header
137  *
138  * This reads the first PROBE_SIZE of a partition, loookng for
139  * VB2_KEYBLOCK_MAGIC
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,
147                      struct disk_partition *info, struct vb2_keyblock **hdrp)
148 {
149         struct blk_desc *desc = dev_get_uclass_plat(blk);
150         struct vb2_keyblock *hdr;
151         struct uuid type;
152         ulong num_blks;
153         int ret;
154
155         if (!partnum)
156                 return log_msg_ret("efi", -ENOENT);
157
158         ret = part_get_info(desc, partnum, info);
159         if (ret)
160                 return log_msg_ret("part", ret);
161
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
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
183         if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) {
184                 free(hdr);
185                 log_debug("no magic\n");
186                 return -ENOENT;
187         }
188
189         *hdrp = hdr;
190
191         return 0;
192 }
193
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
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
352 static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow)
353 {
354         const struct vb2_kernel_preamble *preamble;
355         struct disk_partition info;
356         struct vb2_keyblock *hdr;
357         const char *uuid = NULL;
358         struct cros_priv *priv;
359         int ret;
360
361         log_debug("starting, part=%x\n", bflow->part);
362
363         /* Check for kernel partitions */
364         ret = scan_part(bflow->blk, bflow->part, &info, &hdr);
365         if (ret) {
366                 log_debug("- scan failed: err=%d\n", ret);
367                 return log_msg_ret("scan", ret);
368         }
369
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;
376
377         log_debug("Selected partition %d, header at %lx\n", bflow->part,
378                   (ulong)map_to_sysmem(hdr));
379
380         /* Grab a few things from the preamble */
381         preamble = (void *)hdr + hdr->keyblock_size;
382         priv->body_offset = hdr->keyblock_size + preamble->preamble_size;
383         priv->part_start = info.start;
384
385         /* Now read everything we can learn about kernel */
386 #if CONFIG_IS_ENABLED(PARTITION_UUIDS)
387         uuid = info.uuid;
388 #endif
389         ret = cros_read_info(bflow, uuid, preamble);
390         preamble = NULL;
391         free(hdr);
392         if (ret) {
393                 free(priv->info_buf);
394                 free(priv);
395                 return log_msg_ret("inf", ret);
396         }
397         bflow->size = priv->body_size;
398         bflow->state = BOOTFLOWST_READY;
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
409 #if CONFIG_IS_ENABLED(BOOTSTD_FULL)
410 static int cros_read_all(struct udevice *dev, struct bootflow *bflow)
411 {
412         int ret;
413
414         if (bflow->buf)
415                 return log_msg_ret("ld", -EALREADY);
416         ret = cros_read_kernel(bflow);
417         if (ret)
418                 return log_msg_ret("rd", ret);
419
420         return 0;
421 }
422 #endif /* BOOTSTD_FULL */
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         }
433
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);
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";
451         plat->flags = BOOTMETHF_ANY_PART;
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,
461 #if CONFIG_IS_ENABLED(BOOTSTD_FULL)
462         .read_all       = cros_read_all,
463 #endif /* BOOTSTD_FULL */
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 };