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