]>
Commit | Line | Data |
---|---|---|
476476e7 SG |
1 | /* |
2 | * Copyright (c) 2015 Google, Inc | |
3 | * | |
4 | * SPDX-License-Identifier: GPL-2.0+ | |
5 | * | |
6 | * EFI information obtained here: | |
7 | * http://wiki.phoenix.com/wiki/index.php/EFI_BOOT_SERVICES | |
8 | * | |
96a8d409 SG |
9 | * Loads a payload (U-Boot) within the EFI environment. This is built as an |
10 | * EFI application. It can be built either in 32-bit or 64-bit mode. | |
476476e7 SG |
11 | */ |
12 | ||
13 | #include <common.h> | |
14 | #include <debug_uart.h> | |
15 | #include <efi.h> | |
16 | #include <efi_api.h> | |
17 | #include <errno.h> | |
18 | #include <ns16550.h> | |
19 | #include <asm/cpu.h> | |
20 | #include <asm/io.h> | |
21 | #include <linux/err.h> | |
22 | #include <linux/types.h> | |
23 | ||
24 | DECLARE_GLOBAL_DATA_PTR; | |
25 | ||
26 | #ifndef CONFIG_X86 | |
27 | /* | |
28 | * Problem areas: | |
29 | * - putc() uses the ns16550 address directly and assumed I/O access. Many | |
30 | * platforms will use memory access | |
31 | * get_codeseg32() is only meaningful on x86 | |
32 | */ | |
33 | #error "This file needs to be ported for use on architectures" | |
34 | #endif | |
35 | ||
36 | static struct efi_priv *global_priv; | |
37 | static bool use_uart; | |
38 | ||
39 | struct __packed desctab_info { | |
40 | uint16_t limit; | |
41 | uint64_t addr; | |
42 | uint16_t pad; | |
43 | }; | |
44 | ||
45 | /* | |
46 | * EFI uses Unicode and we don't. The easiest way to get a sensible output | |
47 | * function is to use the U-Boot debug UART. We use EFI's console output | |
48 | * function where available, and assume the built-in UART after that. We rely | |
49 | * on EFI to set up the UART for us and just bring in the functions here. | |
50 | * This last bit is a bit icky, but it's only for debugging anyway. We could | |
51 | * build in ns16550.c with some effort, but this is a payload loader after | |
52 | * all. | |
53 | * | |
54 | * Note: We avoid using printf() so we don't need to bring in lib/vsprintf.c. | |
55 | * That would require some refactoring since we already build this for U-Boot. | |
56 | * Building an EFI shared library version would have to be a separate stem. | |
57 | * That might push us to using the SPL framework to build this stub. However | |
58 | * that would involve a round of EFI-specific changes in SPL. Worth | |
59 | * considering if we start needing more U-Boot functionality. Note that we | |
60 | * could then move get_codeseg32() to arch/x86/cpu/cpu.c. | |
61 | */ | |
97b05973 | 62 | void _debug_uart_init(void) |
476476e7 SG |
63 | { |
64 | } | |
65 | ||
66 | void putc(const char ch) | |
67 | { | |
075bb5c6 BM |
68 | if (ch == '\n') |
69 | putc('\r'); | |
70 | ||
476476e7 SG |
71 | if (use_uart) { |
72 | NS16550_t com_port = (NS16550_t)0x3f8; | |
73 | ||
74 | while ((inb((ulong)&com_port->lsr) & UART_LSR_THRE) == 0) | |
75 | ; | |
76 | outb(ch, (ulong)&com_port->thr); | |
77 | } else { | |
78 | efi_putc(global_priv, ch); | |
79 | } | |
476476e7 SG |
80 | } |
81 | ||
82 | void puts(const char *str) | |
83 | { | |
84 | while (*str) | |
85 | putc(*str++); | |
86 | } | |
87 | ||
88 | static void _debug_uart_putc(int ch) | |
89 | { | |
90 | putc(ch); | |
91 | } | |
92 | ||
93 | DEBUG_UART_FUNCS | |
94 | ||
95 | void *memcpy(void *dest, const void *src, size_t size) | |
96 | { | |
97 | unsigned char *dptr = dest; | |
98 | const unsigned char *ptr = src; | |
99 | const unsigned char *end = src + size; | |
100 | ||
101 | while (ptr < end) | |
102 | *dptr++ = *ptr++; | |
103 | ||
104 | return dest; | |
105 | } | |
106 | ||
107 | void *memset(void *inptr, int ch, size_t size) | |
108 | { | |
109 | char *ptr = inptr; | |
110 | char *end = ptr + size; | |
111 | ||
112 | while (ptr < end) | |
113 | *ptr++ = ch; | |
114 | ||
115 | return ptr; | |
116 | } | |
117 | ||
118 | static void jump_to_uboot(ulong cs32, ulong addr, ulong info) | |
119 | { | |
120 | #ifdef CONFIG_EFI_STUB_32BIT | |
121 | /* | |
122 | * U-Boot requires these parameters in registers, not on the stack. | |
123 | * See _x86boot_start() for this code. | |
124 | */ | |
125 | typedef void (*func_t)(int bist, int unused, ulong info) | |
126 | __attribute__((regparm(3))); | |
127 | ||
128 | ((func_t)addr)(0, 0, info); | |
129 | #else | |
96a8d409 | 130 | cpu_call32(cs32, CONFIG_SYS_TEXT_BASE, info); |
476476e7 SG |
131 | #endif |
132 | } | |
133 | ||
96a8d409 | 134 | #ifdef CONFIG_EFI_STUB_64BIT |
476476e7 SG |
135 | static void get_gdt(struct desctab_info *info) |
136 | { | |
137 | asm volatile ("sgdt %0" : : "m"(*info) : "memory"); | |
138 | } | |
96a8d409 | 139 | #endif |
476476e7 SG |
140 | |
141 | static inline unsigned long read_cr3(void) | |
142 | { | |
143 | unsigned long val; | |
144 | ||
145 | asm volatile("mov %%cr3,%0" : "=r" (val) : : "memory"); | |
146 | return val; | |
147 | } | |
148 | ||
149 | /** | |
150 | * get_codeseg32() - Find the code segment to use for 32-bit code | |
151 | * | |
152 | * U-Boot only works in 32-bit mode at present, so when booting from 64-bit | |
153 | * EFI we must first change to 32-bit mode. To do this we need to find the | |
154 | * correct code segment to use (an entry in the Global Descriptor Table). | |
155 | * | |
156 | * @return code segment GDT offset, or 0 for 32-bit EFI, -ENOENT if not found | |
157 | */ | |
158 | static int get_codeseg32(void) | |
159 | { | |
160 | int cs32 = 0; | |
161 | ||
96a8d409 SG |
162 | #ifdef CONFIG_EFI_STUB_64BIT |
163 | struct desctab_info gdt; | |
164 | uint64_t *ptr; | |
165 | int i; | |
166 | ||
167 | get_gdt(&gdt); | |
168 | for (ptr = (uint64_t *)(unsigned long)gdt.addr, i = 0; i < gdt.limit; | |
169 | i += 8, ptr++) { | |
170 | uint64_t desc = *ptr; | |
171 | uint64_t base, limit; | |
172 | ||
173 | /* | |
174 | * Check that the target U-Boot jump address is within the | |
175 | * selector and that the selector is of the right type. | |
176 | */ | |
177 | base = ((desc >> GDT_BASE_LOW_SHIFT) & GDT_BASE_LOW_MASK) | | |
178 | ((desc >> GDT_BASE_HIGH_SHIFT) & GDT_BASE_HIGH_MASK) | |
179 | << 16; | |
180 | limit = ((desc >> GDT_LIMIT_LOW_SHIFT) & GDT_LIMIT_LOW_MASK) | | |
181 | ((desc >> GDT_LIMIT_HIGH_SHIFT) & GDT_LIMIT_HIGH_MASK) | |
182 | << 16; | |
183 | base <<= 12; /* 4KB granularity */ | |
184 | limit <<= 12; | |
185 | if ((desc & GDT_PRESENT) && (desc && GDT_NOTSYS) && | |
186 | !(desc & GDT_LONG) && (desc & GDT_4KB) && | |
187 | (desc & GDT_32BIT) && (desc & GDT_CODE) && | |
188 | CONFIG_SYS_TEXT_BASE > base && | |
189 | CONFIG_SYS_TEXT_BASE + CONFIG_SYS_MONITOR_LEN < limit | |
190 | ) { | |
191 | cs32 = i; | |
192 | break; | |
193 | } | |
194 | } | |
195 | ||
196 | #ifdef DEBUG | |
197 | puts("\ngdt: "); | |
198 | printhex8(gdt.limit); | |
199 | puts(", addr: "); | |
200 | printhex8(gdt.addr >> 32); | |
201 | printhex8(gdt.addr); | |
202 | for (i = 0; i < gdt.limit; i += 8) { | |
203 | uint32_t *ptr = (uint32_t *)((unsigned long)gdt.addr + i); | |
204 | ||
205 | puts("\n"); | |
206 | printhex2(i); | |
207 | puts(": "); | |
208 | printhex8(ptr[1]); | |
209 | puts(" "); | |
210 | printhex8(ptr[0]); | |
211 | } | |
212 | puts("\n "); | |
213 | puts("32-bit code segment: "); | |
214 | printhex2(cs32); | |
215 | puts("\n "); | |
216 | ||
217 | puts("page_table: "); | |
218 | printhex8(read_cr3()); | |
219 | puts("\n "); | |
220 | #endif | |
221 | if (!cs32) { | |
222 | puts("Can't find 32-bit code segment\n"); | |
223 | return -ENOENT; | |
224 | } | |
225 | #endif | |
226 | ||
476476e7 SG |
227 | return cs32; |
228 | } | |
229 | ||
230 | static int setup_info_table(struct efi_priv *priv, int size) | |
231 | { | |
232 | struct efi_info_hdr *info; | |
233 | efi_status_t ret; | |
234 | ||
235 | /* Get some memory for our info table */ | |
236 | priv->info_size = size; | |
237 | info = efi_malloc(priv, priv->info_size, &ret); | |
238 | if (ret) { | |
239 | printhex2(ret); | |
240 | puts(" No memory for info table: "); | |
241 | return ret; | |
242 | } | |
243 | ||
244 | memset(info, '\0', sizeof(*info)); | |
245 | info->version = EFI_TABLE_VERSION; | |
246 | info->hdr_size = sizeof(*info); | |
247 | priv->info = info; | |
248 | priv->next_hdr = (char *)info + info->hdr_size; | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | static void add_entry_addr(struct efi_priv *priv, enum efi_entry_t type, | |
254 | void *ptr1, int size1, void *ptr2, int size2) | |
255 | { | |
256 | struct efi_entry_hdr *hdr = priv->next_hdr; | |
257 | ||
258 | hdr->type = type; | |
259 | hdr->size = size1 + size2; | |
260 | hdr->addr = 0; | |
261 | hdr->link = ALIGN(sizeof(*hdr) + hdr->size, 16); | |
262 | priv->next_hdr += hdr->link; | |
263 | memcpy(hdr + 1, ptr1, size1); | |
264 | memcpy((void *)(hdr + 1) + size1, ptr2, size2); | |
265 | priv->info->total_size = (ulong)priv->next_hdr - (ulong)priv->info; | |
266 | } | |
267 | ||
268 | /** | |
269 | * efi_main() - Start an EFI image | |
270 | * | |
271 | * This function is called by our EFI start-up code. It handles running | |
272 | * U-Boot. If it returns, EFI will continue. | |
273 | */ | |
274 | efi_status_t efi_main(efi_handle_t image, struct efi_system_table *sys_table) | |
275 | { | |
276 | struct efi_priv local_priv, *priv = &local_priv; | |
277 | struct efi_boot_services *boot = sys_table->boottime; | |
278 | struct efi_mem_desc *desc; | |
279 | struct efi_entry_memmap map; | |
280 | ulong key, desc_size, size; | |
281 | efi_status_t ret; | |
282 | u32 version; | |
283 | int cs32; | |
284 | ||
285 | ret = efi_init(priv, "Payload", image, sys_table); | |
286 | if (ret) { | |
287 | printhex2(ret); puts(" efi_init() failed\n"); | |
288 | return ret; | |
289 | } | |
290 | global_priv = priv; | |
291 | ||
292 | cs32 = get_codeseg32(); | |
293 | if (cs32 < 0) | |
294 | return EFI_UNSUPPORTED; | |
295 | ||
296 | /* Get the memory map so we can switch off EFI */ | |
297 | size = 0; | |
298 | ret = boot->get_memory_map(&size, NULL, &key, &desc_size, &version); | |
299 | if (ret != EFI_BUFFER_TOO_SMALL) { | |
300 | printhex2(BITS_PER_LONG); | |
301 | printhex2(ret); | |
302 | puts(" No memory map\n"); | |
303 | return ret; | |
304 | } | |
305 | size += 1024; /* Since doing a malloc() may change the memory map! */ | |
306 | desc = efi_malloc(priv, size, &ret); | |
307 | if (!desc) { | |
308 | printhex2(ret); | |
309 | puts(" No memory for memory descriptor: "); | |
310 | return ret; | |
311 | } | |
312 | ret = setup_info_table(priv, size + 128); | |
313 | if (ret) | |
314 | return ret; | |
315 | ||
316 | ret = boot->get_memory_map(&size, desc, &key, &desc_size, &version); | |
317 | if (ret) { | |
318 | printhex2(ret); | |
319 | puts(" Can't get memory map\n"); | |
320 | return ret; | |
321 | } | |
322 | ||
323 | ret = boot->exit_boot_services(image, key); | |
324 | if (ret) { | |
325 | /* | |
326 | * Unfortunately it happens that we cannot exit boot services | |
327 | * the first time. But the second time it work. I don't know | |
328 | * why but this seems to be a repeatable problem. To get | |
329 | * around it, just try again. | |
330 | */ | |
331 | printhex2(ret); | |
332 | puts(" Can't exit boot services\n"); | |
333 | size = sizeof(desc); | |
334 | ret = boot->get_memory_map(&size, desc, &key, &desc_size, | |
335 | &version); | |
336 | if (ret) { | |
337 | printhex2(ret); | |
338 | puts(" Can't get memory map\n"); | |
339 | return ret; | |
340 | } | |
341 | ret = boot->exit_boot_services(image, key); | |
342 | if (ret) { | |
343 | printhex2(ret); | |
344 | puts(" Can't exit boot services 2\n"); | |
345 | return ret; | |
346 | } | |
347 | } | |
348 | ||
349 | map.version = version; | |
350 | map.desc_size = desc_size; | |
351 | add_entry_addr(priv, EFIET_MEMORY_MAP, &map, sizeof(map), desc, size); | |
352 | add_entry_addr(priv, EFIET_END, NULL, 0, 0, 0); | |
353 | ||
354 | /* The EFI UART won't work now, switch to a debug one */ | |
355 | use_uart = true; | |
356 | ||
357 | memcpy((void *)CONFIG_SYS_TEXT_BASE, _binary_u_boot_dtb_bin_start, | |
358 | (ulong)_binary_u_boot_dtb_bin_end - | |
359 | (ulong)_binary_u_boot_dtb_bin_start); | |
360 | ||
361 | #ifdef DEBUG | |
362 | puts("EFI table at "); | |
363 | printhex8((ulong)priv->info); | |
364 | puts(" size "); | |
365 | printhex8(priv->info->total_size); | |
366 | #endif | |
367 | putc('\n'); | |
368 | jump_to_uboot(cs32, CONFIG_SYS_TEXT_BASE, (ulong)priv->info); | |
369 | ||
370 | return EFI_LOAD_ERROR; | |
371 | } |