1 // Copyright 2017 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 "osboot.h"
6 
7 #include <efi/protocol/graphics-output.h>
8 #include <efi/runtime-services.h>
9 #include <efi/zircon.h>
10 
11 #include <cmdline.h>
12 #include <inttypes.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <xefi.h>
16 
17 #include <zircon/boot/image.h>
18 #include <zircon/pixelformat.h>
19 
20 
21 static efi_guid zircon_guid = ZIRCON_VENDOR_GUID;
22 static char16_t crashlog_name[] = ZIRCON_CRASHLOG_EFIVAR;
23 
get_last_crashlog(efi_system_table * sys,void * ptr,size_t max)24 static size_t get_last_crashlog(efi_system_table* sys, void* ptr, size_t max) {
25     efi_runtime_services* rs = sys->RuntimeServices;
26 
27     uint32_t attr = ZIRCON_CRASHLOG_EFIATTR;
28     size_t sz = max;
29     efi_status r = rs->GetVariable(crashlog_name, &zircon_guid, &attr, &sz, ptr);
30     if (r == EFI_SUCCESS) {
31         // Erase it
32         rs->SetVariable(crashlog_name, &zircon_guid, ZIRCON_CRASHLOG_EFIATTR, 0, NULL);
33     } else {
34         sz = 0;
35     }
36     return sz;
37 }
38 
39 static unsigned char scratch[32768];
40 
start_zircon(uint64_t entry,void * bootdata)41 static void start_zircon(uint64_t entry, void* bootdata) {
42 #if __x86_64__
43     // ebx = 0, ebp = 0, edi = 0, esi = bootdata
44     __asm__ __volatile__(
45         "movl $0, %%ebp \n"
46         "cli \n"
47         "jmp *%[entry] \n" ::[entry] "a"(entry),
48         [bootdata] "S"(bootdata),
49         "b"(0), "D"(0));
50 #else
51 #warning "add code for other arches here"
52 #endif
53     for (;;)
54         ;
55 }
56 
add_bootdata(void ** ptr,size_t * avail,zbi_header_t * bd,void * data)57 static int add_bootdata(void** ptr, size_t* avail,
58                         zbi_header_t* bd, void* data) {
59     size_t len = ZBI_ALIGN(bd->length);
60     if ((sizeof(zbi_header_t) + len) > *avail) {
61         printf("boot: no room for bootdata type=%08x size=%08x\n",
62                bd->type, bd->length);
63         return -1;
64     }
65     bd->flags |= ZBI_FLAG_VERSION;
66     bd->reserved0 = 0;
67     bd->reserved1 = 0;
68     bd->magic = ZBI_ITEM_MAGIC;
69     bd->crc32 = ZBI_ITEM_NO_CRC32;
70 
71     memcpy(*ptr, bd, sizeof(zbi_header_t));
72     memcpy((*ptr) + sizeof(zbi_header_t), data, len);
73     len += sizeof(zbi_header_t);
74     (*ptr) += len;
75     (*avail) -= len;
76 
77     return 0;
78 }
79 
image_getsize(void * image,size_t sz)80 size_t image_getsize(void* image, size_t sz) {
81     if (sz < sizeof(zircon_kernel_t)) {
82         return 0;
83     }
84     zircon_kernel_t* kernel = image;
85     if ((kernel->hdr_file.type != ZBI_TYPE_CONTAINER) ||
86         (kernel->hdr_file.magic != ZBI_ITEM_MAGIC) ||
87         (kernel->hdr_kernel.type != ZBI_TYPE_KERNEL_X64) ||
88         (kernel->hdr_kernel.magic != ZBI_ITEM_MAGIC)) {
89         return 0;
90     }
91     return ZBI_ALIGN(kernel->hdr_file.length) + sizeof(zbi_header_t);
92 }
93 
header_check(void * image,size_t sz,uint64_t * _entry,size_t * _flen,size_t * _klen)94 static int header_check(void* image, size_t sz, uint64_t* _entry,
95                         size_t* _flen, size_t* _klen) {
96     zbi_header_t* bd = image;
97     size_t flen, klen;
98     uint64_t entry;
99 
100     if (!(bd->flags & ZBI_FLAG_VERSION)) {
101         printf("boot: v1 bootdata kernel no longer supported\n");
102         return -1;
103     }
104     zircon_kernel_t* kernel = image;
105     if ((sz < sizeof(zircon_kernel_t)) ||
106         (kernel->hdr_kernel.type != ZBI_TYPE_KERNEL_X64) ||
107         ((kernel->hdr_kernel.flags & ZBI_FLAG_VERSION) == 0)) {
108         printf("boot: invalid zircon kernel header\n");
109         return -1;
110     }
111     flen = ZBI_ALIGN(kernel->hdr_file.length);
112     klen = ZBI_ALIGN(kernel->hdr_kernel.length);
113     entry = kernel->data_kernel.entry;
114     if (flen > (sz - sizeof(zbi_header_t))) {
115         printf("boot: invalid zircon kernel header (bad flen)\n");
116         return -1;
117     }
118 
119     if (klen > (sz - (sizeof(zbi_header_t) * 2))) {
120         printf("boot: invalid zircon kernel header (bad klen)\n");
121         return -1;
122     }
123     if (_entry) {
124         *_entry = entry;
125     }
126     if (_flen) {
127         *_flen = flen;
128         *_klen = klen;
129     }
130 
131     return 0;
132 }
133 
item_check(zbi_header_t * bd,size_t sz)134 static int item_check(zbi_header_t* bd, size_t sz) {
135     if (sz > 0x7FFFFFFF) {
136         // disallow 2GB+ items to avoid wrap on align issues
137         return -1;
138     }
139     if ((bd->magic != ZBI_ITEM_MAGIC) ||
140         ((bd->flags & ZBI_FLAG_VERSION) == 0) ||
141         (ZBI_ALIGN(bd->length) > sz)) {
142         return -1;
143     } else {
144         return 0;
145     }
146 }
147 
148 //TODO: verify crc32 when present
identify_image(void * image,size_t sz)149 unsigned identify_image(void* image, size_t sz) {
150     if (sz == 0) {
151         return IMAGE_EMPTY;
152     }
153     if (sz < sizeof(zbi_header_t)) {
154         printf("image is too small\n");
155         return IMAGE_INVALID;
156     }
157     zbi_header_t* bd = image;
158     sz -= sizeof(zbi_header_t);
159     if ((bd->type != ZBI_TYPE_CONTAINER) ||
160         item_check(bd, sz)) {
161         printf("image has invalid header\n");
162         return IMAGE_INVALID;
163     }
164     image += sizeof(zbi_header_t);
165     unsigned n = 0;
166     unsigned r = 0;
167     while (sz > sizeof(zbi_header_t)) {
168         bd = image;
169         sz -= sizeof(zbi_header_t);
170         if (item_check(image, sz)) {
171             printf("image has invalid bootitem\n");
172             return IMAGE_INVALID;
173         }
174         if (ZBI_IS_KERNEL_BOOTITEM(bd->type)) {
175             if (n != 0) {
176                 printf("image has kernel in middle\n");
177                 return IMAGE_INVALID;
178             } else {
179                 r = IMAGE_KERNEL;
180             }
181         }
182         if (bd->type == ZBI_TYPE_STORAGE_BOOTFS) {
183             if ((r == IMAGE_KERNEL) || (r == IMAGE_COMBO)) {
184                 r = IMAGE_COMBO;
185             } else {
186                 r = IMAGE_RAMDISK;
187             }
188         }
189         image += ZBI_ALIGN(bd->length) + sizeof(zbi_header_t);
190         sz -= ZBI_ALIGN(bd->length);
191         n++;
192     }
193 
194     return r;
195 }
196 
boot_zircon(efi_handle img,efi_system_table * sys,void * image,size_t isz,void * ramdisk,size_t rsz,void * cmdline,size_t csz)197 int boot_zircon(efi_handle img, efi_system_table* sys,
198                  void* image, size_t isz, void* ramdisk, size_t rsz,
199                  void* cmdline, size_t csz) {
200 
201     efi_boot_services* bs = sys->BootServices;
202     uint64_t entry;
203 
204     if (header_check(image, isz, &entry, NULL, NULL)) {
205         return -1;
206     }
207     if ((ramdisk == NULL) || (rsz < sizeof(zbi_header_t))) {
208         printf("boot: ramdisk missing or too small\n");
209         return -1;
210     }
211     if (isz > kernel_zone_size) {
212         printf("boot: kernel image too large\n");
213         return -1;
214     }
215 
216     zbi_header_t* hdr0 = ramdisk;
217     if ((hdr0->type != ZBI_TYPE_CONTAINER) ||
218         (hdr0->extra != ZBI_CONTAINER_MAGIC) ||
219         !(hdr0->flags & ZBI_FLAG_VERSION)) {
220         printf("boot: ramdisk has invalid bootdata header\n");
221         return -1;
222     }
223 
224     if ((hdr0->length > (rsz - sizeof(zbi_header_t)))) {
225         printf("boot: ramdisk has invalid bootdata length\n");
226         return -1;
227     }
228 
229     // osboot ensures we have FRONT_BYTES ahead of the
230     // ramdisk to prepend our own bootdata items.
231     void* bptr = ramdisk - FRONT_BYTES;
232     size_t blen = FRONT_BYTES;
233 
234     // We create a new container header of the same size
235     // as the one at the start of the ramdisk
236     zbi_header_t hdr = ZBI_CONTAINER_HEADER(hdr0->length + FRONT_BYTES);
237     memcpy(bptr, &hdr, sizeof(hdr));
238     bptr += sizeof(hdr);
239 
240     // pass kernel commandline
241     hdr.type = ZBI_TYPE_CMDLINE;
242     hdr.length = csz;
243     hdr.extra = 0;
244     hdr.flags = ZBI_FLAG_VERSION;
245     if (add_bootdata(&bptr, &blen, &hdr, cmdline)) {
246         return -1;
247     }
248 
249     // pass ACPI root pointer
250     uint64_t rsdp = find_acpi_root(img, sys);
251     if (rsdp != 0) {
252         hdr.type = ZBI_TYPE_ACPI_RSDP;
253         hdr.length = sizeof(rsdp);
254         if (add_bootdata(&bptr, &blen, &hdr, &rsdp)) {
255             return -1;
256         }
257     }
258 
259     // pass SMBIOS entry point pointer
260     uint64_t smbios = find_smbios(img, sys);
261     if (smbios != 0) {
262         hdr.type = ZBI_TYPE_SMBIOS;
263         hdr.length = sizeof(smbios);
264         if (add_bootdata(&bptr, &blen, &hdr, &smbios)) {
265             return -1;
266         }
267     }
268 
269     // pass EFI system table
270     uint64_t addr = (uintptr_t) sys;
271     hdr.type = ZBI_TYPE_EFI_SYSTEM_TABLE;
272     hdr.length = sizeof(sys);
273     if (add_bootdata(&bptr, &blen, &hdr, &addr)) {
274         return -1;
275     }
276 
277     // pass framebuffer data
278     efi_graphics_output_protocol* gop = NULL;
279     bs->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**)&gop);
280     if (gop) {
281         zbi_swfb_t fb = {
282             .base = gop->Mode->FrameBufferBase,
283             .width = gop->Mode->Info->HorizontalResolution,
284             .height = gop->Mode->Info->VerticalResolution,
285             .stride = gop->Mode->Info->PixelsPerScanLine,
286             .format = get_zx_pixel_format(gop),
287         };
288         hdr.type = ZBI_TYPE_FRAMEBUFFER;
289         hdr.length = sizeof(fb);
290         if (add_bootdata(&bptr, &blen, &hdr, &fb)) {
291             return -1;
292         }
293     }
294 
295     memcpy((void*)kernel_zone_base, image, isz);
296 
297     // Obtain the system memory map
298     size_t msize, dsize;
299     for (int attempts = 0;;attempts++) {
300         efi_memory_descriptor* mmap = (efi_memory_descriptor*) (scratch + sizeof(uint64_t));
301         uint32_t dversion = 0;
302         size_t mkey = 0;
303         msize = sizeof(scratch) - sizeof(uint64_t);
304         dsize = 0;
305         efi_status r = sys->BootServices->GetMemoryMap(&msize, mmap, &mkey, &dsize, &dversion);
306         if (r != EFI_SUCCESS) {
307             printf("boot: cannot GetMemoryMap()\n");
308             goto fail;
309         }
310 
311         r = sys->BootServices->ExitBootServices(img, mkey);
312         if (r == EFI_SUCCESS) {
313             break;
314         }
315         if (r == EFI_INVALID_PARAMETER) {
316             if (attempts > 0) {
317                 printf("boot: cannot ExitBootServices(): %s\n", xefi_strerror(r));
318                 goto fail;
319             }
320             // Attempting to exit may cause us to have to re-grab the
321             // memory map, but if it happens more than once something's
322             // broken.
323             continue;
324         }
325         printf("boot: cannot ExitBootServices(): %s\n", xefi_strerror(r));
326         goto fail;
327     }
328     memcpy(scratch, &dsize, sizeof(uint64_t));
329 
330     // install memory map
331     hdr.type = ZBI_TYPE_EFI_MEMORY_MAP;
332     hdr.length = msize + sizeof(uint64_t);
333     if (add_bootdata(&bptr, &blen, &hdr, scratch)) {
334         goto fail;
335     }
336 
337     // obtain the last crashlog if we can
338     size_t sz = get_last_crashlog(sys, scratch, 4096);
339     if (sz > 0) {
340         hdr.type = ZBI_TYPE_CRASHLOG;
341         hdr.length = sz;
342         add_bootdata(&bptr, &blen, &hdr, scratch);
343     }
344 
345     // fill the remaining gap between pre-data and ramdisk image
346     if ((blen < sizeof(hdr)) || (blen & 7)) {
347         goto fail;
348     }
349     hdr.type = ZBI_TYPE_DISCARD;
350     hdr.length = blen - sizeof(hdr);
351     hdr.flags = ZBI_FLAG_VERSION;
352     memcpy(bptr, &hdr, sizeof(hdr));
353 
354     // jump to the kernel
355     start_zircon(entry, ramdisk - FRONT_BYTES);
356 
357 fail:
358     return -1;
359 }
360 
361 static char cmdline[CMDLINE_MAX];
362 
zedboot(efi_handle img,efi_system_table * sys,void * image,size_t sz)363 int zedboot(efi_handle img, efi_system_table* sys,
364             void* image, size_t sz) {
365 
366     size_t flen, klen;
367     if (header_check(image, sz, NULL, &flen, &klen)) {
368         return -1;
369     }
370 
371     // ramdisk portion is file - headers - kernel len
372     uint32_t rlen = flen - sizeof(zbi_header_t) - klen;
373     uint32_t roff = (sizeof(zbi_header_t) * 2) + klen;
374     if (rlen == 0) {
375         printf("zedboot: no ramdisk?!\n");
376         return -1;
377     }
378 
379     // allocate space for the ramdisk
380     efi_boot_services* bs = sys->BootServices;
381     size_t rsz = rlen + sizeof(zbi_header_t) + FRONT_BYTES;
382     size_t pages = BYTES_TO_PAGES(rsz);
383     void* ramdisk = NULL;
384     efi_status r = bs->AllocatePages(AllocateAnyPages, EfiLoaderData, pages,
385                                      (efi_physical_addr*)&ramdisk);
386     if (r) {
387         printf("zedboot: cannot allocate ramdisk buffer\n");
388         return -1;
389     }
390 
391     ramdisk += FRONT_BYTES;
392     *(zbi_header_t*)ramdisk = (zbi_header_t)ZBI_CONTAINER_HEADER(rlen);
393     memcpy(ramdisk + sizeof(zbi_header_t), image + roff, rlen);
394     rlen += sizeof(zbi_header_t);
395 
396     printf("ramdisk @ %p\n", ramdisk);
397 
398     size_t csz = cmdline_to_string(cmdline, sizeof(cmdline));
399 
400     // shrink original image header to include only the kernel
401     zircon_kernel_t* kernel = image;
402     kernel->hdr_file.length = sizeof(zbi_header_t) + klen;
403 
404     return boot_zircon(img, sys, image, roff, ramdisk, rlen, cmdline, csz);
405 }
406 
boot_kernel(efi_handle img,efi_system_table * sys,void * image,size_t sz,void * ramdisk,size_t rsz)407 int boot_kernel(efi_handle img, efi_system_table* sys,
408                 void* image, size_t sz,
409                 void* ramdisk, size_t rsz) {
410 
411     size_t csz = cmdline_to_string(cmdline, sizeof(cmdline));
412 
413     zbi_header_t* bd = image;
414     if ((bd->type == ZBI_TYPE_CONTAINER) &&
415         (bd->extra == ZBI_CONTAINER_MAGIC)) {
416         return boot_zircon(img, sys, image, sz, ramdisk, rsz, cmdline, csz);
417     } else {
418         return -1;
419     }
420 }
421