继续上一篇GOP系统表调用,本文章的主要目标是让屏幕变为一片红色。
它基于一条如下的逻辑链条:
1、由main函数传入的EFI系统表中可以找到BS表的指针
2、BS表指针用BS结构体解析后可以从中获得一个LocateProtocol函数,并通过一个GOP专用的GUID的值,找到GOP对应的protocol指针
3、GOP对应的protocol指针可以被解析为mode、blt、*Mode三个主要方向
4、对于*Mode,使用一个struct解析后可以获得当前分辨率、显示模式等信息
5、调用GOP中的blt,结合第4步中获取到的分辨率信息,使用一个符合pixel格式定义的红色将整个屏幕填满红色,并保持一定时间
在具体的函数实现中,定义反而是从后往前定义的,下面是一个具体的实现。
编译过程
后续文章中详细介绍了如何根据协议编写这个GOP功能,你还需要完成文件格式转换。
可以参考:http://kaoqi.xyz/archives/uefiluo-ji-bian-cheng
当你完成代码编写后,首先用如下命令编译为o文件:
aarch64-linux-gnu-gcc -fPIC -ffreestanding -fno-stack-protector -c GOP.c -o GOP.o然后使用lds链接脚本,并使用如下命令链接:(链接脚本可以参考之前提及的文章)
aarch64-linux-gnu-ld -shared -Bsymbolic -T kquefi.lds GOP.o -o GOP.so最后使用objcopy命令进行PE头格式的转换:
aarch64-linux-gnu-objcopy -j .text -j .data -j .rodata -j .reloc --target=pei-aarch64-little GOP.so GOP.efi最后,加载到U盘的GPT分区表内,并通过UEFI固件指定的方式打开:
基本数据类型的定义
无论如何,各种数据的位宽总是要放在整个代码文件的最上面去定义的:
typedef unsigned long long UINT64;
typedef long long INT64;
typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef unsigned short CHAR16;
typedef unsigned char UINT8;
typedef UINT64 UINTN;
typedef INT64 INTN;
typedef void VOID;这里的这些定义都十分的具体、明确了一系列的具体位宽。
有关于基本EFI函数返回值、基本函数指针,也需要一些基本的定义:
typedef UINT32 EFI_STATUS;
typedef VOID* EFI_HANDLE;有关GUID这个特殊的数据变量也可以尽量放最前面:
typedef struct {
UINT32 Data1;
UINT16 Data2;
UINT16 Data3;
UINT8 Data4;
} EFI_GUID;一些十分确定的表结构也可以放在最前面:
typedef struct {
UINT64 Signature;
UINT32 Revision;
UINT32 HeaderSize;
UINT32 CRC32;
UINT32 Reserved;
} EFI_TABLE_HEADER;GOP协议相关定义
GOP协议的层级结构为:
1、最上层可供BS表查询的PROTOCOL结构
2、由PROTOCOL获取的、用于解析BLT与当前显示屏信息的*Mode结构以及另外两个函数结构
3、由mode指针对应的一个mode信息表的结构
4、由blt结构解析中对应的PIXEL结构,即BltBuffer数组、一个BLT行动的枚举结构
//PIXEL的定义
typedef struct {
UINT8 Blue;
UINT8 Green;
UINT8 Red;
UINT8 Reserved;
} EFI_GRAPHICS_OUTPUT_BLT_PIXEL;
//BLT操作的枚举
typedef enum {
EfiBltVideoFill,
EfiBltVideoToBltBuffer,
EfiBltBufferToVideo,
EfiBltVideoToVideo,
EfiGraphicsOutputBltOperationMax
} EFI_GRAPHICS_OUTPUT_BLT_OPERATION;
//PIXEL的枚举,由于UEFI原文里也没说这里具体应该对应啥,所以结合GRUB2的源码的话应该就这么枚举就OK
typedef enum {
PixelRedGreenBlueReserved8BitPerColor,
PixelBlueGreenRedReserved8BitPerColor,
PixelBitMask,
PixelBltOnly,
PixelFormatMax
} EFI_GRAPHICS_PIXEL_FORMAT;
//PIXEL掩码的定义
typedef struct {
UINT32 RedMask;
UINT32 GreenMask;
UINT32 BlueMask;
UINT32 ReservedMask;
} EFI_PIXEL_BITMASK;
//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;
// 反向解析第1个
typedef EFI_STATUS
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
UINT32 ModeNumber,
UINTN *SizeOfInfo,
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
);
// 反向解析第2个
typedef EFI_STATUS
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
UINT32 ModeNumber
);
// 反向解析第3个,最重要的BLT部分
typedef EFI_STATUS
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT) (
EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
UINTN SourceX,
UINTN SourceY,
UINTN DestinationX,
UINTN DestinationY,
UINTN Width,
UINTN Height,
UINTN Delta OPTIONAL
);
// 反向解析第4个,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协议由Locate找到后的本体struct
typedef 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;
} EFI_GRAPHICS_OUTPUT_PROTOCOL;启动表与系统表的结构
main函数遵循如下的一个调用流程:
1、由main函数导入的EFI系统表指针
2、由EFI系统表struct解析得到的Boot Service启动表
3、由Boot Service启动表struct得到的LocateProtocol函数
4、由Locate函数获取到的GOP的protocol函数指针
其源代码也是一个反向的结构:
// 这是给定的EFI_WATCHDOG_TIMER函数定义
typedef
EFI_STATUS
(*EFI_SET_WATCHDOG_TIMER) (
UINTN Timeout,
UINT64 WatchdogCode,
UINTN DataSize,
CHAR16 *WatchdogData OPTIONAL
);
// 这是给定的LocateProtocol函数定义
typedef
EFI_STATUS
(*EFI_LOCATE_PROTOCOL) (
EFI_GUID *Protocol,
VOID *Registration OPTIONAL,
VOID **Interface
);
// 然后定义BS表本身,注意,其中大部分的指针都被忽略掉了
typedef struct {
EFI_TABLE_HEADER Hdr;
UINT64 _bufA[29];
EFI_SET_WATCHDOG_TIMER SetWatchdogTimer;
UINT64 _bufB[7];
EFI_LOCATE_PROTOCOL LocateProtocol;
UINT64 _bufC[5];
} EFI_BOOT_SERVICES;
//系统表本表结构体
typedef struct {
EFI_TABLE_HEADER Hdr;
CHAR16 *FirmwareVendor;
UINT32 FirmwareRevision;
EFI_HANDLE ConsoleInHandle;
UINT32 *ConIn;
EFI_HANDLE ConsoleOutHandle;
UINT32 *ConOut;
EFI_HANDLE StandardErrorHandle;
UINT32 *StdErr;
UINT32 *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
UINT32 *ConfigurationTable;
} EFI_SYSTEM_TABLE;这里在获取GOP的函数指针的时候,需要使用一个**函数的技巧。
对于void方法来说,1个指针*后跟变量,意味着传递一个函数句柄的副本给CPU(所有的函数入参,都不可能在函数内部修改入参的地址值,因为所有函数的入参,其在ISA指令集的本质是将入参的指针复制一份,分别保存为主参A地址的内容保存入参指针地址、形参B地址的内容保存入参指针地址,这么一回事)。无法在函数中修改一个入参的地址,这是C语言函数解释器的特性,也是基于C语言中函数的ISA翻译的定义来的。
main函数本体
在由系统表逐步获取到GOP中对应的功能函数之后,还需要补充main函数本身的一些机制。
1、main函数由系统表、启动表、GUID定义找到GOP协议
2、GOP协议获取Mode解析后,获取当前屏幕的分辨率信息、PIXEL格式信息
3、调用并定义一个pixel实例,调整BLT的EFI_GRAPHICS_OUTPUT_BLT_OPERATION操作码为EfiBltVideoFill,根据PIXEL格式定义一个纯色pixel并根据屏幕分辨率信息,填满整个屏幕
4、保持个10秒左右的时间
//这是整个efi文件的入口函数,也是后续链接器的entry点
//使用main函数时,会逐一反向用到上面定义的各种结构
EFI_STATUS *EFI_Main (EFI_HANDLE ImageHandle,EFI_SYSTEM_TABLE *SystemTable)
{
//在main函数中可以直接定义一个gop的guid数据结构
EFI_GUID gop_guid = {0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}};
//调用系统表、启动表,找GOP句柄
EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP = NULL;
EFI_STATUS Status;
SystemTable->BootServices;
SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);
Status = SystemTable->BootServices->LocateProtocol(
&gop_guid,
NULL,
(void**)&GOP
);
if (Status != 0 || GOP == NULL) {
return Status; //GOP协议不可用直接返回
}
//获取GOP句柄后画画
unsigned int width = GOP->Mode->Info->HorizontalResolution;
unsigned int height = GOP->Mode->Info->VerticalResolution;
unsigned long long fb_base = GOP->Mode->FrameBufferBase;
unsigned int *fb_ptr = (unsigned int*)(unsigned long)fb_base;
unsigned int red_pixel;
if (GOP->Mode->Info->PixelFormat == 1) {
red_pixel = 0x00FF0000; // RGBA格式红色
} else {
red_pixel = 0x000000FF; // BGRA格式红色
}
//填充整个区域为红色
unsigned int total_pixels = width * height;
for (unsigned int i = 0; i < total_pixels; i++) {
fb_ptr[i] = red_pixel;
}
return 0;
}