GOP instead of VBE, and the same reasoning. There are no mode numbers worth hardcoding here: the firmware publishes however many modes the hardware can do and which index means what varies by machine, so the menu is built by walking them. Press R within four seconds for a list, then either a mode number or Enter to type a size. 800x600 stays the floor. Fixes a header bug this is the first code to notice. The spec's order in EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE is MaxMode then Mode; efi.h had them swapped. Info, SizeOfInfo and FrameBufferBase sit at the same offsets either way because both fields are UINT32, so the framebuffer worked perfectly and nothing was wrong until something asked how many modes existed -- and was told the current mode number instead. Zero, on a machine reporting thirty. QueryMode and SetMode were void* padding and now have real prototypes; ConIn likewise, so the loader can read a key. Under OVMF the console includes the serial line, which is where `make qemu-uefi` puts it and where the person actually is -- the same mistake the BIOS menu made first time round. Verified over serial against OVMF: 30 modes enumerated; picking listed mode 14 gives 1280x1024; typing 1024x768 gives 1024x768; no key keeps the firmware's mode and boots. All reach schinit. bootcheck 108 [ok]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
317 lines
12 KiB
C
317 lines
12 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
#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;
|
|
void *LocateHandleBuffer;
|
|
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);
|
|
|
|
typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL {
|
|
EFI_GOP_QUERY_MODE QueryMode;
|
|
EFI_GOP_SET_MODE SetMode;
|
|
void *Blt;
|
|
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
|
|
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
|
|
|
|
#endif /* SCHOS_EFI_H */
|