1// Copyright 2018 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 "trampoline.h"
6
7#include <inttypes.h>
8#include <libzbi/zbi.h>
9#include <string.h>
10#include <zircon/boot/e820.h>
11
12#define BOOT_LOADER_NAME_ENV "multiboot.boot_loader_name="
13
14static size_t zbi_size(const zbi_header_t* zbi) {
15    return sizeof(*zbi) + zbi->length;
16}
17
18// Convert the multiboot memory information to ZBI_TYPE_E820_TABLE format.
19static void add_memory_info(void* zbi, size_t capacity,
20                            const multiboot_info_t* info) {
21    if ((info->flags & MB_INFO_MMAP) &&
22        info->mmap_addr != 0 &&
23        info->mmap_length >= sizeof(memory_map_t)) {
24        size_t nranges = 0;
25        for (memory_map_t* mmap = (void*)info->mmap_addr;
26             (uintptr_t)mmap < info->mmap_addr + info->mmap_length;
27             mmap = (void*)((uintptr_t)mmap + 4 + mmap->size)) {
28            ++nranges;
29        }
30        e820entry_t* ranges;
31        void* payload;
32        zbi_result_t result = zbi_create_section(
33            zbi, capacity, nranges * sizeof(ranges[0]),
34            ZBI_TYPE_E820_TABLE, 0, 0, &payload);
35        if (result != ZBI_RESULT_OK) {
36            panic("zbi_create_section(%p, %#"PRIxPTR", %#zx) failed: %d",
37                  zbi, capacity, nranges * sizeof(ranges[0]), (int)result);
38        }
39        ranges = payload;
40        for (memory_map_t* mmap = (void*)info->mmap_addr;
41             (uintptr_t)mmap < info->mmap_addr + info->mmap_length;
42             mmap = (void*)((uintptr_t)mmap + 4 + mmap->size)) {
43            *ranges++ = (e820entry_t){
44                .addr = (((uint64_t)mmap->base_addr_high << 32) |
45                         mmap->base_addr_low),
46                .size = (((uint64_t)mmap->length_high << 32) |
47                         mmap->length_low),
48                // MB_MMAP_TYPE_* matches E820_* values.
49                .type = mmap->type,
50            };
51        }
52    } else {
53        const e820entry_t ranges[] = {
54            {
55                .addr = 0,
56                .size = info->mem_lower << 10,
57                .type = E820_RAM,
58            },
59            {
60                .addr = 1 << 20,
61                .size = ((uint64_t)info->mem_upper << 10) - (1 << 20),
62                .type = E820_RAM,
63            },
64        };
65        zbi_result_t result = zbi_append_section(
66            zbi, capacity, sizeof(ranges), ZBI_TYPE_E820_TABLE, 0, 0, ranges);
67        if (result != ZBI_RESULT_OK) {
68            panic("zbi_append_section(%p, %#"PRIxPTR", %#zx) failed: %d",
69                  zbi, capacity, sizeof(ranges), (int)result);
70        }
71    }
72}
73
74static void add_cmdline(void* zbi, size_t capacity,
75                            const multiboot_info_t* info) {
76    // Boot loader command line.
77    if (info->flags & MB_INFO_CMD_LINE) {
78        const char* cmdline = (void*)info->cmdline;
79        size_t len = strlen(cmdline) + 1;
80        zbi_result_t result = zbi_append_section(
81            zbi, capacity, len, ZBI_TYPE_CMDLINE, 0, 0, cmdline);
82        if (result != ZBI_RESULT_OK) {
83            panic("zbi_append_section(%p, %#"PRIxPTR", %zu) failed: %d",
84                  zbi, capacity, len, (int)result);
85        }
86    }
87
88    // Boot loader name.
89    if (info->flags & MB_INFO_BOOT_LOADER) {
90        const char* name = (void*)info->boot_loader_name;
91        size_t len = strlen(name) + 1;
92        void *payload;
93        zbi_result_t result = zbi_create_section(
94            zbi, capacity, sizeof(BOOT_LOADER_NAME_ENV) - 1 + len,
95            ZBI_TYPE_CMDLINE, 0, 0, &payload);
96        if (result != ZBI_RESULT_OK) {
97            panic("zbi_create_section(%p, %#"PRIxPTR", %zu) failed: %d",
98                  zbi, capacity, sizeof(BOOT_LOADER_NAME_ENV) - 1 + len,
99                  (int)result);
100        }
101        for (char *p = (memcpy(payload, BOOT_LOADER_NAME_ENV,
102                               sizeof(BOOT_LOADER_NAME_ENV) - 1) +
103                        sizeof(BOOT_LOADER_NAME_ENV) - 1);
104             len > 0;
105             ++p, ++name, --len) {
106            *p = (*name == ' ' || *name == '\t' ||
107                  *name == '\n' || *name == '\r') ? '+' : *name;
108        }
109    }
110}
111
112static void add_zbi_items(void* zbi, size_t capacity,
113                            const multiboot_info_t* info) {
114    add_memory_info(zbi, capacity, info);
115    add_cmdline(zbi, capacity, info);
116}
117
118noreturn void multiboot_main(uint32_t magic, multiboot_info_t* info) {
119    if (magic != MULTIBOOT_BOOTLOADER_MAGIC) {
120        panic("bad multiboot magic from bootloader %#"PRIx32" != %#"PRIx32"\n",
121              magic, (uint32_t)MULTIBOOT_BOOTLOADER_MAGIC);
122    }
123
124    if (!(info->flags & MB_INFO_MODS)) {
125        panic("missing multiboot modules");
126    }
127    if (info->mods_count != 1) {
128        panic("cannot handle multiboot mods_count %"PRIu32" != 1\n",
129              info->mods_count);
130    }
131
132    module_t* const mod = (void*)info->mods_addr;
133    zircon_kernel_t* zbi = (void*)mod->mod_start;
134    size_t zbi_len = mod->mod_end - mod->mod_start;
135
136    if (zbi == NULL || zbi_len < sizeof(zbi->hdr_file) ||
137        zbi_len < sizeof(zbi_header_t) + zbi->hdr_file.length) {
138        panic("insufficient multiboot module [%#"PRIx32",%#"PRIx32") for ZBI",
139              mod->mod_start, mod->mod_end);
140    }
141
142    zbi_header_t* bad_hdr;
143    zbi_result_t result = zbi_check(zbi, &bad_hdr);
144    if (result != ZBI_RESULT_OK) {
145        panic("ZBI failed check: %d at offset %#zx",
146              (int)result, (size_t)((uint8_t*)bad_hdr - (uint8_t*)zbi));
147    }
148
149    if (zbi->hdr_kernel.type != ZBI_TYPE_KERNEL_X64) {
150        panic("ZBI first item has type %#"PRIx32" != %#"PRIx32"\n",
151              zbi->hdr_kernel.type, (uint32_t)ZBI_TYPE_KERNEL_X64);
152    }
153
154    // The kernel will sit at PHYS_LOAD_ADDRESS, where the code now
155    // running sits.  The space until kernel_memory_end is reserved
156    // and can't be used for anything else.
157    const size_t kernel_load_size =
158        offsetof(zircon_kernel_t, data_kernel) + zbi->hdr_kernel.length;
159    uint8_t* const kernel_load_end = PHYS_LOAD_ADDRESS + kernel_load_size;
160    uint8_t* const kernel_memory_end =
161        kernel_load_end + ZBI_ALIGN(zbi->data_kernel.reserve_memory_size);
162
163    if (!(info->flags & MB_INFO_MEM_SIZE)) {
164        panic("multiboot memory information missing");
165    }
166    uintptr_t upper_memory_limit = info->mem_upper << 10;
167    if (info->mem_upper > (UINT32_MAX >> 10)) {
168        upper_memory_limit = -4096u;
169    }
170    if (upper_memory_limit < (uintptr_t)kernel_memory_end) {
171        panic("upper memory limit %#"PRIxPTR" < kernel end %p",
172              upper_memory_limit, kernel_memory_end);
173    }
174
175    // Now we can append other items to the ZBI.
176    const size_t capacity = upper_memory_limit - (uintptr_t)zbi;
177    add_zbi_items(zbi, capacity, info);
178
179    // Use discarded ZBI space to hold the trampoline.
180    void* trampoline;
181    result = zbi_create_section(zbi, capacity, sizeof(struct trampoline),
182                                ZBI_TYPE_DISCARD, 0, 0, &trampoline);
183    if (result != ZBI_RESULT_OK) {
184        panic("zbi_create_section(%p, %#"PRIxPTR", %#zx) failed: %d",
185              zbi, capacity, sizeof(struct trampoline), (int)result);
186    }
187
188    // Separate the kernel from the data ZBI and move it into temporary
189    // space that the trampoline code will copy it from.
190    const zbi_header_t data_zbi_hdr = ZBI_CONTAINER_HEADER(
191        zbi->hdr_file.length -
192        sizeof(zbi->hdr_kernel) -
193        zbi->hdr_kernel.length);
194    uint8_t* const zbi_end = (uint8_t*)zbi + zbi_size(&zbi->hdr_file);
195    zircon_kernel_t* kernel;
196    zbi_header_t* data;
197    uintptr_t free_memory;
198    if ((uint8_t*)zbi < kernel_memory_end) {
199        // The ZBI overlaps where the kernel needs to sit.  Copy it further up.
200        if (zbi_end < kernel_memory_end) {
201            data = (void*)kernel_memory_end;
202        } else {
203            data = (void*)zbi_end;
204        }
205        // It needs to be page-aligned.
206        data = (void*)(((uintptr_t)data + 4095) & -4096);
207        // First fill in the data ZBI.
208        *data = data_zbi_hdr;
209        memcpy(data + 1,
210               (uint8_t*)&zbi->data_kernel + zbi->hdr_kernel.length,
211               data->length);
212        // Now place the kernel after that.
213        kernel = (void*)((uint8_t*)(data + 1) + data->length);
214        memcpy(kernel, zbi, kernel_load_size);
215        free_memory = (uintptr_t)kernel + kernel_load_size;
216    } else {
217        // The ZBI can stay where it is.  Find a place to copy the kernel.
218        size_t space = (uint8_t*)zbi - kernel_memory_end;
219        if (space >= kernel_load_size) {
220            // There's space right after the final kernel location.
221            kernel = (void*)kernel_memory_end;
222            free_memory = (uintptr_t)zbi_end;
223        } else {
224            // Use space after the ZBI.
225            kernel = (void*)((uint8_t*)zbi + zbi_size(&zbi->hdr_file));
226            free_memory = (uintptr_t)kernel + kernel_load_size;
227        }
228        // Copy the kernel into some available scratch space.
229        memcpy(kernel, zbi, kernel_load_size);
230        // Now clobber what was the tail end of the kernel with a new
231        // container header for just the remainder of the ZBI.
232        data = (void*)((uint8_t*)&zbi->data_kernel + zbi->hdr_kernel.length);
233        *data = data_zbi_hdr;
234        // Move the ZBI up to get it page-aligned.
235        if ((uintptr_t)data % 4096 != 0) {
236            void* orig = data;
237            data = (void*)(((uintptr_t)data + 4095) & -4096);
238            memmove(data, orig, zbi_size(data));
239        }
240    }
241
242    // Fix up the kernel container's size.
243    kernel->hdr_file.length =
244        sizeof(kernel->hdr_kernel) + kernel->hdr_kernel.length;
245
246    // Set up page tables in free memory.
247    enable_64bit_paging(free_memory, upper_memory_limit);
248
249    // Copy the kernel into place and enter its code in 64-bit mode.
250    boot_zbi(kernel, data, trampoline);
251}
252