/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2021 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ │ above copyright notice and this permission notice appear in all copies. │ │ │ │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/relocations.h" #include "ape/sections.internal.h" #include "libc/dce.h" #include "libc/intrin/bits.h" #include "libc/intrin/newbie.h" #include "libc/intrin/weaken.h" #include "libc/macros.internal.h" #include "libc/nt/efi.h" #include "libc/nt/thunk/msabi.h" #include "libc/runtime/e820.internal.h" #include "libc/runtime/internal.h" #include "libc/runtime/pc.internal.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" /* TODO: Why can't we change CR3? Could it really need PML5T? */ /* TODO: Why does QEMU in UEFI mode take ten seconds to boot? */ struct EfiArgs { char *Args[0x400 / sizeof(char *)]; char ArgBlock[0xC00]; }; static const EFI_GUID kEfiLoadedImageProtocol = LOADED_IMAGE_PROTOCOL; static const EFI_GUID kEfiGraphicsOutputProtocol = GRAPHICS_OUTPUT_PROTOCOL; extern const char vga_console[]; extern void _EfiPostboot(struct mman *, uint64_t *, uintptr_t, char **); static void EfiInitVga(struct mman *mm, EFI_SYSTEM_TABLE *SystemTable) { EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphInfo; EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *GraphMode; EFI_PIXEL_BITMASK *PixelInfo; unsigned vid_typ = PC_VIDEO_TEXT; size_t bytes_per_pix = 0; SystemTable->BootServices->LocateProtocol(&kEfiGraphicsOutputProtocol, NULL, &GraphInfo); GraphMode = GraphInfo->Mode; switch (GraphMode->Info->PixelFormat) { case PixelRedGreenBlueReserved8BitPerColor: vid_typ = PC_VIDEO_RGBX8888; bytes_per_pix = 4; break; case PixelBlueGreenRedReserved8BitPerColor: vid_typ = PC_VIDEO_BGRX8888; bytes_per_pix = 4; break; case PixelBitMask: PixelInfo = &GraphMode->Info->PixelInformation; switch (le32toh(PixelInfo->RedMask)) { case 0x00FF0000U: if (le32toh(PixelInfo->ReservedMask) >= 0x01000000U && le32toh(PixelInfo->GreenMask) == 0x0000FF00U && le32toh(PixelInfo->BlueMask) == 0x000000FFU) { vid_typ = PC_VIDEO_BGRX8888; bytes_per_pix = 4; } break; case 0x000000FFU: if (le32toh(PixelInfo->ReservedMask) >= 0x01000000U && le32toh(PixelInfo->GreenMask) == 0x0000FF00U && le32toh(PixelInfo->BlueMask) == 0x00FF0000U) { vid_typ = PC_VIDEO_RGBX8888; bytes_per_pix = 4; } break; case 0x0000F800U: if (le32toh(PixelInfo->ReservedMask) <= 0x0000FFFFU && le32toh(PixelInfo->GreenMask) == 0x000007E0U && le32toh(PixelInfo->BlueMask) == 0x0000001FU) { vid_typ = PC_VIDEO_BGR565; bytes_per_pix = 2; } break; case 0x00007C00U: if (le32toh(PixelInfo->ReservedMask) <= 0x0000FFFFU && le32toh(PixelInfo->GreenMask) == 0x000003E0U && le32toh(PixelInfo->BlueMask) == 0x0000001FU) { vid_typ = PC_VIDEO_BGR555; bytes_per_pix = 2; } break; } default: notpossible; } if (!bytes_per_pix) notpossible; mm->pc_video_type = vid_typ; mm->pc_video_stride = GraphMode->Info->PixelsPerScanLine * bytes_per_pix; mm->pc_video_width = GraphMode->Info->HorizontalResolution; mm->pc_video_height = GraphMode->Info->VerticalResolution; mm->pc_video_framebuffer = GraphMode->FrameBufferBase; mm->pc_video_framebuffer_size = GraphMode->FrameBufferSize; mm->pc_video_curs_info.y = mm->pc_video_curs_info.x = 0; SystemTable->BootServices->SetMem((void *)GraphMode->FrameBufferBase, GraphMode->FrameBufferSize, 0); } /** * EFI Application Entrypoint. * * This entrypoint is mutually exclusive from WinMain since * Windows apps and EFI apps use the same PE binary format. * So if you want to trade away Windows so that you can use * UEFI instead of the normal BIOS boot process, do this: * * STATIC_YOINK("EfiMain"); * int main() { ... } * * You can use QEMU to test this, but please note that UEFI * goes thousands of times slower than the normal BIOS boot * * qemu-system-x86_64 \ * -bios OVMF.fd \ * -nographic \ * -net none \ * -drive format=raw,file=fat:rw:o/tool/viz * FS0: * deathstar.com * * @see libc/dce.h */ __msabi noasan EFI_STATUS EfiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { int type, x87cw = 0x037f; struct mman *mm; uint32_t DescVersion; uintptr_t i, j, MapSize; struct EfiArgs *ArgBlock; EFI_LOADED_IMAGE *ImgInfo; EFI_MEMORY_DESCRIPTOR *Map, *Desc; uint64_t Address; uintptr_t Args, MapKey, DescSize; uint64_t p, pe, cr4, *m, *pd, *sp, *pml4t, *pdt1, *pdt2, *pdpt1, *pdpt2; /* * Allocates and clears PC-compatible memory and copies image. Marks the * pages as EfiRuntimeServicesData, so that we can simply free up all * EfiLoader... and EfiBootServices... pages later. The first page at * address 0 is normally already allocated as EfiBootServicesData, so * handle it separately. */ Address = 0; SystemTable->BootServices->AllocatePages( AllocateAddress, EfiRuntimeServicesData, 4096 / 4096, &Address); Address = 4096; SystemTable->BootServices->AllocatePages( AllocateAddress, EfiRuntimeServicesData, (IMAGE_BASE_REAL - 4096) / 4096, &Address); Address = 0x79000; SystemTable->BootServices->AllocatePages( AllocateAddress, EfiRuntimeServicesData, (0x7e000 - 0x79000 + sizeof(struct EfiArgs) + 4095) / 4096, &Address); Address = IMAGE_BASE_PHYSICAL; SystemTable->BootServices->AllocatePages( AllocateAddress, EfiRuntimeServicesData, ((_end - _base) + 4095) / 4096, &Address); mm = (struct mman *)0x0500; SystemTable->BootServices->SetMem(mm, sizeof(*mm), 0); SystemTable->BootServices->SetMem((void *)0x79000, 0x7e000 - 0x79000 + sizeof(struct EfiArgs), 0); SystemTable->BootServices->CopyMem((void *)IMAGE_BASE_PHYSICAL, _base, _end - _base); /* * Converts UEFI shell arguments to argv. */ ArgBlock = (struct EfiArgs *)0x7e000; SystemTable->BootServices->HandleProtocol(ImageHandle, &kEfiLoadedImageProtocol, &ImgInfo); Args = GetDosArgv(ImgInfo->LoadOptions, ArgBlock->ArgBlock, sizeof(ArgBlock->ArgBlock), ArgBlock->Args, ARRAYLEN(ArgBlock->Args)); /* * Gets information about our current video mode. Clears the screen. * TODO: if needed, switch to a video mode that has a linear frame buffer * type we support. */ if (_weaken(vga_console)) EfiInitVga(mm, SystemTable); /* * Asks UEFI which parts of our RAM we're allowed to use. */ Map = NULL; MapSize = 0; SystemTable->BootServices->GetMemoryMap(&MapSize, Map, &MapKey, &DescSize, &DescVersion); SystemTable->BootServices->AllocatePool(EfiLoaderData, MapSize, &Map); MapSize *= 2; SystemTable->BootServices->GetMemoryMap(&MapSize, Map, &MapKey, &DescSize, &DescVersion); for (j = i = 0, Desc = Map; i < MapSize / DescSize; ++i) { switch (Desc->Type) { case EfiLoaderCode: case EfiLoaderData: case EfiBootServicesCode: case EfiBootServicesData: if (Desc->PhysicalStart != 0) break; /* fallthrough */ case EfiConventionalMemory: mm->e820[j].addr = Desc->PhysicalStart; mm->e820[j].size = Desc->NumberOfPages * 4096; mm->e820[j].type = kMemoryUsable; ++j; } Desc = (EFI_MEMORY_DESCRIPTOR *)((char *)Desc + DescSize); } SystemTable->BootServices->FreePool(Map); /* * Sets up page tables. */ pd = (uint64_t *)0x79000; pdt1 = (uint64_t *)0x7b000; pdt2 = (uint64_t *)0x7a000; pdpt1 = (uint64_t *)0x7d000; pdpt2 = (uint64_t *)0x7c000; pml4t = (uint64_t *)0x7e000; for (i = 0; i < 512; ++i) { pd[i] = 0x1000 * i + PAGE_V + PAGE_RSRV + PAGE_RW; } pdt1[0] = (intptr_t)pd + PAGE_V + PAGE_RW; pdt2[0] = (intptr_t)pd + PAGE_V + PAGE_RW; pdpt1[0] = (intptr_t)pdt1 + PAGE_V + PAGE_RW; pdpt2[0] = (intptr_t)pdt2 + PAGE_V + PAGE_RW; pml4t[0] = (intptr_t)pdpt1 + PAGE_V + PAGE_RW; pml4t[256] = (intptr_t)pdpt2 + PAGE_V + PAGE_RW; /* * Asks UEFI to handover control? */ SystemTable->BootServices->ExitBootServices(ImageHandle, MapKey); /* * Switches to copied image and launches program. */ _EfiPostboot(mm, pml4t, Args, ArgBlock->Args); unreachable; }