]> Git Repo - u-boot.git/blob - boot/bootmeth_cros.c
82cbdcf2c93a6390499c0cc7d054d13e35e2cfcb
[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 <[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>
15 #include <bootm.h>
16 #include <bootmeth.h>
17 #include <display_options.h>
18 #include <dm.h>
19 #include <malloc.h>
20 #include <mapmem.h>
21 #include <part.h>
22 #include <linux/sizes.h>
23 #include "bootmeth_cros.h"
24
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
54 enum {
55         PROBE_SIZE      = SZ_4K,        /* initial bytes read from partition */
56
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 */
60 };
61
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
75  * @info_buf: Buffer containing ChromiumOS info
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;
84         void *info_buf;
85 };
86
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
132 /**
133  * scan_part() - Scan a kernel partition to see if has a ChromeOS header
134  *
135  * This reads the first PROBE_SIZE of a partition, loookng for
136  * VB2_KEYBLOCK_MAGIC
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,
144                      struct disk_partition *info, struct vb2_keyblock **hdrp)
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
168         if (memcmp(VB2_KEYBLOCK_MAGIC, hdr->magic, VB2_KEYBLOCK_MAGIC_SIZE)) {
169                 free(hdr);
170                 return -ENOENT;
171         }
172
173         *hdrp = hdr;
174
175         return 0;
176 }
177
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
297 static int cros_read_bootflow(struct udevice *dev, struct bootflow *bflow)
298 {
299         struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
300         ulong base, setup, cmdline, num_blks, kern_base;
301         const struct vb2_kernel_preamble *preamble;
302         struct cros_priv s_priv, *priv = &s_priv;
303         struct disk_partition info;
304         const char *uuid = NULL;
305         struct vb2_keyblock *hdr;
306         int part, ret;
307         void *buf;
308
309         log_debug("starting, part=%d\n", bflow->part);
310
311         /* We consider the whole disk, not any one partition */
312         if (bflow->part)
313                 return log_msg_ret("max", -ENOENT);
314
315         /* Check partition 2 then 4 */
316         part = 2;
317         ret = scan_part(bflow->blk, part, &info, &hdr);
318         if (ret) {
319                 part = 4;
320                 ret = scan_part(bflow->blk, part, &info, &hdr);
321                 if (ret)
322                         return log_msg_ret("scan", ret);
323         }
324         bflow->part = part;
325
326         log_info("Selected parition %d, header at %lx\n", bflow->part,
327                  (ulong)map_to_sysmem(hdr));
328         preamble = (void *)hdr + hdr->keyblock_size;
329         log_debug("Kernel preamble at %lx, version major %x, minor %x\n",
330                   (ulong)map_to_sysmem(preamble),
331                   preamble->header_version_major,
332                   preamble->header_version_minor);
333
334         log_debug("  - load_address %lx, bl_addr %lx, bl_size %lx\n",
335                   (ulong)preamble->body_load_address,
336                   (ulong)preamble->bootloader_address,
337                   (ulong)preamble->bootloader_size);
338
339         priv->body_offset = hdr->keyblock_size + preamble->preamble_size;
340         priv->part_start = info.start;
341         priv->body_size = preamble->body_signature.data_size;
342         priv->body_load_address = preamble->body_load_address;
343         priv->bootloader_address = preamble->bootloader_address;
344         priv->bootloader_size = preamble->bootloader_size;
345         log_debug("Kernel body at %lx size %lx\n", priv->body_offset,
346                   priv->body_size);
347         bflow->size = priv->body_size;
348
349         buf = memalign(SZ_1K, priv->body_size);
350         if (!buf)
351                 return log_msg_ret("buf", -ENOMEM);
352
353         /* Check that the header is not smaller than permitted */
354         if (priv->body_offset < PROBE_SIZE)
355                 return log_msg_ret("san", EFAULT);
356
357         /* Read kernel body */
358         num_blks = priv->body_size >> desc->log2blksz;
359         log_debug("Reading body to %lx, blk=%s, size=%lx, blocks=%lx\n",
360                   (ulong)map_to_sysmem(buf), bflow->blk->name, priv->body_size,
361                   num_blks);
362         ret = blk_read(bflow->blk,
363                        priv->part_start +
364                        (priv->body_offset >> desc->log2blksz),
365                        num_blks, buf);
366         if (ret != num_blks)
367                 return log_msg_ret("inf", -EIO);
368         base = map_to_sysmem(buf) + priv->bootloader_address -
369                 priv->body_load_address;
370
371         setup = base + X86_SETUP_OFFSET;
372         cmdline = base + CMDLINE_OFFSET;
373         kern_base = base + X86_KERNEL_OFFSET;
374         log_debug("base %lx setup %lx cmdline %lx kern_base %lx\n", base,
375                   setup, cmdline, kern_base);
376
377 #ifdef CONFIG_X86
378         const char *version;
379
380         version = zimage_get_kernel_version(map_sysmem(setup, 0),
381                                             map_sysmem(kern_base, 0));
382         log_debug("version %s\n", version);
383         if (version)
384                 bflow->name = strdup(version);
385 #endif
386         if (!bflow->name)
387                 bflow->name = strdup("ChromeOS");
388         if (!bflow->name)
389                 return log_msg_ret("nam", -ENOMEM);
390         bflow->os_name = strdup("ChromeOS");
391         if (!bflow->os_name)
392                 return log_msg_ret("os", -ENOMEM);
393
394 #if CONFIG_IS_ENABLED(PARTITION_UUIDS)
395         uuid = info.uuid;
396 #endif
397         ret = copy_cmdline(map_sysmem(cmdline, 0), uuid, &bflow->cmdline);
398         if (ret)
399                 return log_msg_ret("cmd", ret);
400
401         bflow->state = BOOTFLOWST_READY;
402         bflow->buf = buf;
403         bflow->x86_setup = map_sysmem(setup, 0);
404
405         return 0;
406 }
407
408 static int cros_read_file(struct udevice *dev, struct bootflow *bflow,
409                          const char *file_path, ulong addr, ulong *sizep)
410 {
411         return -ENOSYS;
412 }
413
414 static int cros_boot(struct udevice *dev, struct bootflow *bflow)
415 {
416 #ifdef CONFIG_X86
417         zboot_start(map_to_sysmem(bflow->buf), bflow->size, 0, 0,
418                     map_to_sysmem(bflow->x86_setup),
419                     bflow->cmdline);
420 #endif
421
422         return log_msg_ret("go", -EFAULT);
423 }
424
425 static int cros_bootmeth_bind(struct udevice *dev)
426 {
427         struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
428
429         plat->desc = "ChromiumOS boot";
430
431         return 0;
432 }
433
434 static struct bootmeth_ops cros_bootmeth_ops = {
435         .check          = cros_check,
436         .read_bootflow  = cros_read_bootflow,
437         .read_file      = cros_read_file,
438         .boot           = cros_boot,
439 };
440
441 static const struct udevice_id cros_bootmeth_ids[] = {
442         { .compatible = "u-boot,cros" },
443         { }
444 };
445
446 U_BOOT_DRIVER(bootmeth_cros) = {
447         .name           = "bootmeth_cros",
448         .id             = UCLASS_BOOTMETH,
449         .of_match       = cros_bootmeth_ids,
450         .ops            = &cros_bootmeth_ops,
451         .bind           = cros_bootmeth_bind,
452 };
This page took 0.042464 seconds and 2 git commands to generate.