]> Git Repo - J-u-boot.git/blob - boot/bootmeth_efi.c
Merge patch series "env: mmc: fix use of two separate partitions with proper type...
[J-u-boot.git] / boot / bootmeth_efi.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Bootmethod for distro boot via EFI
4  *
5  * Copyright 2021 Google LLC
6  * Written by Simon Glass <[email protected]>
7  */
8
9 #define LOG_CATEGORY UCLASS_BOOTSTD
10
11 #include <bootdev.h>
12 #include <bootflow.h>
13 #include <bootmeth.h>
14 #include <command.h>
15 #include <dm.h>
16 #include <efi_default_filename.h>
17 #include <efi_loader.h>
18 #include <fs.h>
19 #include <malloc.h>
20 #include <mapmem.h>
21 #include <mmc.h>
22 #include <net.h>
23 #include <pxe_utils.h>
24 #include <linux/sizes.h>
25
26 #define EFI_DIRNAME     "/EFI/BOOT/"
27
28 static int get_efi_pxe_arch(void)
29 {
30         /* http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xml */
31         if (IS_ENABLED(CONFIG_ARM64))
32                 return 0xb;
33         else if (IS_ENABLED(CONFIG_ARM))
34                 return 0xa;
35         else if (IS_ENABLED(CONFIG_X86_64))
36                 return 0x6;
37         else if (IS_ENABLED(CONFIG_X86))
38                 return 0x7;
39         else if (IS_ENABLED(CONFIG_ARCH_RV32I))
40                 return 0x19;
41         else if (IS_ENABLED(CONFIG_ARCH_RV64I))
42                 return 0x1b;
43         else if (IS_ENABLED(CONFIG_SANDBOX))
44                 return 0;       /* not used */
45
46         return -EINVAL;
47 }
48
49 static int get_efi_pxe_vci(char *str, int max_len)
50 {
51         int ret;
52
53         ret = get_efi_pxe_arch();
54         if (ret < 0)
55                 return ret;
56
57         snprintf(str, max_len, "PXEClient:Arch:%05x:UNDI:003000", ret);
58
59         return 0;
60 }
61
62 /**
63  * bootmeth_uses_network() - check if the media device is Ethernet
64  *
65  * @bflow: Bootflow to check
66  * Returns: true if the media device is Ethernet, else false
67  */
68 static bool bootmeth_uses_network(struct bootflow *bflow)
69 {
70         const struct udevice *media = dev_get_parent(bflow->dev);
71
72         return IS_ENABLED(CONFIG_CMD_DHCP) &&
73             device_get_uclass_id(media) == UCLASS_ETH;
74 }
75
76 static void set_efi_bootdev(struct blk_desc *desc, struct bootflow *bflow)
77 {
78         const struct udevice *media_dev;
79         int size = bflow->size;
80         const char *dev_name;
81         char devnum_str[9];
82         char dirname[200];
83         char *last_slash;
84
85         /*
86          * This is a horrible hack to tell EFI about this boot device. Once we
87          * unify EFI with the rest of U-Boot we can clean this up. The same hack
88          * exists in multiple places, e.g. in the fs, tftp and load commands.
89          *
90          * Once we can clean up the EFI code to make proper use of driver model,
91          * this can go away.
92          */
93         media_dev = dev_get_parent(bflow->dev);
94         snprintf(devnum_str, sizeof(devnum_str), "%x:%x",
95                  desc ? desc->devnum : dev_seq(media_dev),
96                  bflow->part);
97
98         strlcpy(dirname, bflow->fname, sizeof(dirname));
99         last_slash = strrchr(dirname, '/');
100         if (last_slash)
101                 *last_slash = '\0';
102
103         dev_name = device_get_uclass_id(media_dev) == UCLASS_MASS_STORAGE ?
104                  "usb" : blk_get_uclass_name(device_get_uclass_id(media_dev));
105         log_debug("setting bootdev %s, %s, %s, %p, %x\n",
106                   dev_name, devnum_str, bflow->fname, bflow->buf, size);
107         efi_set_bootdev(dev_name, devnum_str, bflow->fname, bflow->buf, size);
108 }
109
110 static int efiload_read_file(struct bootflow *bflow, ulong addr)
111 {
112         struct blk_desc *desc = NULL;
113         loff_t bytes_read;
114         int ret;
115
116         if (bflow->blk)
117                  desc = dev_get_uclass_plat(bflow->blk);
118         ret = bootmeth_setup_fs(bflow, desc);
119         if (ret)
120                 return log_msg_ret("set", ret);
121
122         ret = fs_read(bflow->fname, addr, 0, bflow->size, &bytes_read);
123         if (ret)
124                 return log_msg_ret("read", ret);
125         bflow->buf = map_sysmem(addr, bflow->size);
126
127         set_efi_bootdev(desc, bflow);
128
129         return 0;
130 }
131
132 static int distro_efi_check(struct udevice *dev, struct bootflow_iter *iter)
133 {
134         /* This only works on block and network devices */
135         if (bootflow_iter_check_blk(iter) && bootflow_iter_check_net(iter))
136                 return log_msg_ret("blk", -ENOTSUPP);
137
138         /* This works on block devices and network devices */
139         if (iter->method_flags & BOOTFLOW_METHF_PXE_ONLY)
140                 return log_msg_ret("pxe", -ENOTSUPP);
141
142         return 0;
143 }
144
145 /*
146  * distro_efi_try_bootflow_files() - Check that files are present
147  *
148  * This reads any FDT file and checks whether the bootflow file is present, for
149  * later reading. We avoid reading the bootflow now, since it is likely large,
150  * it may take a long time and we want to avoid needing to allocate memory for
151  * it
152  *
153  * @dev: bootmeth device to use
154  * @bflow: bootflow to update
155  */
156 static int distro_efi_try_bootflow_files(struct udevice *dev,
157                                          struct bootflow *bflow)
158 {
159         struct blk_desc *desc = NULL;
160         ulong fdt_addr, size;
161         char fname[256];
162         int ret, seq;
163
164         /* We require a partition table */
165         if (!bflow->part)
166                 return -ENOENT;
167
168         strcpy(fname, EFI_DIRNAME);
169         strcat(fname, BOOTEFI_NAME);
170
171         if (bflow->blk)
172                  desc = dev_get_uclass_plat(bflow->blk);
173         ret = bootmeth_try_file(bflow, desc, NULL, fname);
174         if (ret)
175                 return log_msg_ret("try", ret);
176
177         /* Since we can access the file, let's call it ready */
178         bflow->state = BOOTFLOWST_READY;
179
180         fdt_addr = env_get_hex("fdt_addr_r", 0);
181
182         /* try the various available names */
183         ret = -ENOENT;
184         *fname = '\0';
185         for (seq = 0; ret == -ENOENT; seq++) {
186                 ret = efi_get_distro_fdt_name(fname, sizeof(fname), seq);
187                 if (ret == -EALREADY)
188                         bflow->flags = BOOTFLOWF_USE_PRIOR_FDT;
189                 if (!ret) {
190                         /* Limit FDT files to 4MB */
191                         size = SZ_4M;
192                         ret = bootmeth_common_read_file(dev, bflow, fname,
193                                                         fdt_addr, &size);
194                 }
195         }
196
197         if (*fname) {
198                 bflow->fdt_fname = strdup(fname);
199                 if (!bflow->fdt_fname)
200                         return log_msg_ret("fil", -ENOMEM);
201         }
202
203         if (!ret) {
204                 bflow->fdt_size = size;
205                 bflow->fdt_addr = fdt_addr;
206
207                 /*
208                  * TODO: Apply extension overlay
209                  *
210                  * Here we need to load and apply the extension overlay. This is
211                  * not implemented. See do_extension_apply(). The extension
212                  * stuff needs an implementation in boot/extension.c so it is
213                  * separate from the command code. Really the extension stuff
214                  * should use the device tree and a uclass / driver interface
215                  * rather than implementing its own list
216                  */
217         } else {
218                 log_debug("No device tree available\n");
219                 bflow->flags |= BOOTFLOWF_USE_BUILTIN_FDT;
220         }
221
222         return 0;
223 }
224
225 static int distro_efi_read_bootflow_net(struct bootflow *bflow)
226 {
227         char file_addr[17], fname[256];
228         char *tftp_argv[] = {"tftp", file_addr, fname, NULL};
229         struct cmd_tbl cmdtp = {};      /* dummy */
230         const char *addr_str, *fdt_addr_str, *bootfile_name;
231         int ret, arch, size;
232         ulong addr, fdt_addr;
233         char str[36];
234
235         ret = get_efi_pxe_vci(str, sizeof(str));
236         if (ret)
237                 return log_msg_ret("vci", ret);
238         ret = get_efi_pxe_arch();
239         if (ret < 0)
240                 return log_msg_ret("arc", ret);
241         arch = ret;
242
243         ret = env_set("bootp_vci", str);
244         if (ret)
245                 return log_msg_ret("vcs", ret);
246         ret = env_set_hex("bootp_arch", arch);
247         if (ret)
248                 return log_msg_ret("ars", ret);
249
250         /* figure out the load address */
251         addr_str = env_get("kernel_addr_r");
252         addr = addr_str ? hextoul(addr_str, NULL) : image_load_addr;
253
254         /* clear any previous bootfile */
255         env_set("bootfile", NULL);
256
257         /* read the kernel */
258         ret = dhcp_run(addr, NULL, true);
259         if (ret)
260                 return log_msg_ret("dhc", ret);
261
262         size = env_get_hex("filesize", -1);
263         if (size <= 0)
264                 return log_msg_ret("sz", -EINVAL);
265         bflow->size = size;
266
267         /* bootfile should be setup by dhcp */
268         bootfile_name = env_get("bootfile");
269         if (!bootfile_name)
270                 return log_msg_ret("bootfile_name", ret);
271         bflow->fname = strdup(bootfile_name);
272
273         /* do the hideous EFI hack */
274         efi_set_bootdev("Net", "", bflow->fname, map_sysmem(addr, 0),
275                         bflow->size);
276
277         /* read the DT file also */
278         fdt_addr_str = env_get("fdt_addr_r");
279         if (!fdt_addr_str)
280                 return log_msg_ret("fdt", -EINVAL);
281         fdt_addr = hextoul(fdt_addr_str, NULL);
282         sprintf(file_addr, "%lx", fdt_addr);
283
284         /* We only allow the first prefix with PXE */
285         ret = efi_get_distro_fdt_name(fname, sizeof(fname), 0);
286         if (ret)
287                 return log_msg_ret("nam", ret);
288
289         bflow->fdt_fname = strdup(fname);
290         if (!bflow->fdt_fname)
291                 return log_msg_ret("fil", -ENOMEM);
292
293         if (!do_tftpb(&cmdtp, 0, 3, tftp_argv)) {
294                 bflow->fdt_size = env_get_hex("filesize", 0);
295                 bflow->fdt_addr = fdt_addr;
296         } else {
297                 log_debug("No device tree available\n");
298                 bflow->flags |= BOOTFLOWF_USE_BUILTIN_FDT;
299         }
300
301         bflow->state = BOOTFLOWST_READY;
302
303         return 0;
304 }
305
306 static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow)
307 {
308         int ret;
309
310         /*
311          * bootmeth_efi doesn't allocate any buffer neither for blk nor net device
312          * set flag to avoid freeing static buffer.
313          */
314         bflow->flags |= BOOTFLOWF_STATIC_BUF;
315
316         if (bootmeth_uses_network(bflow)) {
317                 /* we only support reading from one device, so ignore 'dev' */
318                 ret = distro_efi_read_bootflow_net(bflow);
319                 if (ret)
320                         return log_msg_ret("net", ret);
321         } else {
322                 ret = distro_efi_try_bootflow_files(dev, bflow);
323                 if (ret)
324                         return log_msg_ret("blk", ret);
325         }
326
327         return 0;
328 }
329
330 static int distro_efi_boot(struct udevice *dev, struct bootflow *bflow)
331 {
332         ulong kernel, fdt;
333         int ret;
334
335         kernel = env_get_hex("kernel_addr_r", 0);
336         if (!bootmeth_uses_network(bflow)) {
337                 ret = efiload_read_file(bflow, kernel);
338                 if (ret)
339                         return log_msg_ret("read", ret);
340
341                 /*
342                  * use the provided device tree if not using the built-in fdt
343                  */
344                 if (bflow->flags & ~BOOTFLOWF_USE_BUILTIN_FDT)
345                         fdt = bflow->fdt_addr;
346
347         } else {
348                 /*
349                  * This doesn't actually work for network devices:
350                  *
351                  * do_bootefi_image() No UEFI binary known at 0x02080000
352                  *
353                  * But this is the same behaviour for distro boot, so it can be
354                  * fixed here.
355                  */
356                 fdt = env_get_hex("fdt_addr_r", 0);
357         }
358
359         if (bflow->flags & BOOTFLOWF_USE_BUILTIN_FDT) {
360                 log_debug("Booting with built-in fdt\n");
361                 if (efi_binary_run(map_sysmem(kernel, 0), bflow->size,
362                                    EFI_FDT_USE_INTERNAL))
363                         return log_msg_ret("run", -EINVAL);
364         } else {
365                 log_debug("Booting with external fdt\n");
366                 if (efi_binary_run(map_sysmem(kernel, 0), bflow->size,
367                                    map_sysmem(fdt, 0)))
368                         return log_msg_ret("run", -EINVAL);
369         }
370
371         return 0;
372 }
373
374 static int distro_bootmeth_efi_bind(struct udevice *dev)
375 {
376         struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
377
378         plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
379                 "EFI boot from an .efi file" : "EFI";
380
381         return 0;
382 }
383
384 static struct bootmeth_ops distro_efi_bootmeth_ops = {
385         .check          = distro_efi_check,
386         .read_bootflow  = distro_efi_read_bootflow,
387         .read_file      = bootmeth_common_read_file,
388         .boot           = distro_efi_boot,
389 };
390
391 static const struct udevice_id distro_efi_bootmeth_ids[] = {
392         { .compatible = "u-boot,distro-efi" },
393         { }
394 };
395
396 /* Put a number before 'efi' to provide a default ordering */
397 U_BOOT_DRIVER(bootmeth_4efi) = {
398         .name           = "bootmeth_efi",
399         .id             = UCLASS_BOOTMETH,
400         .of_match       = distro_efi_bootmeth_ids,
401         .ops            = &distro_efi_bootmeth_ops,
402         .bind           = distro_bootmeth_efi_bind,
403 };
This page took 0.07434 seconds and 4 git commands to generate.