1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <inttypes.h>
6#include <limits.h>
7#include <stdio.h>
8#include <string.h>
9
10#include <efi/boot-services.h>
11#include <efi/protocol/device-path.h>
12#include <efi/protocol/graphics-output.h>
13#include <efi/protocol/simple-text-input.h>
14#include <efi/system-table.h>
15
16#include <cmdline.h>
17#include <device_id.h>
18#include <framebuffer.h>
19#include <inet6.h>
20#include <xefi.h>
21
22#include "osboot.h"
23
24#include <zircon/boot/netboot.h>
25
26#define DEFAULT_TIMEOUT 3
27
28#define KBUFSIZE (32*1024*1024)
29#define RBUFSIZE (512 * 1024 * 1024)
30
31
32static nbfile nbkernel;
33static nbfile nbramdisk;
34static nbfile nbcmdline;
35
36nbfile* netboot_get_buffer(const char* name, size_t size) {
37    if (!strcmp(name, NB_KERNEL_FILENAME)) {
38        return &nbkernel;
39    }
40    if (!strcmp(name, NB_RAMDISK_FILENAME)) {
41        efi_physical_addr mem = 0xFFFFFFFF;
42        size_t buf_size = size > 0 ? (size + PAGE_MASK) & ~PAGE_MASK : RBUFSIZE;
43
44        if (nbramdisk.size > 0) {
45            if (nbramdisk.size < buf_size) {
46                mem = (efi_physical_addr)nbramdisk.data;
47                nbramdisk.data = 0;
48                if (gBS->FreePages(mem - FRONT_BYTES, (nbramdisk.size / PAGE_SIZE) + FRONT_PAGES)) {
49                    printf("Could not free previous ramdisk allocation\n");
50                    nbramdisk.size = 0;
51                    return NULL;
52                }
53                nbramdisk.size = 0;
54            } else {
55                return &nbramdisk;
56            }
57        }
58
59        printf("netboot: allocating %zu for ramdisk (requested %zu)\n", buf_size, size);
60        if (gBS->AllocatePages(AllocateMaxAddress, EfiLoaderData,
61                               (buf_size / PAGE_SIZE) + FRONT_PAGES, &mem)) {
62            printf("Failed to allocate network io buffer\n");
63            return NULL;
64        }
65        nbramdisk.data = (void*) (mem + FRONT_BYTES);
66        nbramdisk.size = buf_size;
67
68        return &nbramdisk;
69    }
70    if (!strcmp(name, NB_CMDLINE_FILENAME)) {
71        return &nbcmdline;
72    }
73    return NULL;
74}
75
76// Wait for a keypress from a set of valid keys. If 0 < timeout_s < INT_MAX, the
77// first key in the set of valid keys will be returned after timeout_s seconds
78// if no other valid key is pressed.
79char key_prompt(char* valid_keys, int timeout_s) {
80    if (strlen(valid_keys) < 1) return 0;
81    if (timeout_s <= 0) return valid_keys[0];
82
83    efi_event TimerEvent;
84    efi_event WaitList[2];
85
86    efi_status status;
87    size_t Index;
88    efi_input_key key;
89    memset(&key, 0, sizeof(key));
90
91    status = gBS->CreateEvent(EVT_TIMER, 0, NULL, NULL, &TimerEvent);
92    if (status != EFI_SUCCESS) {
93        printf("could not create event timer: %s\n", xefi_strerror(status));
94        return 0;
95    }
96
97    status = gBS->SetTimer(TimerEvent, TimerPeriodic, 10000000);
98    if (status != EFI_SUCCESS) {
99        printf("could not set timer: %s\n", xefi_strerror(status));
100        return 0;
101    }
102
103    int wait_idx = 0;
104    int key_idx = wait_idx;
105    WaitList[wait_idx++] = gSys->ConIn->WaitForKey;
106    int timer_idx = wait_idx;  // timer should always be last
107    WaitList[wait_idx++] = TimerEvent;
108
109    bool cur_vis = gConOut->Mode->CursorVisible;
110    int32_t col = gConOut->Mode->CursorColumn;
111    int32_t row = gConOut->Mode->CursorRow;
112    gConOut->EnableCursor(gConOut, false);
113
114    // TODO: better event loop
115    char pressed = 0;
116    if (timeout_s < INT_MAX) {
117        printf("%-10d", timeout_s);
118    }
119    do {
120        status = gBS->WaitForEvent(wait_idx, WaitList, &Index);
121
122        // Check the timer
123        if (!EFI_ERROR(status)) {
124            if (Index == timer_idx) {
125                if (timeout_s < INT_MAX) {
126                    timeout_s--;
127                    gConOut->SetCursorPosition(gConOut, col, row);
128                    printf("%-10d", timeout_s);
129                }
130                continue;
131            } else if (Index == key_idx) {
132                status = gSys->ConIn->ReadKeyStroke(gSys->ConIn, &key);
133                if (EFI_ERROR(status)) {
134                    // clear the key and wait for another event
135                    memset(&key, 0, sizeof(key));
136                } else {
137                    char* which_key = strchr(valid_keys, key.UnicodeChar);
138                    if (which_key) {
139                        pressed = *which_key;
140                        break;
141                    }
142                }
143            }
144        } else {
145            printf("Error waiting for event: %s\n", xefi_strerror(status));
146            gConOut->EnableCursor(gConOut, cur_vis);
147            return 0;
148        }
149    } while (timeout_s);
150
151    gBS->CloseEvent(TimerEvent);
152    gConOut->EnableCursor(gConOut, cur_vis);
153    if (timeout_s > 0 && pressed) {
154        return pressed;
155    }
156
157    // Default to first key in list
158    return valid_keys[0];
159}
160
161void do_select_fb() {
162    uint32_t cur_mode = get_gfx_mode();
163    uint32_t max_mode = get_gfx_max_mode();
164    while (true) {
165        printf("\n");
166        print_fb_modes();
167        printf("Choose a framebuffer mode or press (b) to return to the menu\n");
168        char key = key_prompt("b0123456789", INT_MAX);
169        if (key == 'b') break;
170        if (key - '0' >= max_mode) {
171            printf("invalid mode: %c\n", key);
172            continue;
173        }
174        set_gfx_mode(key - '0');
175        printf("Use \"bootloader.fbres=%ux%u\" to use this resolution by default\n",
176                get_gfx_hres(), get_gfx_vres());
177        printf("Press space to accept or (r) to choose again ...");
178        key = key_prompt("r ", 5);
179        if (key == ' ') {
180            return;
181        }
182        set_gfx_mode(cur_mode);
183    }
184}
185
186void do_bootmenu(bool have_fb) {
187    char* menukeys;
188    if (have_fb)
189        menukeys = "rfx";
190    else
191        menukeys = "rx";
192    printf("  BOOT MENU  \n");
193    printf("  ---------  \n");
194    if (have_fb)
195        printf("  (f) list framebuffer modes\n");
196    printf("  (r) reset\n");
197    printf("  (x) exit menu\n");
198    printf("\n");
199    char key = key_prompt(menukeys, INT_MAX);
200    switch (key) {
201    case 'f': {
202        do_select_fb();
203        break;
204    }
205    case 'r':
206        gSys->RuntimeServices->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
207        break;
208    case 'x':
209    default:
210        break;
211    }
212}
213
214static char cmdbuf[CMDLINE_MAX];
215void print_cmdline(void) {
216    cmdline_to_string(cmdbuf, sizeof(cmdbuf));
217    printf("cmdline: %s\n", cmdbuf);
218}
219
220static char netboot_cmdline[CMDLINE_MAX];
221void do_netboot() {
222    efi_physical_addr mem = 0xFFFFFFFF;
223    if (gBS->AllocatePages(AllocateMaxAddress, EfiLoaderData, KBUFSIZE / 4096, &mem)) {
224        printf("Failed to allocate network io buffer\n");
225        return;
226    }
227    nbkernel.data = (void*) mem;
228    nbkernel.size = KBUFSIZE;
229
230    // ramdisk is dynamically allocated now
231    nbramdisk.data = 0;
232    nbramdisk.size = 0;
233
234    nbcmdline.data = (void*) netboot_cmdline;
235    nbcmdline.size = sizeof(netboot_cmdline);
236    nbcmdline.offset = 0;
237
238    printf("\nNetBoot Server Started...\n\n");
239    efi_tpl prev_tpl = gBS->RaiseTPL(TPL_NOTIFY);
240    while (true) {
241        int n = netboot_poll();
242        if (n < 1) {
243            continue;
244        }
245        if (nbkernel.offset < 32768) {
246            // too small to be a kernel
247            continue;
248        }
249        uint8_t* x = nbkernel.data;
250        if ((x[0] == 'M') && (x[1] == 'Z') && (x[0x80] == 'P') && (x[0x81] == 'E')) {
251            size_t exitdatasize;
252            efi_status r;
253            efi_handle h;
254
255            efi_device_path_hw_memmap mempath[2] = {
256                {
257                    .Header = {
258                        .Type = DEVICE_PATH_HARDWARE,
259                        .SubType = DEVICE_PATH_HW_MEMMAP,
260                        .Length = { (uint8_t)(sizeof(efi_device_path_hw_memmap) & 0xff),
261                            (uint8_t)((sizeof(efi_device_path_hw_memmap) >> 8) & 0xff), },
262                    },
263                    .MemoryType = EfiLoaderData,
264                    .StartAddress = (efi_physical_addr)nbkernel.data,
265                    .EndAddress = (efi_physical_addr)(nbkernel.data + nbkernel.offset),
266                },
267                {
268                    .Header = {
269                        .Type = DEVICE_PATH_END,
270                        .SubType = DEVICE_PATH_ENTIRE_END,
271                        .Length = { (uint8_t)(sizeof(efi_device_path_protocol) & 0xff),
272                            (uint8_t)((sizeof(efi_device_path_protocol) >> 8) & 0xff), },
273                    },
274                },
275            };
276
277            printf("Attempting to run EFI binary...\n");
278            r = gBS->LoadImage(false, gImg, (efi_device_path_protocol*)mempath, (void*)nbkernel.data, nbkernel.offset, &h);
279            if (EFI_ERROR(r)) {
280                printf("LoadImage Failed (%s)\n", xefi_strerror(r));
281                continue;
282            }
283            r = gBS->StartImage(h, &exitdatasize, NULL);
284            if (EFI_ERROR(r)) {
285                printf("StartImage Failed %zu\n", r);
286                continue;
287            }
288            printf("\nNetBoot Server Resuming...\n");
289            continue;
290        }
291
292        // make sure network traffic is not in flight, etc
293        netboot_close();
294
295        // Restore the TPL before booting the kernel, or failing to netboot
296        gBS->RestoreTPL(prev_tpl);
297
298        cmdline_append((void*) nbcmdline.data, nbcmdline.offset);
299        print_cmdline();
300
301        const char* fbres = cmdline_get("bootloader.fbres", NULL);
302        if (fbres) {
303            set_gfx_mode_from_cmdline(fbres);
304        }
305
306        // maybe it's a kernel image?
307        boot_kernel(gImg, gSys, (void*) nbkernel.data, nbkernel.offset,
308                    (void*) nbramdisk.data, nbramdisk.offset);
309        break;
310    }
311}
312
313// Finds c in s and swaps it with the character at s's head. For example:
314// swap_to_head('b', "foobar", 6) = "boofar";
315static inline void swap_to_head(const char c, char* s, const size_t n) {
316    // Empty buffer?
317    if (n == 0) return;
318
319    // Find c in s
320    size_t i;
321    for (i = 0; i < n; i++) {
322        if (c == s[i]) {
323            break;
324        }
325    }
326
327    // Couldn't find c in s
328    if (i == n) return;
329
330    // Swap c to the head.
331    const char tmp = s[0];
332    s[0] = s[i];
333    s[i] = tmp;
334}
335
336size_t kernel_zone_size;
337efi_physical_addr kernel_zone_base;
338
339EFIAPI efi_status efi_main(efi_handle img, efi_system_table* sys) {
340    xefi_init(img, sys);
341    gConOut->ClearScreen(gConOut);
342
343    uint64_t mmio;
344    if (xefi_find_pci_mmio(gBS, 0x0C, 0x03, 0x30, &mmio) == EFI_SUCCESS) {
345        char tmp[32];
346        sprintf(tmp, "%#" PRIx64 , mmio);
347        cmdline_set("xdc.mmio", tmp);
348    }
349
350    // Load the cmdline
351    size_t csz = 0;
352    char* cmdline_file = xefi_load_file(L"cmdline", &csz, 0);
353    if (cmdline_file) {
354        cmdline_append(cmdline_file, csz);
355    }
356
357    efi_graphics_output_protocol* gop;
358    efi_status status = gBS->LocateProtocol(&GraphicsOutputProtocol, NULL,
359                                            (void**)&gop);
360    bool have_fb = !EFI_ERROR(status);
361
362    if (have_fb) {
363        const char* fbres = cmdline_get("bootloader.fbres", NULL);
364        if (fbres) {
365            set_gfx_mode_from_cmdline(fbres);
366        }
367        draw_logo();
368    }
369
370    int32_t prev_attr = gConOut->Mode->Attribute;
371    gConOut->SetAttribute(gConOut, EFI_LIGHTZIRCON | EFI_BACKGROUND_BLACK);
372    draw_version(BOOTLOADER_VERSION);
373    gConOut->SetAttribute(gConOut, prev_attr);
374
375    if (have_fb) {
376        printf("Framebuffer base is at %" PRIx64 "\n\n",
377               gop->Mode->FrameBufferBase);
378    }
379
380    // Set aside space for the kernel down at the 1MB mark up front
381    // to avoid other allocations getting in the way.
382    // The kernel itself is about 1MB, but we leave generous space
383    // for its BSS afterwards.
384    //
385    // Previously we requested 32MB but that caused issues. When the kernel
386    // becomes relocatable this won't be an problem. See ZX-2368.
387    kernel_zone_base = 0x100000;
388    kernel_zone_size = 6 * 1024 * 1024;
389
390    if (gBS->AllocatePages(AllocateAddress, EfiLoaderData,
391                          BYTES_TO_PAGES(kernel_zone_size), &kernel_zone_base)) {
392        printf("boot: cannot obtain memory for kernel @ %p\n", (void*) kernel_zone_base);
393        kernel_zone_size = 0;
394    }
395    printf("KALLOC DONE\n");
396
397    // Default boot defaults to network
398    const char* defboot = cmdline_get("bootloader.default", "network");
399    const char* nodename = cmdline_get("zircon.nodename", "");
400
401    // See if there's a network interface
402    bool have_network = netboot_init(nodename) == 0;
403    if (have_network) {
404        if (have_fb) {
405            draw_nodename(netboot_nodename());
406        } else {
407            printf("\nNodename: %s\n", netboot_nodename());
408        }
409        // If nodename was set through cmdline earlier in the code path then
410        // netboot_nodename will return that same value, otherwise it will
411        // return the generated value in which case it needs to be added to
412        // the command line arguments.
413        if (nodename[0] == 0) {
414            cmdline_set("zircon.nodename", netboot_nodename());
415        }
416    }
417
418    printf("\n\n");
419    print_cmdline();
420
421    // First look for a self-contained zircon boot image
422    size_t zedboot_size = 0;
423    void* zedboot_kernel = NULL;
424
425    zedboot_kernel = xefi_load_file(L"zedboot.bin", &zedboot_size, 0);
426    switch (identify_image(zedboot_kernel, zedboot_size)) {
427    case IMAGE_COMBO:
428        printf("zedboot.bin is a valid kernel+ramdisk combo image\n");
429        break;
430    case IMAGE_EMPTY:
431        break;
432    default:
433        zedboot_kernel = NULL;
434        zedboot_size = 0;
435    }
436
437    // Look for a kernel image on disk
438    size_t ksz = 0;
439    unsigned ktype = IMAGE_INVALID;
440    void* kernel = NULL;
441
442    kernel = image_load_from_disk(img, sys, &ksz);
443    if (kernel != NULL) {
444        printf("zircon image loaded from zircon partition\n");
445        ktype = IMAGE_COMBO;
446    } else {
447        kernel = xefi_load_file(L"zircon.bin", &ksz, 0);
448        switch ((ktype = identify_image(kernel, ksz))) {
449        case IMAGE_EMPTY:
450            break;
451        case IMAGE_KERNEL:
452            printf("zircon.bin is a kernel image\n");
453            break;
454        case IMAGE_COMBO:
455            printf("zircon.bin is a kernel+ramdisk combo image\n");
456            break;
457        case IMAGE_RAMDISK:
458            printf("zircon.bin is a ramdisk?!\n");
459        case IMAGE_INVALID:
460            printf("zircon.bin is not a valid kernel or combo image\n");
461            ktype = IMAGE_INVALID;
462            ksz = 0;
463            kernel = NULL;
464        }
465    }
466
467    if (!have_network && zedboot_kernel == NULL && kernel == NULL) {
468        goto fail;
469    }
470
471    char valid_keys[5];
472    memset(valid_keys, 0, sizeof(valid_keys));
473    int key_idx = 0;
474
475    if (have_network) {
476        valid_keys[key_idx++] = 'n';
477    }
478    if (kernel != NULL) {
479        valid_keys[key_idx++] = 'm';
480    }
481    if (zedboot_kernel) {
482        valid_keys[key_idx++] = 'z';
483    }
484
485    // The first entry in valid_keys will be the default after the timeout.
486    // Use the value of bootloader.default to determine the first entry. If
487    // bootloader.default is not set, use "network".
488    if (!memcmp(defboot, "local", 5)) {
489        swap_to_head('m', valid_keys, key_idx);
490    } else if (!memcmp(defboot, "zedboot", 7)) {
491        swap_to_head('z', valid_keys, key_idx);
492    } else {
493        swap_to_head('n', valid_keys, key_idx);
494    }
495    valid_keys[key_idx++] = 'b';
496
497    // make sure we update valid_keys if we ever add new options
498    if (key_idx >= sizeof(valid_keys)) goto fail;
499
500    // Disable WDT
501    // The second parameter can be any value outside of the range [0,0xffff]
502    gBS->SetWatchdogTimer(0, 0x10000, 0, NULL);
503
504    int timeout_s = cmdline_get_uint32("bootloader.timeout", DEFAULT_TIMEOUT);
505    while (true) {
506        printf("\nPress (b) for the boot menu");
507        if (have_network) {
508            printf(", ");
509            if (!kernel) printf("or ");
510            printf("(n) for network boot");
511        }
512        if (kernel) {
513            printf(", ");
514            printf("or (m) to boot the zircon.bin on the device");
515        }
516        if (zedboot_kernel) {
517            printf(", ");
518            printf("or (z) to launch zedboot");
519        }
520        printf(" ...");
521
522        char key = key_prompt(valid_keys, timeout_s);
523        printf("\n\n");
524
525        switch (key) {
526        case 'b':
527            do_bootmenu(have_fb);
528            break;
529        case 'n':
530            do_netboot();
531            break;
532        case 'm':
533            if (ktype == IMAGE_COMBO) {
534                zedboot(img, sys, kernel, ksz);
535            } else {
536                size_t rsz = 0;
537                void* ramdisk = NULL;
538                efi_file_protocol* ramdisk_file = xefi_open_file(L"bootdata.bin");
539                const char* ramdisk_name = "bootdata.bin";
540                if (ramdisk_file == NULL) {
541                    ramdisk_file = xefi_open_file(L"ramdisk.bin");
542                    ramdisk_name = "ramdisk.bin";
543                }
544                if (ramdisk_file) {
545                    printf("Loading %s...\n", ramdisk_name);
546                    ramdisk = xefi_read_file(ramdisk_file, &rsz, FRONT_BYTES);
547                    ramdisk_file->Close(ramdisk_file);
548                }
549                boot_kernel(gImg, gSys, kernel, ksz, ramdisk, rsz);
550            }
551            goto fail;
552        case 'z':
553            zedboot(img, sys, zedboot_kernel, zedboot_size);
554            goto fail;
555        default:
556            goto fail;
557        }
558    }
559
560fail:
561    printf("\nBoot Failure\n");
562    xefi_wait_any_key();
563    return EFI_SUCCESS;
564}
565