紧接着上一篇文章,这里开始讲怎么从efi的main函数出发,进行GOP的系统调用。
主要目标是从基本的系统调用表开始,由GUID进行locateProtocol后,直接给一个pixel填满整个屏幕。本文暂时不追求terminal、渲染器这些东西的实现,就追求一个最基本的屏幕染色的功能。
调用主要流程
裸机代码编程可以参考之前写的文章:
http://kaoqi.xyz/archives/uefixi-tong-biao-tou-wen-jian
起码,我们解决了main函数的一些基本问题。
这次任务的主要流程,就是调用GOP这个Protocol中的BLT中的EfiBltVideoFill的枚举,将一个具体的pixel实现整个屏幕填充。
这包括如下的一些主要流程:
1、根据输入的EFI系统表,解析出boot services后输入GOP的GUID调用LocateProtocol函数并获取对应的协议指针
2、根据BS查到的协议指针,使用EFI_GRAPHICS_OUTPUT_PROTOCOL结构解析并获取协议中的Mode指针
3、根据Mode指针,使用EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE结构体获取帧基地址后,直接进行内存上帧基地址的修改可以直接触发对屏幕显示的更改,这样就直接完成任务了。
4、根据Mode指针,获取到一系列信息后,可以在PROTOCOL层级中调用BLT函数,使用EfiBltVideoFill方式进行单PIXEL的填充。这个过程相当于调用了UEFI固件提供给你的能力。
基础数据类型定义
主要定义从8到64不同位宽的数据格式
typedef unsigned long long UINT64;
typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef unsigned char UINT8;
typedef UINT64 UINTN;
typedef INT64 INTN;
typedef void VOID;EFI系统调用表与GUID相关的结构体
定义与EFI相关的基本信息,也包括用于检索的GUID相关的内容。
杂项部分:
typedef UINT32 EFI_STATUS;
typedef VOID* EFI_HANDLE;GUID结构体:
//GUID的结构体
typedef struct {
UINT32 Data1;
UINT16 Data2;
UINT16 Data3;
UINT8 Data4;
} EFI_GUID;系统表部分结构体:(注意,boot services在更下面定义的)
//系统表表头的结构体
typedef struct {
UINT64 Signature;
UINT32 Revision;
UINT32 HeaderSize;
UINT32 CRC32;
UINT32 Reserved;
} EFI_TABLE_HEADER;
//系统表本表结构体
typedef struct {
EFI_TABLE_HEADER Hdr;
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;
EFI_RUNTIME_SERVICES *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;启动服务表结构体由于过于长,给作者干力竭了,所以使用额外的一篇文章来描述。
描述的文件在这里:
http://kaoqi.xyz/archives/uefizhong-de-boot-servicebiao-jie-gou
在本文中,对于GOP协议,主要关心的是handle的调用获取API,所以下面放一个简化版的struct:
// 首先,boot service的表拥有一个完全一样的表头
typedef struct {
UINT64 Signature;
UINT32 Revision;
UINT32 HeaderSize;
UINT32 CRC32;
UINT32 Reserved;
} EFI_TABLE_HEADER;
// 这是给定的LocateProtocol函数定义
typedef
EFI_STATUS
(*EFI_LOCATE_PROTOCOL) (
EFI_GUID *Protocol,
VOID *Registration OPTIONAL,
VOID **Interface
);
// 然后定义BS表本身,注意,其中大部分的指针都被忽略掉了
typedef struct {
EFI_TABLE_HEADER Hdr;
UINT64 _buf[37]; // 在Locate之前存在37个指针函数方法
EFI_LOCATE_PROTOCOL LocateProtocol; // 这是我们需要的BS服务函数
UINT64 _buf[5]; // 在Locate之后还有5个或6个函数
} EFI_BOOT_SERVICES;GOP相关的结构体
GOP协议接口相关与协议内部的数据结构:
//GUID的具体的值
static const EFI_GUID gEfiGraphicsOutputProtocolGuid = {
0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}
};
//PIXEL的枚举,由于UEFI原文里也没说这里具体应该对应啥,所以结合GRUB2的源码的话应该就这么枚举就OK
typedef enum {
PixelRedGreenBlueReserved8BitPerColor,
PixelBlueGreenRedReserved8BitPerColor,
PixelBitMask,
PixelBltOnly,
PixelFormatMax
} EFI_GRAPHICS_PIXEL_FORMAT;
//mode指针的下级指针struct
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelInformation;
UINT32 PixelsPerScanLine;
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
//mode指针本体struct,可以在获取到GOP协议句柄后调用mode指针,完成解析
typedef struct {
UINT32 MaxMode;
UINT32 Mode;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
UINT64 FrameBufferBase;
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
//GOP协议的4个主要部分其中之一
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
UINT32 ModeNumber,
UINTN *SizeOfInfo,
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
);
//GOP协议的4个主要部分其中之二
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
UINT32 ModeNumber
);
//GOP协议的4个主要部分其中之三,也是最重要的BLT部分
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
VOID *BltBuffer, OPTIONAL
EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
UINTN SourceX,
UINTN SourceY,
UINTN DestinationX,
UINTN DestinationY,
UINTN Width,
UINTN Height,
UINTN Delta OPTIONAL
);
//GOP协议由Locate找到后的本体struct
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
};最终main函数代码参考
这里声明一下,下面这段代码中缺失了很多关于UEFI中系统表、BS表的struct描述,所以直接跑跑不通,需要结合进一步的实验来验证,这个后续专门出一篇文章来讲。
/**
* UEFI Application to fill the screen with Red color using GOP Protocol.
* This code assumes it is compiled within a UEFI development environment (like EDK II)
* or linked against standard UEFI headers. Since the prompt asks for a single C file
* without external libraries and allows defining structs, we define the necessary
* UEFI types and protocols here.
*
* Note: In a real UEFI environment, the entry point is usually `UefiMain` or `efi_main`,
* and `gBS` (Boot Services) is provided by the environment. Here we simulate the structure.
*/
// --- Basic UEFI Type Definitions ---
typedef unsigned long long UINT64;
typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef unsigned char UINT8;
typedef UINT64 UINTN; // Assuming 64-bit UEFI
typedef INT64 INTN;
typedef void VOID;
typedef UINT32 EFI_STATUS;
typedef VOID* EFI_HANDLE;
typedef GUID EFI_GUID; // Simplified, usually defined as struct
#define EFI_SUCCESS 0
#define EFI_ERROR(x) ((INTN)(x) < 0)
// --- GUID Definition for GOP ---
static const EFI_GUID gEfiGraphicsOutputProtocolGuid = {
0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}
};
// --- Struct Definitions based on UEFI Spec ---
typedef enum {
PixelRedGreenBlueReserved8BitPerColor,
PixelBlueGreenRedReserved8BitPerColor,
PixelBitMask,
PixelBltOnly,
PixelFormatMax
} EFI_GRAPHICS_PIXEL_FORMAT;
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelInformation; // Only valid if PixelFormat == PixelBitMask
UINT32 PixelsPerScanLine;
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
typedef struct {
UINT32 MaxMode;
UINT32 Mode;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
UINT64 FrameBufferBase;
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN UINT32 ModeNumber,
OUT UINTN *SizeOfInfo,
OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
);
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN UINT32 ModeNumber
);
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN VOID *BltBuffer, OPTIONAL
IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height,
IN UINTN Delta OPTIONAL
);
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
};
// --- Boot Services Definition (Simplified) ---
typedef struct {
// ... other services
EFI_STATUS (EFIAPI *LocateProtocol) (
IN EFI_GUID *Protocol,
IN VOID *Registration, OPTIONAL
OUT VOID **Interface
);
// ... other services
} EFI_BOOT_SERVICES;
// Global pointer to Boot Services (Provided by UEFI Core in real env)
extern EFI_BOOT_SERVICES *gBS;
/**
* Main Entry Point
*/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop = NULL;
EFI_STATUS Status;
UINT8 *FrameBuffer;
UINTN NumPixels;
UINTN Index;
// 1. Locate the GOP Protocol
Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&Gop);
if (EFI_ERROR(Status) || Gop == NULL) {
return Status; // Failed to find GOP
}
// 2. Ensure we are in a valid mode (Optional: Query and Set if needed)
// For simplicity, we assume the current mode set by firmware is acceptable.
// If specific resolution is needed, one would call QueryMode and SetMode here.
// 3. Get Frame Buffer Address
FrameBuffer = (UINT8 *)(UINTN)Gop->Mode->FrameBufferBase;
// Calculate number of pixels based on current mode info
// Note: PixelsPerScanLine might be larger than HorizontalResolution due to padding
UINT32 Width = Gop->Mode->Info->HorizontalResolution;
UINT32 Height = Gop->Mode->Info->VerticalResolution;
UINT32 PixelsPerScanLine = Gop->Mode->Info->PixelsPerScanLine;
// Total bytes to fill. We will fill the entire buffer provided by GOP.
UINTN BufferSize = Gop->Mode->FrameBufferSize;
// 4. Fill Screen with Red
// The pixel format determines how "Red" is represented.
// Common formats:
// - PixelRedGreenBlueReserved8BitPerColor: R=0xFF, G=0x00, B=0x00, Res=0x00 -> 0xFF000000 (if little endian 32-bit write) or byte sequence FF 00 00 00
// - PixelBlueGreenRedReserved8BitPerColor: B=0xFF, G=0x00, R=0x00, Res=0x00 -> Byte sequence 00 00 FF 00
EFI_GRAPHICS_PIXEL_FORMAT Format = Gop->Mode->Info->PixelFormat;
UINT32 ColorValue = 0;
if (Format == PixelRedGreenBlueReserved8BitPerColor) {
// Memory layout: [R][G][B][Res]
// Little Endian 32-bit word: 0x000000FF (Res=0, B=0, G=0, R=255)
// But when writing bytes sequentially:
// Byte 0: R (0xFF)
// Byte 1: G (0x00)
// Byte 2: B (0x00)
// Byte 3: Res (0x00)
// So a 32-bit integer 0x000000FF written to memory becomes FF 00 00 00 on Little Endian.
ColorValue = 0x000000FF;
} else if (Format == PixelBlueGreenRedReserved8BitPerColor) {
// Memory layout: [B][G][R][Res]
// Byte 0: B (0x00)
// Byte 1: G (0x00)
// Byte 2: R (0xFF)
// Byte 3: Res (0x00)
// 32-bit integer: 0x00FF0000
ColorValue = 0x00FF0000;
} else if (Format == PixelBitMask) {
// Construct color based on masks.
// Simplified: Assume standard RGB masks if available, otherwise default to 0
// For robustness, we should check masks. Here we assume Red mask is lowest bits or similar.
// A safe bet for "Red" in bitmask is usually setting the Red bits.
// Without specific mask values in this snippet, we'll default to a simple pattern or skip.
// Let's assume a common mask: R=0x00FF0000, G=0x0000FF00, B=0x000000FF (common in some BIOS impls)
// Or R=0x000000FF.
// To be safe and generic without complex mask parsing logic in this snippet:
// We will just fill with 0xFF assuming 8-bit R is significant or use Blt which handles format.
// However, direct memset requires knowing exact byte pattern.
// Let's use a simple approach: Fill with 0xFF.
// If R is the first byte (RGBR), it works. If B is first (BGR), it makes it Blue.
// Given the ambiguity, using Blt is safer, but prompt implies direct manipulation or simple struct usage.
// Let's stick to the most common Little-Endian 32bpp formats.
ColorValue = 0x000000FF; // Default assumption
} else {
return EFI_UNSUPPORTED;
}
// Optimization: Use 32-bit writes if possible, otherwise byte-by-byte
// Since FrameBufferSize might not be divisible by 4 in some edge cases (unlikely for GOP),
// we iterate by bytes for maximum compatibility with the "Raw" requirement.
// However, iterating byte-by-byte is slow. Let's try to use 32-bit writes.
UINT32 *FrameBuffer32 = (UINT32 *)FrameBuffer;
UINTN NumPixels32 = BufferSize / 4;
for (Index = 0; Index < NumPixels32; Index++) {
FrameBuffer32[Index] = ColorValue;
}
// Handle remaining bytes if any
UINT8 *RemainingBytes = (UINT8 *)&FrameBuffer32[NumPixels32];
UINTN RemainingCount = BufferSize % 4;
UINT8 *ColorBytes = (UINT8 *)&ColorValue;
for (Index = 0; Index < RemainingCount; Index++) {
RemainingBytes[Index] = ColorBytes[Index];
}
return EFI_SUCCESS;
}