Three fixes for real machines that broke at any resolution but 800x600: - kmain() took the loader's framebuffer address as uint32, and vga_fb_init() re-published the scanout from that and the 16-bit VBE block. An aperture above 4 GB moved the console onto the wrong memory right after the vga: line. Both now use the loader's 64-bit display block, mapped with ioremap(). - The loader mirrored onto the console splitter's GOP, which forwards to the primary itself, and onto GOPs that share the primary's aperture. After a mode change that rewrote the screen over itself with another mode's stride. Handles without a device path and overlapping apertures are skipped, and a mode change re-matches the remaining mirrors. - A mode whose reported size, stride or aperture does not add up after SetMode is refused, and the loader returns to the previous one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
351 lines
14 KiB
C
351 lines
14 KiB
C
// 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 } }
|
|
#define SMBIOS_TABLE_GUID \
|
|
{ 0xEB9D2D31, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } }
|
|
#define SMBIOS3_TABLE_GUID \
|
|
{ 0xF2FD1544, 0x9794, 0x4A2C, { 0x99, 0x2E, 0xE5, 0xBB, 0xCF, 0x20, 0xE3, 0x94 } }
|
|
|
|
typedef struct {
|
|
EFI_GUID VendorGuid;
|
|
void *VendorTable;
|
|
} EFI_CONFIGURATION_TABLE;
|
|
|
|
// --- device path ----------------------------------------------------------
|
|
//
|
|
// Only ever asked whether a handle *has* one: a protocol installed by a real
|
|
// device's driver sits on a handle with a path to that device, and a virtual
|
|
// one -- the console splitter's GOP -- sits on a handle with none.
|
|
|
|
#define EFI_DEVICE_PATH_PROTOCOL_GUID \
|
|
{ 0x09576E91, 0x6D3F, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B } }
|
|
|
|
// --- 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 */
|