1/*-
2 * Copyright (c) 1998 Robert Nordier
3 * All rights reserved.
4 * Copyright (c) 2001 Robert Drehmel
5 * All rights reserved.
6 * Copyright (c) 2014 Nathan Whitehorn
7 * All rights reserved.
8 * Copyright (c) 2015 Eric McCorkle
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms are freely
12 * permitted provided that the above copyright notice and this
13 * paragraph and the following disclaimer are duplicated in all
14 * such forms.
15 *
16 * This software is provided "AS IS" and without any express or
17 * implied warranties, including, without limitation, the implied
18 * warranties of merchantability and fitness for a particular
19 * purpose.
20 */
21
22#include <sys/cdefs.h>
23__FBSDID("$FreeBSD$");
24
25#include <sys/param.h>
26#include <machine/elf.h>
27#include <machine/stdarg.h>
28#include <stand.h>
29
30#include <efi.h>
31#include <eficonsctl.h>
32#include <efichar.h>
33
34#include "boot_module.h"
35#include "paths.h"
36#include "proto.h"
37
38static void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3);
39
40const boot_module_t *boot_modules[] =
41{
42#ifdef EFI_ZFS_BOOT
43	&zfs_module,
44#endif
45#ifdef EFI_UFS_BOOT
46	&ufs_module
47#endif
48};
49const UINTN num_boot_modules = nitems(boot_modules);
50
51static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL;
52static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
53static EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
54static EFI_GUID ConsoleControlGUID = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
55
56static EFI_PHYSICAL_ADDRESS heap;
57static UINTN heapsize;
58
59/*
60 * try_boot only returns if it fails to load the loader. If it succeeds
61 * it simply boots, otherwise it returns the status of last EFI call.
62 */
63EFI_STATUS
64try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize)
65{
66	size_t bufsize, cmdsize;
67	void *buf;
68	char *cmd;
69	EFI_HANDLE loaderhandle;
70	EFI_LOADED_IMAGE *loaded_image;
71	EFI_STATUS status;
72
73	/*
74	 * Read in and parse the command line from /boot.config or /boot/config,
75	 * if present. We'll pass it the next stage via a simple ASCII
76	 * string. loader.efi has a hack for ASCII strings, so we'll use that to
77	 * keep the size down here. We only try to read the alternate file if
78	 * we get EFI_NOT_FOUND because all other errors mean that the boot_module
79	 * had troubles with the filesystem. We could return early, but we'll let
80	 * loading the actual kernel sort all that out. Since these files are
81	 * optional, we don't report errors in trying to read them.
82	 */
83	cmd = NULL;
84	cmdsize = 0;
85	status = mod->load(PATH_DOTCONFIG, dev, &buf, &bufsize);
86	if (status == EFI_NOT_FOUND)
87		status = mod->load(PATH_CONFIG, dev, &buf, &bufsize);
88	if (status == EFI_SUCCESS) {
89		cmdsize = bufsize + 1;
90		cmd = malloc(cmdsize);
91		if (cmd == NULL)
92			goto errout;
93		memcpy(cmd, buf, bufsize);
94		cmd[bufsize] = '\0';
95		free(buf);
96		buf = NULL;
97	}
98
99	if ((status = BS->LoadImage(TRUE, IH, efi_devpath_last_node(dev->devpath),
100	    loaderbuf, loadersize, &loaderhandle)) != EFI_SUCCESS) {
101		printf("Failed to load image provided by %s, size: %zu, (%lu)\n",
102		     mod->name, loadersize, EFI_ERROR_CODE(status));
103		goto errout;
104	}
105
106	status = OpenProtocolByHandle(loaderhandle, &LoadedImageGUID,
107	    (void **)&loaded_image);
108	if (status != EFI_SUCCESS) {
109		printf("Failed to query LoadedImage provided by %s (%lu)\n",
110		    mod->name, EFI_ERROR_CODE(status));
111		goto errout;
112	}
113
114	if (cmd != NULL)
115		printf("    command args: %s\n", cmd);
116
117	loaded_image->DeviceHandle = dev->devhandle;
118	loaded_image->LoadOptionsSize = cmdsize;
119	loaded_image->LoadOptions = cmd;
120
121	DPRINTF("Starting '%s' in 5 seconds...", PATH_LOADER_EFI);
122	DSTALL(1000000);
123	DPRINTF(".");
124	DSTALL(1000000);
125	DPRINTF(".");
126	DSTALL(1000000);
127	DPRINTF(".");
128	DSTALL(1000000);
129	DPRINTF(".");
130	DSTALL(1000000);
131	DPRINTF(".\n");
132
133	if ((status = BS->StartImage(loaderhandle, NULL, NULL)) !=
134	    EFI_SUCCESS) {
135		printf("Failed to start image provided by %s (%lu)\n",
136		    mod->name, EFI_ERROR_CODE(status));
137		loaded_image->LoadOptionsSize = 0;
138		loaded_image->LoadOptions = NULL;
139	}
140
141errout:
142	if (cmd != NULL)
143		free(cmd);
144	if (buf != NULL)
145		free(buf);
146	if (loaderbuf != NULL)
147		free(loaderbuf);
148
149	return (status);
150}
151
152EFI_STATUS
153efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab)
154{
155	EFI_HANDLE *handles;
156	EFI_LOADED_IMAGE *img;
157	EFI_DEVICE_PATH *imgpath;
158	EFI_STATUS status;
159	EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
160	SIMPLE_TEXT_OUTPUT_INTERFACE *conout = NULL;
161	UINTN i, hsize, nhandles;
162	CHAR16 *text;
163
164	/* Basic initialization*/
165	ST = Xsystab;
166	IH = Ximage;
167	BS = ST->BootServices;
168	RS = ST->RuntimeServices;
169
170	heapsize = 64 * 1024 * 1024;
171	status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
172	    EFI_SIZE_TO_PAGES(heapsize), &heap);
173	if (status != EFI_SUCCESS) {
174		ST->ConOut->OutputString(ST->ConOut,
175		    __DECONST(CHAR16 *,
176		    L"Failed to allocate memory for heap.\r\n"));
177		BS->Exit(IH, status, 0, NULL);
178	}
179
180	setheap((void *)(uintptr_t)heap, (void *)(uintptr_t)(heap + heapsize));
181
182	/* Set up the console, so printf works. */
183	status = BS->LocateProtocol(&ConsoleControlGUID, NULL,
184	    (VOID **)&ConsoleControl);
185	if (status == EFI_SUCCESS)
186		(void)ConsoleControl->SetMode(ConsoleControl,
187		    EfiConsoleControlScreenText);
188	/*
189	 * Reset the console enable the cursor. Later we'll choose a better
190	 * console size through GOP/UGA.
191	 */
192	conout = ST->ConOut;
193	conout->Reset(conout, TRUE);
194	/* Explicitly set conout to mode 0, 80x25 */
195	conout->SetMode(conout, 0);
196	conout->EnableCursor(conout, TRUE);
197	conout->ClearScreen(conout);
198
199	printf("\n>> FreeBSD EFI boot block\n");
200	printf("   Loader path: %s\n\n", PATH_LOADER_EFI);
201	printf("   Initializing modules:");
202	for (i = 0; i < num_boot_modules; i++) {
203		printf(" %s", boot_modules[i]->name);
204		if (boot_modules[i]->init != NULL)
205			boot_modules[i]->init();
206	}
207	putchar('\n');
208
209	/* Fetch all the block I/O handles, we have to search through them later */
210	hsize = 0;
211	BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, NULL,
212	    &hsize, NULL);
213	handles = malloc(hsize);
214	if (handles == NULL)
215		efi_panic(EFI_OUT_OF_RESOURCES, "Failed to allocate %d handles\n",
216		    hsize);
217	status = BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID,
218	    NULL, &hsize, handles);
219	if (status != EFI_SUCCESS)
220		efi_panic(status, "Failed to get device handles\n");
221	nhandles = hsize / sizeof(*handles);
222
223	/* Determine the devpath of our image so we can prefer it. */
224	status = OpenProtocolByHandle(IH, &LoadedImageGUID, (void **)&img);
225	imgpath = NULL;
226	if (status == EFI_SUCCESS) {
227		text = efi_devpath_name(img->FilePath);
228		if (text != NULL) {
229			printf("   Load Path: %S\n", text);
230			efi_setenv_freebsd_wcs("Boot1Path", text);
231			efi_free_devpath_name(text);
232		}
233
234		status = OpenProtocolByHandle(img->DeviceHandle,
235		    &DevicePathGUID, (void **)&imgpath);
236		if (status != EFI_SUCCESS) {
237			DPRINTF("Failed to get image DevicePath (%lu)\n",
238			    EFI_ERROR_CODE(status));
239		} else {
240			text = efi_devpath_name(imgpath);
241			if (text != NULL) {
242				printf("   Load Device: %S\n", text);
243				efi_setenv_freebsd_wcs("Boot1Dev", text);
244				efi_free_devpath_name(text);
245			}
246		}
247	}
248
249	choice_protocol(handles, nhandles, imgpath);
250
251	/* If we get here, we're out of luck... */
252	efi_panic(EFI_LOAD_ERROR, "No bootable partitions found!");
253}
254
255/*
256 * add_device adds a device to the passed devinfo list.
257 */
258void
259add_device(dev_info_t **devinfop, dev_info_t *devinfo)
260{
261	dev_info_t *dev;
262
263	if (*devinfop == NULL) {
264		*devinfop = devinfo;
265		return;
266	}
267
268	for (dev = *devinfop; dev->next != NULL; dev = dev->next)
269		;
270
271	dev->next = devinfo;
272}
273
274void
275efi_exit(EFI_STATUS s)
276{
277
278	BS->FreePages(heap, EFI_SIZE_TO_PAGES(heapsize));
279	BS->Exit(IH, s, 0, NULL);
280}
281
282void
283exit(int error __unused)
284{
285	efi_exit(EFI_LOAD_ERROR);
286}
287
288/*
289 * OK. We totally give up. Exit back to EFI with a sensible status so
290 * it can try the next option on the list.
291 */
292static void
293efi_panic(EFI_STATUS s, const char *fmt, ...)
294{
295	va_list ap;
296
297	printf("panic: ");
298	va_start(ap, fmt);
299	vprintf(fmt, ap);
300	va_end(ap);
301	printf("\n");
302
303	efi_exit(s);
304}
305
306int getchar(void)
307{
308	return (-1);
309}
310
311void
312putchar(int c)
313{
314	CHAR16 buf[2];
315
316	if (c == '\n') {
317		buf[0] = '\r';
318		buf[1] = 0;
319		ST->ConOut->OutputString(ST->ConOut, buf);
320	}
321	buf[0] = c;
322	buf[1] = 0;
323	ST->ConOut->OutputString(ST->ConOut, buf);
324}
325