xref: /freebsd/stand/efi/boot1/boot1.c (revision 61e21613)
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 
35 static void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3);
36 
37 const 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 };
46 const UINTN num_boot_modules = nitems(boot_modules);
47 
48 static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL;
49 static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
50 static EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
51 static EFI_GUID ConsoleControlGUID = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
52 
53 static EFI_PHYSICAL_ADDRESS heap;
54 static 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  */
60 EFI_STATUS
61 try_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 
158 errout:
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 
169 EFI_STATUS
170 efi_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  */
275 void
276 add_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 
291 void
292 efi_exit(EFI_STATUS s)
293 {
294 
295 	BS->FreePages(heap, EFI_SIZE_TO_PAGES(heapsize));
296 	BS->Exit(IH, s, 0, NULL);
297 }
298 
299 void
300 exit(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  */
309 static void
310 efi_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 
323 int getchar(void)
324 {
325 	return (-1);
326 }
327 
328 void
329 putchar(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