Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/stand/efi/loader/arch/amd64/multiboot2.c
106465 views
1
/*-
2
* Copyright (c) 2021 Roger Pau MonnĂ© <[email protected]>
3
* All rights reserved.
4
*
5
* Redistribution and use in source and binary forms, with or without
6
* modification, are permitted provided that the following conditions
7
* are met:
8
* 1. Redistributions of source code must retain the above copyright
9
* notice, this list of conditions and the following disclaimer.
10
* 2. Redistributions in binary form must reproduce the above copyright
11
* notice, this list of conditions and the following disclaimer in the
12
* documentation and/or other materials provided with the distribution.
13
*
14
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
* SUCH DAMAGE.
25
*/
26
27
/*
28
* This multiboot2 implementation only implements a subset of the full
29
* multiboot2 specification in order to be able to boot Xen and a
30
* FreeBSD Dom0. Trying to use it to boot other multiboot2 compliant
31
* kernels will most surely fail.
32
*
33
* The full multiboot specification can be found here:
34
* https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html
35
*/
36
37
#include <sys/param.h>
38
#include <sys/exec.h>
39
#include <sys/linker.h>
40
#include <sys/module.h>
41
#include <sys/stdint.h>
42
#define _MACHINE_ELF_WANT_32BIT
43
#include <machine/elf.h>
44
#include <machine/metadata.h>
45
#include <string.h>
46
#include <stand.h>
47
48
#include <efi.h>
49
#include <efilib.h>
50
51
#include "bootstrap.h"
52
#include "multiboot2.h"
53
#include "loader_efi.h"
54
#include "modinfo.h"
55
56
extern int elf32_loadfile_raw(char *filename, uint64_t dest,
57
struct preloaded_file **result, int multiboot);
58
extern int elf64_load_modmetadata(struct preloaded_file *fp, uint64_t dest);
59
extern int elf64_obj_loadfile(char *filename, uint64_t dest,
60
struct preloaded_file **result);
61
62
extern void multiboot2_exec(void *entry, uint64_t multiboot_info,
63
uint64_t stack);
64
65
/*
66
* Multiboot2 header information to pass between the loading and the exec
67
* functions.
68
*/
69
struct mb2hdr {
70
uint32_t efi64_entry;
71
};
72
73
static int
74
loadfile(char *filename, uint64_t dest, struct preloaded_file **result)
75
{
76
unsigned int i;
77
int error, fd;
78
void *header_search = NULL;
79
void *multiboot = NULL;
80
ssize_t search_size;
81
struct multiboot_header *header;
82
struct mb2hdr hdr;
83
bool keep_bs = false;
84
85
/*
86
* Read MULTIBOOT_SEARCH size in order to search for the
87
* multiboot magic header.
88
*/
89
if (filename == NULL)
90
return (EFTYPE);
91
if ((fd = open(filename, O_RDONLY)) == -1)
92
return (errno);
93
header_search = malloc(MULTIBOOT_SEARCH);
94
if (header_search == NULL) {
95
error = ENOMEM;
96
goto out;
97
}
98
search_size = read(fd, header_search, MULTIBOOT_SEARCH);
99
100
for (i = 0; i < search_size; i += MULTIBOOT_HEADER_ALIGN) {
101
header = header_search + i;
102
if (header->magic == MULTIBOOT2_HEADER_MAGIC)
103
break;
104
}
105
106
if (i >= search_size) {
107
error = EFTYPE;
108
goto out;
109
}
110
111
/* Valid multiboot header has been found, validate checksum */
112
if (header->magic + header->architecture + header->header_length +
113
header->checksum != 0) {
114
printf("Multiboot checksum failed, magic: %#x "
115
"architecture: %#x header_length %#x checksum: %#x\n",
116
header->magic, header->architecture, header->header_length,
117
header->checksum);
118
error = EFTYPE;
119
goto out;
120
}
121
122
if (header->architecture != MULTIBOOT2_ARCHITECTURE_I386) {
123
printf("Unsupported architecture: %#x\n",
124
header->architecture);
125
error = EFTYPE;
126
goto out;
127
}
128
129
multiboot = malloc(header->header_length - sizeof(*header));
130
error = lseek(fd, i + sizeof(*header), SEEK_SET);
131
if (error != i + sizeof(*header)) {
132
printf("Unable to set file pointer to header location: %d\n",
133
error);
134
goto out;
135
}
136
search_size = read(fd, multiboot,
137
header->header_length - sizeof(*header));
138
139
bzero(&hdr, sizeof(hdr));
140
for (i = 0; i < search_size; ) {
141
struct multiboot_header_tag *tag;
142
struct multiboot_header_tag_entry_address *entry;
143
struct multiboot_header_tag_information_request *req;
144
unsigned int j;
145
146
tag = multiboot + i;
147
148
switch(tag->type) {
149
case MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST:
150
req = (void *)tag;
151
for (j = 0;
152
j < (tag->size - sizeof(*tag)) / sizeof(uint32_t);
153
j++) {
154
switch (req->requests[j]) {
155
case MULTIBOOT_TAG_TYPE_MMAP:
156
case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
157
/* Only applicable to BIOS. */
158
break;
159
160
case MULTIBOOT_TAG_TYPE_EFI_BS:
161
case MULTIBOOT_TAG_TYPE_EFI64:
162
case MULTIBOOT_TAG_TYPE_EFI64_IH:
163
/* Tags unconditionally added. */
164
break;
165
166
default:
167
if (req->flags &
168
MULTIBOOT_HEADER_TAG_OPTIONAL)
169
break;
170
171
printf(
172
"Unknown non-optional information request %u\n",
173
req->requests[j]);
174
error = EINVAL;
175
goto out;
176
}
177
}
178
break;
179
180
case MULTIBOOT_HEADER_TAG_EFI_BS:
181
/* Never shut down BS. */
182
keep_bs = true;
183
break;
184
185
case MULTIBOOT_HEADER_TAG_MODULE_ALIGN:
186
/* We will align modules by default already. */
187
case MULTIBOOT_HEADER_TAG_END:
188
break;
189
190
case MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64:
191
entry = (void *)tag;
192
hdr.efi64_entry = entry->entry_addr;
193
break;
194
195
default:
196
if (tag->flags & MULTIBOOT_HEADER_TAG_OPTIONAL)
197
break;
198
printf("Unknown header tag %#x not optional\n",
199
tag->type);
200
error = EINVAL;
201
goto out;
202
}
203
204
i += roundup2(tag->size, MULTIBOOT_TAG_ALIGN);
205
if (tag->type == MULTIBOOT_HEADER_TAG_END)
206
break;
207
}
208
209
if (hdr.efi64_entry == 0) {
210
printf("No EFI64 entry address provided\n");
211
error = EINVAL;
212
goto out;
213
}
214
if (!keep_bs) {
215
printf("Unable to boot MB2 with BS exited\n");
216
error = EINVAL;
217
goto out;
218
}
219
220
error = elf32_loadfile_raw(filename, dest, result, 1);
221
if (error != 0) {
222
printf(
223
"elf32_loadfile_raw failed: %d unable to load multiboot kernel\n",
224
error);
225
goto out;
226
}
227
228
file_addmetadata(*result, MODINFOMD_NOCOPY | MODINFOMD_MB2HDR,
229
sizeof(hdr), &hdr);
230
231
/*
232
* f_addr is already aligned to PAGE_SIZE, make sure
233
* f_size it's also aligned so when the modules are loaded
234
* they are aligned to PAGE_SIZE.
235
*/
236
(*result)->f_size = roundup((*result)->f_size, PAGE_SIZE);
237
238
out:
239
if (header_search != NULL)
240
free(header_search);
241
if (multiboot != NULL)
242
free(multiboot);
243
close(fd);
244
return (error);
245
}
246
247
static unsigned int add_string(void *buf, unsigned int type, const char *str)
248
{
249
struct multiboot_tag *tag;
250
251
tag = buf;
252
tag->type = type;
253
tag->size = sizeof(*tag) + strlen(str) + 1;
254
strcpy(buf + sizeof(*tag), str);
255
return (roundup2(tag->size, MULTIBOOT_TAG_ALIGN));
256
}
257
258
static unsigned int add_efi(void *buf)
259
{
260
struct multiboot_tag *bs;
261
struct multiboot_tag_efi64 *efi64;
262
struct multiboot_tag_efi64_ih *ih;
263
unsigned int len;
264
265
len = 0;
266
bs = buf;
267
bs->type = MULTIBOOT_TAG_TYPE_EFI_BS;
268
bs->size = sizeof(*bs);
269
len += roundup2(bs->size, MULTIBOOT_TAG_ALIGN);
270
271
efi64 = buf + len;
272
efi64->type = MULTIBOOT_TAG_TYPE_EFI64;
273
efi64->size = sizeof(*efi64);
274
efi64->pointer = (uintptr_t)ST;
275
len += roundup2(efi64->size, MULTIBOOT_TAG_ALIGN);
276
277
ih = buf + len;
278
ih->type = MULTIBOOT_TAG_TYPE_EFI64_IH;
279
ih->size = sizeof(*ih);
280
ih->pointer = (uintptr_t)IH;
281
282
return (len + roundup2(ih->size, MULTIBOOT_TAG_ALIGN));
283
}
284
285
static unsigned int add_module(void *buf, vm_offset_t start, vm_offset_t end,
286
const char *cmdline)
287
{
288
struct multiboot_tag_module *mod;
289
290
mod = buf;
291
mod->type = MULTIBOOT_TAG_TYPE_MODULE;
292
mod->size = sizeof(*mod);
293
mod->mod_start = start;
294
mod->mod_end = end;
295
if (cmdline != NULL)
296
{
297
strcpy(buf + sizeof(*mod), cmdline);
298
mod->size += strlen(cmdline) + 1;
299
}
300
301
return (roundup2(mod->size, MULTIBOOT_TAG_ALIGN));
302
}
303
304
static unsigned int add_end(void *buf)
305
{
306
struct multiboot_tag *tag;
307
308
tag = buf;
309
tag->type = MULTIBOOT_TAG_TYPE_END;
310
tag->size = sizeof(*tag);
311
312
return (roundup2(tag->size, MULTIBOOT_TAG_ALIGN));
313
}
314
315
static int
316
exec(struct preloaded_file *fp)
317
{
318
EFI_PHYSICAL_ADDRESS addr = 0;
319
EFI_PHYSICAL_ADDRESS stack = 0;
320
EFI_STATUS status;
321
void *multiboot_space;
322
vm_offset_t modulep, kernend, kern_base,
323
payload_base;
324
char *cmdline = NULL;
325
size_t len;
326
int error;
327
uint32_t *total_size;
328
struct file_metadata *md;
329
struct xen_header header;
330
struct mb2hdr *hdr;
331
332
333
_Static_assert(sizeof(header) <= PAGE_SIZE, "header too big");
334
335
if ((md = file_findmetadata(fp,
336
MODINFOMD_NOCOPY | MODINFOMD_MB2HDR)) == NULL) {
337
printf("Missing Multiboot2 EFI64 entry point\n");
338
return(EFTYPE);
339
}
340
hdr = (void *)&md->md_data;
341
342
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
343
EFI_SIZE_TO_PAGES(PAGE_SIZE), &addr);
344
if (EFI_ERROR(status)) {
345
printf("Failed to allocate pages for multiboot2 header: %lu\n",
346
DECODE_ERROR(status));
347
error = ENOMEM;
348
goto error;
349
}
350
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
351
EFI_SIZE_TO_PAGES(128 * 1024), &stack);
352
if (EFI_ERROR(status)) {
353
printf("Failed to allocate pages for Xen stack: %lu\n",
354
DECODE_ERROR(status));
355
error = ENOMEM;
356
goto error;
357
}
358
359
/*
360
* Scratch space to build the multiboot2 header. Reserve the start of
361
* the space to place the header with the size, which we don't know
362
* yet.
363
*/
364
multiboot_space = (void *)(uintptr_t)(addr + sizeof(uint32_t) * 2);
365
366
/*
367
* Don't pass the memory size found by the bootloader, the memory
368
* available to Dom0 will be lower than that.
369
*/
370
unsetenv("smbios.memory.enabled");
371
372
/* Set the Xen command line. */
373
if (fp->f_args == NULL) {
374
/* Add the Xen command line if it is set. */
375
cmdline = getenv("xen_cmdline");
376
if (cmdline != NULL) {
377
fp->f_args = strdup(cmdline);
378
if (fp->f_args == NULL) {
379
error = ENOMEM;
380
goto error;
381
}
382
}
383
}
384
if (fp->f_args != NULL) {
385
len = strlen(fp->f_name) + 1 + strlen(fp->f_args) + 1;
386
cmdline = malloc(len);
387
if (cmdline == NULL) {
388
error = ENOMEM;
389
goto error;
390
}
391
snprintf(cmdline, len, "%s %s", fp->f_name, fp->f_args);
392
multiboot_space += add_string(multiboot_space,
393
MULTIBOOT_TAG_TYPE_CMDLINE, cmdline);
394
free(cmdline);
395
}
396
397
multiboot_space += add_string(multiboot_space,
398
MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME, "FreeBSD Loader");
399
multiboot_space += add_efi(multiboot_space);
400
401
/*
402
* Prepare the multiboot module list, Xen assumes the first
403
* module is the Dom0 kernel, and the second one is the initramfs.
404
* This is not optimal for FreeBSD, that doesn't have a initramfs
405
* but instead loads modules dynamically and creates the metadata
406
* info on-the-fly.
407
*
408
* As expected, the first multiboot module is going to be the
409
* FreeBSD kernel loaded as a raw file. The second module is going
410
* to contain the metadata info and the loaded modules.
411
*
412
* There's a small header prefixed in the second module that contains
413
* some information required to calculate the relocated address of
414
* modulep based on the original offset of modulep from the start of
415
* the module address. Note other fields might be added to this header
416
* if required.
417
*
418
* Native layout:
419
* fp->f_addr + fp->f_size
420
* +---------+----------------+------------+
421
* | | | |
422
* | Kernel | Modules | Metadata |
423
* | | | |
424
* +---------+----------------+------------+
425
* fp->f_addr modulep kernend
426
*
427
* Xen dom0 layout:
428
* fp->f_addr fp->f_addr + fp->f_size
429
* +---------+------------+----------------+------------+
430
* | | | | |
431
* | Kernel | xen_header | Modules | Metadata |
432
* | | | | |
433
* +---------+------------+----------------+------------+
434
* modulep kernend
435
* \________/\__________________________________________/
436
* module 0 module 1
437
*/
438
439
fp = file_findfile(NULL, md_kerntype);
440
if (fp == NULL) {
441
printf("No FreeBSD kernel provided, aborting\n");
442
error = EINVAL;
443
goto error;
444
}
445
446
error = bi_load(fp->f_args, &modulep, &kernend, false);
447
if (error != 0)
448
goto error;
449
450
/*
451
* Note that the Xen kernel requires to be started with BootServices
452
* enabled, and hence we cannot use efi_copy_finish to relocate the
453
* loaded data from the staging area to the expected loaded addresses.
454
* This is fine because the Xen kernel is relocatable, so it can boot
455
* fine straight from the staging area. We use efi_translate to get the
456
* staging addresses where the kernels and metadata are currently
457
* loaded.
458
*/
459
kern_base = (uintptr_t)efi_translate(fp->f_addr);
460
payload_base = kern_base + fp->f_size - PAGE_SIZE;
461
multiboot_space += add_module(multiboot_space, kern_base, payload_base,
462
NULL);
463
multiboot_space += add_module(multiboot_space, payload_base,
464
(uintptr_t)efi_translate(kernend), "header");
465
466
header.flags = XENHEADER_HAS_MODULEP_OFFSET;
467
header.modulep_offset = modulep - (fp->f_addr + fp->f_size - PAGE_SIZE);
468
archsw.arch_copyin(&header, fp->f_addr + fp->f_size - PAGE_SIZE,
469
sizeof(header));
470
471
multiboot_space += add_end(multiboot_space);
472
total_size = (uint32_t *)(uintptr_t)(addr);
473
*total_size = (uintptr_t)multiboot_space - addr;
474
475
if (*total_size > PAGE_SIZE)
476
panic("Multiboot header exceeds fixed size");
477
478
efi_time_fini();
479
dev_cleanup();
480
multiboot2_exec(efi_translate(hdr->efi64_entry), addr,
481
stack + 128 * 1024);
482
483
panic("exec returned");
484
485
error:
486
if (addr)
487
BS->FreePages(addr, EFI_SIZE_TO_PAGES(PAGE_SIZE));
488
if (stack)
489
BS->FreePages(stack, EFI_SIZE_TO_PAGES(128 * 1024));
490
return (error);
491
}
492
493
static int
494
obj_loadfile(char *filename, uint64_t dest, struct preloaded_file **result)
495
{
496
struct preloaded_file *mfp, *kfp, *rfp;
497
int error;
498
499
/* See if there's a multiboot kernel loaded */
500
mfp = file_findfile(NULL, md_kerntype_mb);
501
if (mfp == NULL)
502
return (EFTYPE);
503
504
/*
505
* We have a multiboot kernel loaded, see if there's a FreeBSD
506
* kernel loaded also.
507
*/
508
kfp = file_findfile(NULL, md_kerntype);
509
if (kfp == NULL) {
510
/*
511
* No kernel loaded, this must be it. The kernel has to
512
* be loaded as a raw file, it will be processed by
513
* Xen and correctly loaded as an ELF file.
514
*/
515
rfp = file_loadraw(filename, md_kerntype, 0);
516
if (rfp == NULL) {
517
printf(
518
"Unable to load %s as a multiboot payload kernel\n",
519
filename);
520
return (EINVAL);
521
}
522
523
/* Load kernel metadata... */
524
setenv("kernelname", filename, 1);
525
error = elf64_load_modmetadata(rfp, rfp->f_addr + rfp->f_size);
526
if (error) {
527
printf("Unable to load kernel %s metadata error: %d\n",
528
rfp->f_name, error);
529
return (EINVAL);
530
}
531
532
533
/*
534
* Reserve one page at the end of the kernel to place some
535
* metadata in order to cope for Xen relocating the modules and
536
* the metadata information.
537
*/
538
rfp->f_size = roundup(rfp->f_size, PAGE_SIZE);
539
rfp->f_size += PAGE_SIZE;
540
*result = rfp;
541
} else {
542
/* The rest should be loaded as regular modules */
543
error = elf64_obj_loadfile(filename, dest, result);
544
if (error != 0) {
545
printf("Unable to load %s as an object file, error: %d",
546
filename, error);
547
return (error);
548
}
549
}
550
551
return (0);
552
}
553
554
static int
555
obj_exec(struct preloaded_file *fp)
556
{
557
558
return (EFTYPE);
559
}
560
561
struct file_format multiboot2 = {
562
.l_load = loadfile,
563
.l_exec = exec
564
};
565
struct file_format multiboot2_obj = {
566
.l_load = obj_loadfile,
567
.l_exec = obj_exec
568
};
569
570