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 EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL;
36static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
37
38#ifndef EFI_DEBUG
39static const char *prio_str[] = {
40	"error",
41	"not supported",
42	"good",
43	"better"
44};
45#endif
46
47/*
48 * probe_handle determines if the passed handle represents a logical partition
49 * if it does it uses each module in order to probe it and if successful it
50 * returns EFI_SUCCESS.
51 */
52static int
53probe_handle(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath)
54{
55	dev_info_t *devinfo;
56	EFI_BLOCK_IO *blkio;
57	EFI_DEVICE_PATH *devpath;
58	EFI_STATUS status;
59	UINTN i;
60	int preferred;
61
62	/* Figure out if we're dealing with an actual partition. */
63	status = OpenProtocolByHandle(h, &DevicePathGUID, (void **)&devpath);
64	if (status == EFI_UNSUPPORTED)
65		return (0);
66
67	if (status != EFI_SUCCESS) {
68		DPRINTF("\nFailed to query DevicePath (%lu)\n",
69		    EFI_ERROR_CODE(status));
70		return (-1);
71	}
72#ifdef EFI_DEBUG
73	{
74		CHAR16 *text = efi_devpath_name(devpath);
75		DPRINTF("probing: %S ", text);
76		efi_free_devpath_name(text);
77	}
78#endif
79	status = OpenProtocolByHandle(h, &BlockIoProtocolGUID, (void **)&blkio);
80	if (status == EFI_UNSUPPORTED)
81		return (0);
82
83	if (status != EFI_SUCCESS) {
84		DPRINTF("\nFailed to query BlockIoProtocol (%lu)\n",
85		    EFI_ERROR_CODE(status));
86		return (-1);
87	}
88
89	if (!blkio->Media->LogicalPartition)
90		return (0);
91
92	preferred = efi_devpath_same_disk(imgpath, devpath);
93
94	/* Run through each module, see if it can load this partition */
95	devinfo = malloc(sizeof(*devinfo));
96	if (devinfo == NULL) {
97		DPRINTF("\nFailed to allocate devinfo\n");
98		return (-1);
99	}
100	devinfo->dev = blkio;
101	devinfo->devpath = devpath;
102	devinfo->devhandle = h;
103	devinfo->preferred = preferred;
104	devinfo->next = NULL;
105
106	for (i = 0; i < num_boot_modules; i++) {
107		devinfo->devdata = NULL;
108
109		status = boot_modules[i]->probe(devinfo);
110		if (status == EFI_SUCCESS)
111			return (preferred + 1);
112	}
113	free(devinfo);
114
115	return (0);
116}
117
118/*
119 * load_loader attempts to load the loader image data.
120 *
121 * It tries each module and its respective devices, identified by mod->probe,
122 * in order until a successful load occurs at which point it returns EFI_SUCCESS
123 * and EFI_NOT_FOUND otherwise.
124 *
125 * Only devices which have preferred matching the preferred parameter are tried.
126 */
127static EFI_STATUS
128load_loader(const boot_module_t **modp, dev_info_t **devinfop, void **bufp,
129    size_t *bufsize, int preferred)
130{
131	UINTN i;
132	dev_info_t *dev;
133	const boot_module_t *mod;
134
135	for (i = 0; i < num_boot_modules; i++) {
136		mod = boot_modules[i];
137		for (dev = mod->devices(); dev != NULL; dev = dev->next) {
138			if (dev->preferred != preferred)
139				continue;
140
141			if (mod->load(PATH_LOADER_EFI, dev, bufp, bufsize) ==
142			    EFI_SUCCESS) {
143				*devinfop = dev;
144				*modp = mod;
145				return (EFI_SUCCESS);
146			}
147		}
148	}
149
150	return (EFI_NOT_FOUND);
151}
152
153void
154choice_protocol(EFI_HANDLE *handles, UINTN nhandles, EFI_DEVICE_PATH *imgpath)
155{
156	UINT16 boot_current;
157	size_t sz;
158	UINT16 boot_order[100];
159	unsigned i;
160	int rv;
161	EFI_STATUS status;
162	const boot_module_t *mod;
163	dev_info_t *dev;
164	void *loaderbuf;
165	size_t loadersize;
166
167	/* Report UEFI Boot Manager Protocol details */
168	boot_current = 0;
169	sz = sizeof(boot_current);
170	if (efi_global_getenv("BootCurrent", &boot_current, &sz) == EFI_SUCCESS) {
171		printf("   BootCurrent: %04x\n", boot_current);
172
173		sz = sizeof(boot_order);
174		if (efi_global_getenv("BootOrder", &boot_order, &sz) == EFI_SUCCESS) {
175			printf("   BootOrder:");
176			for (i = 0; i < sz / sizeof(boot_order[0]); i++)
177				printf(" %04x%s", boot_order[i],
178				    boot_order[i] == boot_current ? "[*]" : "");
179			printf("\n");
180		}
181	}
182
183#ifdef TEST_FAILURE
184	/*
185	 * For testing failover scenarios, it's nice to be able to fail fast.
186	 * Define TEST_FAILURE to create a boot1.efi that always fails after
187	 * reporting the boot manager protocol details.
188	 */
189	BS->Exit(IH, EFI_OUT_OF_RESOURCES, 0, NULL);
190#endif
191
192	/* Scan all partitions, probing with all modules. */
193	printf("   Probing %zu block devices...", nhandles);
194	DPRINTF("\n");
195	for (i = 0; i < nhandles; i++) {
196		rv = probe_handle(handles[i], imgpath);
197#ifdef EFI_DEBUG
198		printf("%c", "x.+*"[rv + 1]);
199#else
200		printf("%s\n", prio_str[rv + 1]);
201#endif
202	}
203	printf(" done\n");
204
205
206	/* Status summary. */
207	for (i = 0; i < num_boot_modules; i++) {
208		printf("    ");
209		boot_modules[i]->status();
210	}
211
212	status = load_loader(&mod, &dev, &loaderbuf, &loadersize, 1);
213	if (status != EFI_SUCCESS) {
214		status = load_loader(&mod, &dev, &loaderbuf, &loadersize, 0);
215		if (status != EFI_SUCCESS) {
216			printf("Failed to load '%s'\n", PATH_LOADER_EFI);
217			return;
218		}
219	}
220
221	try_boot(mod, dev, loaderbuf, loadersize);
222}
223