]>
Commit | Line | Data |
---|---|---|
9975fe96 RC |
1 | /* |
2 | * EFI utils | |
3 | * | |
4 | * Copyright (c) 2017 Rob Clark | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0+ | |
7 | */ | |
8 | ||
9 | #include <common.h> | |
10 | #include <charset.h> | |
11 | #include <malloc.h> | |
12 | #include <efi_loader.h> | |
13 | ||
14 | static const struct efi_boot_services *bs; | |
15 | static const struct efi_runtime_services *rs; | |
16 | ||
17 | #define LOAD_OPTION_ACTIVE 0x00000001 | |
18 | #define LOAD_OPTION_FORCE_RECONNECT 0x00000002 | |
19 | #define LOAD_OPTION_HIDDEN 0x00000008 | |
20 | ||
21 | /* | |
22 | * bootmgr implements the logic of trying to find a payload to boot | |
23 | * based on the BootOrder + BootXXXX variables, and then loading it. | |
24 | * | |
25 | * TODO detecting a special key held (f9?) and displaying a boot menu | |
26 | * like you would get on a PC would be clever. | |
27 | * | |
28 | * TODO if we had a way to write and persist variables after the OS | |
29 | * has started, we'd also want to check OsIndications to see if we | |
30 | * should do normal or recovery boot. | |
31 | */ | |
32 | ||
33 | ||
34 | /* | |
35 | * See section 3.1.3 in the v2.7 UEFI spec for more details on | |
36 | * the layout of EFI_LOAD_OPTION. In short it is: | |
37 | * | |
38 | * typedef struct _EFI_LOAD_OPTION { | |
39 | * UINT32 Attributes; | |
40 | * UINT16 FilePathListLength; | |
41 | * // CHAR16 Description[]; <-- variable length, NULL terminated | |
42 | * // EFI_DEVICE_PATH_PROTOCOL FilePathList[]; <-- FilePathListLength bytes | |
43 | * // UINT8 OptionalData[]; | |
44 | * } EFI_LOAD_OPTION; | |
45 | */ | |
46 | struct load_option { | |
47 | u32 attributes; | |
48 | u16 file_path_length; | |
49 | u16 *label; | |
50 | struct efi_device_path *file_path; | |
51 | u8 *optional_data; | |
52 | }; | |
53 | ||
54 | /* parse an EFI_LOAD_OPTION, as described above */ | |
55 | static void parse_load_option(struct load_option *lo, void *ptr) | |
56 | { | |
57 | lo->attributes = *(u32 *)ptr; | |
58 | ptr += sizeof(u32); | |
59 | ||
60 | lo->file_path_length = *(u16 *)ptr; | |
61 | ptr += sizeof(u16); | |
62 | ||
63 | lo->label = ptr; | |
64 | ptr += (utf16_strlen(lo->label) + 1) * 2; | |
65 | ||
66 | lo->file_path = ptr; | |
67 | ptr += lo->file_path_length; | |
68 | ||
69 | lo->optional_data = ptr; | |
70 | } | |
71 | ||
72 | /* free() the result */ | |
73 | static void *get_var(u16 *name, const efi_guid_t *vendor, | |
74 | unsigned long *size) | |
75 | { | |
76 | efi_guid_t *v = (efi_guid_t *)vendor; | |
77 | efi_status_t ret; | |
78 | void *buf = NULL; | |
79 | ||
80 | *size = 0; | |
81 | EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); | |
82 | if (ret == EFI_BUFFER_TOO_SMALL) { | |
83 | buf = malloc(*size); | |
84 | EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); | |
85 | } | |
86 | ||
87 | if (ret != EFI_SUCCESS) { | |
88 | free(buf); | |
89 | *size = 0; | |
90 | return NULL; | |
91 | } | |
92 | ||
93 | return buf; | |
94 | } | |
95 | ||
96 | /* | |
97 | * Attempt to load load-option number 'n', returning device_path and file_path | |
98 | * if successful. This checks that the EFI_LOAD_OPTION is active (enabled) | |
99 | * and that the specified file to boot exists. | |
100 | */ | |
101 | static void *try_load_entry(uint16_t n, struct efi_device_path **device_path, | |
102 | struct efi_device_path **file_path) | |
103 | { | |
104 | struct load_option lo; | |
105 | u16 varname[] = L"Boot0000"; | |
106 | u16 hexmap[] = L"0123456789ABCDEF"; | |
107 | void *load_option, *image = NULL; | |
108 | unsigned long size; | |
109 | ||
110 | varname[4] = hexmap[(n & 0xf000) >> 12]; | |
111 | varname[5] = hexmap[(n & 0x0f00) >> 8]; | |
112 | varname[6] = hexmap[(n & 0x00f0) >> 4]; | |
113 | varname[7] = hexmap[(n & 0x000f) >> 0]; | |
114 | ||
115 | load_option = get_var(varname, &efi_global_variable_guid, &size); | |
116 | if (!load_option) | |
117 | return NULL; | |
118 | ||
119 | parse_load_option(&lo, load_option); | |
120 | ||
121 | if (lo.attributes & LOAD_OPTION_ACTIVE) { | |
122 | efi_status_t ret; | |
123 | u16 *str = NULL; | |
124 | ||
125 | debug("%s: trying to load \"%ls\" from: %ls\n", __func__, | |
126 | lo.label, (str = efi_dp_str(lo.file_path))); | |
127 | efi_free_pool(str); | |
128 | ||
129 | ret = efi_load_image_from_path(lo.file_path, &image); | |
130 | ||
131 | if (ret != EFI_SUCCESS) | |
132 | goto error; | |
133 | ||
134 | printf("Booting: %ls\n", lo.label); | |
135 | efi_dp_split_file_path(lo.file_path, device_path, file_path); | |
136 | } | |
137 | ||
138 | error: | |
139 | free(load_option); | |
140 | ||
141 | return image; | |
142 | } | |
143 | ||
144 | /* | |
145 | * Attempt to load, in the order specified by BootOrder EFI variable, the | |
146 | * available load-options, finding and returning the first one that can | |
147 | * be loaded successfully. | |
148 | */ | |
149 | void *efi_bootmgr_load(struct efi_device_path **device_path, | |
150 | struct efi_device_path **file_path) | |
151 | { | |
152 | uint16_t *bootorder; | |
153 | unsigned long size; | |
154 | void *image = NULL; | |
155 | int i, num; | |
156 | ||
157 | __efi_entry_check(); | |
158 | ||
159 | bs = systab.boottime; | |
160 | rs = systab.runtime; | |
161 | ||
162 | bootorder = get_var(L"BootOrder", &efi_global_variable_guid, &size); | |
163 | if (!bootorder) | |
164 | goto error; | |
165 | ||
166 | num = size / sizeof(uint16_t); | |
167 | for (i = 0; i < num; i++) { | |
168 | debug("%s: trying to load Boot%04X\n", __func__, bootorder[i]); | |
169 | image = try_load_entry(bootorder[i], device_path, file_path); | |
170 | if (image) | |
171 | break; | |
172 | } | |
173 | ||
174 | free(bootorder); | |
175 | ||
176 | error: | |
177 | __efi_exit_check(); | |
178 | ||
179 | return image; | |
180 | } |