// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #ifndef SCHOS_EFI_H #define SCHOS_EFI_H // The parts of UEFI this loader actually uses, written from the // specification. // // Not gnu-efi and not EDK II. Both are large dependencies for something this // small, and both raise a licensing question the project does not need to // answer -- see docs/licensing.md. What is here is the handful of structures // and function pointers needed to read a file, ask for the framebuffer, take // the memory map and leave: perhaps two hundred lines against tens of // thousands. // // Everything is called through function pointers in tables the firmware hands // us, using the Microsoft x64 calling convention. clang's // --target=x86_64-unknown-windows produces that natively, which is why no // __attribute__((ms_abi)) appears anywhere below. // // Fields this loader never touches are left as `void *` padding rather than // spelled out. The offsets are what matter and they are preserved exactly; // naming a field we do not use would only invite it to drift. typedef unsigned char UINT8; typedef unsigned short UINT16; typedef unsigned int UINT32; typedef unsigned long long UINT64; typedef long long INTN; typedef unsigned long long UINTN; typedef UINT16 CHAR16; typedef UINT8 BOOLEAN; typedef void *EFI_HANDLE; typedef UINTN EFI_STATUS; typedef UINT64 EFI_PHYSICAL_ADDRESS; typedef UINT64 EFI_VIRTUAL_ADDRESS; #define EFI_SUCCESS 0 #define EFI_BUFFER_TOO_SMALL ((EFI_STATUS)(0x8000000000000000ULL | 5)) #define EFI_ERROR(s) (((INTN)(s)) < 0) typedef struct { UINT32 Data1; UINT16 Data2; UINT16 Data3; UINT8 Data4[8]; } EFI_GUID; // --- memory --------------------------------------------------------------- typedef enum { AllocateAnyPages, AllocateMaxAddress, AllocateAddress } EFI_ALLOCATE_TYPE; // Only the types this loader has to reason about are named. The rule it // applies is simply which of them are free for the kernel to use. #define EfiReservedMemoryType 0 #define EfiLoaderCode 1 #define EfiLoaderData 2 #define EfiBootServicesCode 3 #define EfiBootServicesData 4 #define EfiRuntimeServicesCode 5 #define EfiRuntimeServicesData 6 #define EfiConventionalMemory 7 #define EfiUnusableMemory 8 #define EfiACPIReclaimMemory 9 #define EfiACPIMemoryNVS 10 #define EfiMemoryMappedIO 11 #define EfiMemoryMappedIOPortSpace 12 #define EfiPalCode 13 #define EfiPersistentMemory 14 typedef struct { UINT32 Type; UINT32 Pad; EFI_PHYSICAL_ADDRESS PhysicalStart; EFI_VIRTUAL_ADDRESS VirtualStart; UINT64 NumberOfPages; UINT64 Attribute; } EFI_MEMORY_DESCRIPTOR; // --- simple text output --------------------------------------------------- typedef struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { void *Reset; EFI_STATUS (*OutputString)(struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, CHAR16 *String); void *TestString; void *QueryMode; void *SetMode; void *SetAttribute; EFI_STATUS (*ClearScreen)(struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This); void *SetCursorPosition; void *EnableCursor; void *Mode; } EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL; // --- console input --------------------------------------------------------- typedef struct { UINT16 ScanCode; UINT16 UnicodeChar; } EFI_INPUT_KEY; struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL; typedef EFI_STATUS (*EFI_INPUT_RESET)( struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, unsigned char ExtendedVerification); // Returns EFI_NOT_READY when nothing has been typed, which is what makes a // timed prompt possible without blocking the boot. typedef EFI_STATUS (*EFI_INPUT_READ_KEY)( struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This, EFI_INPUT_KEY *Key); typedef struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL { EFI_INPUT_RESET Reset; EFI_INPUT_READ_KEY ReadKeyStroke; void *WaitForKey; } EFI_SIMPLE_TEXT_INPUT_PROTOCOL; // --- boot services -------------------------------------------------------- typedef struct { char Hdr[24]; void *RaiseTPL; void *RestoreTPL; EFI_STATUS (*AllocatePages)(EFI_ALLOCATE_TYPE Type, UINTN MemoryType, UINTN Pages, EFI_PHYSICAL_ADDRESS *Memory); EFI_STATUS (*FreePages)(EFI_PHYSICAL_ADDRESS Memory, UINTN Pages); EFI_STATUS (*GetMemoryMap)(UINTN *MemoryMapSize, EFI_MEMORY_DESCRIPTOR *MemoryMap, UINTN *MapKey, UINTN *DescriptorSize, UINT32 *DescriptorVersion); EFI_STATUS (*AllocatePool)(UINTN PoolType, UINTN Size, void **Buffer); EFI_STATUS (*FreePool)(void *Buffer); void *CreateEvent; void *SetTimer; void *WaitForEvent; void *SignalEvent; void *CloseEvent; void *CheckEvent; void *InstallProtocolInterface; void *ReinstallProtocolInterface; void *UninstallProtocolInterface; EFI_STATUS (*HandleProtocol)(EFI_HANDLE Handle, EFI_GUID *Protocol, void **Interface); void *Reserved; void *RegisterProtocolNotify; void *LocateHandle; void *LocateDevicePath; void *InstallConfigurationTable; void *LoadImage; void *StartImage; void *Exit; void *UnloadImage; EFI_STATUS (*ExitBootServices)(EFI_HANDLE ImageHandle, UINTN MapKey); void *GetNextMonotonicCount; EFI_STATUS (*Stall)(UINTN Microseconds); void *SetWatchdogTimer; void *ConnectController; void *DisconnectController; void *OpenProtocol; void *CloseProtocol; void *OpenProtocolInformation; void *ProtocolsPerHandle; /* SearchType 2 is ByProtocol: every handle that speaks a given protocol, * which is how the loader finds the displays LocateProtocol does not * return -- it answers with the first one and there is no second call. */ EFI_STATUS (*LocateHandleBuffer)(UINTN SearchType, EFI_GUID *Protocol, void *SearchKey, UINTN *NoHandles, EFI_HANDLE **Buffer); EFI_STATUS (*LocateProtocol)(EFI_GUID *Protocol, void *Registration, void **Interface); } EFI_BOOT_SERVICES; typedef struct { char Hdr[24]; CHAR16 *FirmwareVendor; UINT32 FirmwareRevision; EFI_HANDLE ConsoleInHandle; EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; EFI_HANDLE ConsoleOutHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut; EFI_HANDLE StandardErrorHandle; EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr; void *RuntimeServices; EFI_BOOT_SERVICES *BootServices; UINTN NumberOfTableEntries; void *ConfigurationTable; } EFI_SYSTEM_TABLE; // --- configuration tables ------------------------------------------------- // // Where UEFI publishes things a BIOS left lying in low memory. The ACPI RSDP is // the one that matters here: the kernel finds it by scanning the EBDA and // 0xE0000-0x100000, which is a BIOS convention that firmware is under no // obligation to honour -- and OVMF does not. #define EFI_ACPI_20_TABLE_GUID \ { 0x8868E871, 0xE4F1, 0x11D3, { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 } } #define EFI_ACPI_10_TABLE_GUID \ { 0xEB9D2D30, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } } typedef struct { EFI_GUID VendorGuid; void *VendorTable; } EFI_CONFIGURATION_TABLE; // --- loaded image --------------------------------------------------------- #define EFI_LOADED_IMAGE_PROTOCOL_GUID \ { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } } typedef struct { UINT32 Revision; EFI_HANDLE ParentHandle; void *SystemTable; EFI_HANDLE DeviceHandle; // the volume this image was loaded from void *FilePath; void *Reserved; UINT32 LoadOptionsSize; void *LoadOptions; void *ImageBase; UINT64 ImageSize; UINTN ImageCodeType; UINTN ImageDataType; void *Unload; } EFI_LOADED_IMAGE_PROTOCOL; // --- file system ---------------------------------------------------------- #define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID \ { 0x0964E5B22, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } } #define EFI_FILE_MODE_READ 0x0000000000000001ULL typedef struct EFI_FILE_PROTOCOL { UINT64 Revision; EFI_STATUS (*Open)(struct EFI_FILE_PROTOCOL *This, struct EFI_FILE_PROTOCOL **NewHandle, CHAR16 *FileName, UINT64 OpenMode, UINT64 Attributes); EFI_STATUS (*Close)(struct EFI_FILE_PROTOCOL *This); void *Delete; EFI_STATUS (*Read)(struct EFI_FILE_PROTOCOL *This, UINTN *BufferSize, void *Buffer); void *Write; EFI_STATUS (*GetPosition)(struct EFI_FILE_PROTOCOL *This, UINT64 *Position); EFI_STATUS (*SetPosition)(struct EFI_FILE_PROTOCOL *This, UINT64 Position); void *GetInfo; void *SetInfo; void *Flush; } EFI_FILE_PROTOCOL; typedef struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL { UINT64 Revision; EFI_STATUS (*OpenVolume)(struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This, EFI_FILE_PROTOCOL **Root); } EFI_SIMPLE_FILE_SYSTEM_PROTOCOL; // --- graphics output ------------------------------------------------------ #define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \ { 0x9042A9DE, 0x23DC, 0x4A38, { 0x96, 0xFB, 0x7A, 0xDE, 0xD0, 0x80, 0x51, 0x6A } } #define PixelRedGreenBlueReserved8BitPerColor 0 #define PixelBlueGreenRedReserved8BitPerColor 1 #define PixelBitMask 2 #define PixelBltOnly 3 typedef struct { UINT32 Version; UINT32 HorizontalResolution; UINT32 VerticalResolution; UINT32 PixelFormat; UINT32 PixelInformation[4]; UINT32 PixelsPerScanLine; } EFI_GRAPHICS_OUTPUT_MODE_INFORMATION; typedef struct { // MaxMode first, then Mode. That is the specification's order and getting // it backwards is invisible until something reads them: Info, // SizeOfInfo and FrameBufferBase all sit at the same offsets either way, // because both fields are UINT32. It was backwards here, so the first code // to ask how many modes existed was told the current mode number instead -- // zero, on a machine with plenty of them. UINT32 MaxMode; UINT32 Mode; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; UINTN SizeOfInfo; EFI_PHYSICAL_ADDRESS FrameBufferBase; UINTN FrameBufferSize; } EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE; struct EFI_GRAPHICS_OUTPUT_PROTOCOL; // Ask what a mode is, and switch to one. Declared properly rather than as // void*, because choosing a resolution means walking every mode the firmware // offers and asking each one its size -- the same shape as the BIOS path's // VBE enumeration, and for the same reason: a table of numbers can only // describe hardware somebody already tested. typedef EFI_STATUS (*EFI_GOP_QUERY_MODE)( struct EFI_GRAPHICS_OUTPUT_PROTOCOL *This, UINT32 ModeNumber, UINTN *SizeOfInfo, EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info); typedef EFI_STATUS (*EFI_GOP_SET_MODE)( struct EFI_GRAPHICS_OUTPUT_PROTOCOL *This, UINT32 ModeNumber); /* Blt is how you draw on a GOP that has no linear framebuffer to write into -- * and there is one of those on every machine with a virtio-gpu, because the * device has no aperture. It is also the only way to put pixels on a *second* * display: the loader draws into the primary's framebuffer directly and then * hands that same memory to the other GOP's Blt as a source buffer. */ struct EFI_GRAPHICS_OUTPUT_PROTOCOL; #define ByProtocol 2 #define EfiBltVideoFill 0 #define EfiBltBufferToVideo 2 typedef EFI_STATUS (*EFI_GOP_BLT)(struct EFI_GRAPHICS_OUTPUT_PROTOCOL *This, void *BltBuffer, UINTN BltOperation, UINTN SourceX, UINTN SourceY, UINTN DestinationX, UINTN DestinationY, UINTN Width, UINTN Height, UINTN Delta); typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL { EFI_GOP_QUERY_MODE QueryMode; EFI_GOP_SET_MODE SetMode; EFI_GOP_BLT Blt; EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; } EFI_GRAPHICS_OUTPUT_PROTOCOL; #endif /* SCHOS_EFI_H */