菜单

kaoqi
kaoqi
发布于 2026-09-07 / 38 阅读
0
0

Simple text protocol解析

在获取到UEFI的系统表后,首先提供在整个系统表之上,甚至在BS服务之前的,就是两个最重要的有关输入输出的接口:

EFI_SIMPLE_TEXT_INPUT_PROTOCOL

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL

可以说,有关这两个协议的学习,是打开UEFI世界的钥匙。

目前可靠的可供参考的资料:

1、基于EDK2的编译链工具环境

2、基于EDK2中ShellPkg的Shell主函数分析

UEFI协议中的output接口

有关SIMPLE_TEXT相关的两个协议的定义都在第12章Console Support里面。

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL其实是EFI_SERIAL_IO_PROTOCOL和EFI_GRAPHICS_OUTPUT_PROTOCOL的综合。

#define EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID \
 {0x387477c2,0x69c7,0x11d2,\
  {0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}}

typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
 EFI_TEXT_RESET                           Reset; //reset函数指针,用于重置文本输出设置硬件
 EFI_TEXT_STRING                          OutputString; //string函数指针,用于输出文本
 EFI_TEXT_TEST_STRING                     TestString; //测试Unicode字符编码,解决字符渲染兼容性
 EFI_TEXT_QUERY_MODE                      QueryMode; //查询当前文本输出设备支持的所有显示模式
 EFI_TEXT_SET_MODE                        SetMode; //切换显示模式
 EFI_TEXT_SET_ATTRIBUTE                   SetAttribute; //设置后续输出文本的前景色与背景色
 EFI_TEXT_CLEAR_SCREEN                    ClearScreen; //使用背景色清空整个屏幕
 EFI_TEXT_SET_CURSOR_POSITION             SetCursorPosition; //设置光标位置
 EFI_TEXT_ENABLE_CURSOR                   EnableCursor; //启用光标可见
 SIMPLE_TEXT_OUTPUT_MODE                  *Mode; //用于信息查询与解析
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

为了方便解释这个output函数的能力,可以以*mode的解析展开:

//******************************************************
// SIMPLE_TEXT_OUTPUT_MODE
//******************************************************
typedef struct {
 INT32                              MaxMode; //最大模式数
 // current settings
 INT32                              Mode; //当前模式数
 INT32                              Attribute; //当前文本属性
 INT32                              CursorColumn; //光标所在列坐标
 INT32                              CursorRow; //光标所在行坐标
 BOOLEAN                            CursorVisible; //可见性
} SIMPLE_TEXT_OUTPUT_MODE;

output的输出string也是依赖光标展开的,所以第一步是解析光标函数:

typedef
EFI_STATUS
(EFIAPI *EFI_TEXT_SET_CURSOR_POSITION) (
 IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL             *This,
 IN UINTN                                       Column,
 IN UINTN                                       Row
 );

字符串的逻辑坐标像物理像素坐标转换的过程中会存在一些衍生问题,比如逻辑上一行最多有多少个逻辑字符,什么时候换行,行宽是多少等等。为了解决这些问题,需要由mode指针EFI_TEXT_QUERY_MODE这个具体的结构,对其逻辑上的行列做解析:

typedef
EFI_STATUS
(EFIAPI *EFI_TEXT_QUERY_MODE) (
 IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL          *This,
 IN UINTN                                    ModeNumber,
 OUT UINTN                                   *Columns,
 OUT UINTN                                   *Rows
 );

这里的行、列,含义是屏幕每一行 最多可以容纳多少个字符(Columns列数),屏幕垂直方向最多可以显示多少行字符(Rows行数)。

除了逻辑字符之外,还需要理清Attribute所描述的字符本体色、字符背景色。INT32 Attribute这个变量的BIT 0-3描述前景色,BIT4-7描述背景色。相关的颜色由下面描述

//*******************************************************
// Attributes
//*******************************************************
#define EFI_BLACK                              0x00
#define EFI_BLUE                               0x01
#define EFI_GREEN                              0x02
#define EFI_CYAN                               0x03
#define EFI_RED                                0x04
#define EFI_MAGENTA                            0x05
#define EFI_BROWN                              0x06
#define EFI_LIGHTGRAY                          0x07
#define EFI_BRIGHT                             0x08
#define EFI_DARKGRAY(EFI_BLACK \| EFI_BRIGHT)  0x08
#define EFI_LIGHTBLUE                          0x09
#define EFI_LIGHTGREEN                         0x0A
#define EFI_LIGHTCYAN                          0x0B
#define EFI_LIGHTRED                           0x0C
#define EFI_LIGHTMAGENTA                       0x0D
#define EFI_YELLOW                             0x0E
#define EFI_WHITE                              0x0F


#define EFI_BACKGROUND_BLACK                   0x00
#define EFI_BACKGROUND_BLUE                    0x10
#define EFI_BACKGROUND_GREEN                   0x20
#define EFI_BACKGROUND_CYAN                    0x30
#define EFI_BACKGROUND_RED                     0x40
#define EFI_BACKGROUND_MAGENTA                 0x50
#define EFI_BACKGROUND_BROWN                   0x60
#define EFI_BACKGROUND_LIGHTGRAY               0x70
//
// Macro to accept color values in their raw form to create
// a value that represents both a foreground and background
// color in a single byte.
// For Foreground, and EFI_\* value is valid from EFI_BLACK(0x00)
// to EFI_WHITE (0x0F).
// For Background, only EFI_BLACK, EFI_BLUE, EFI_GREEN,
// EFI_CYAN, EFI_RED, EFI_MAGENTA, EFI_BROWN, and EFI_LIGHTGRAY
// are acceptable.
//
// Do not use EFI_BACKGROUND_xxx values with this macro.
//#define EFI_TEXT_ATTR(Foreground,Background) \
((Foreground) | ((Background) << 4))

目前只能通过拼接的方式设置单纯颜色,还无法根据SIMPLE TEXT的什么接口去设置字体或行间距等复杂的内容。

到这里,其实作为output的协议介绍就差不多了,生成一个bash需要进一步学习input的东西。

INPUT接口

系统表一般只会将*ConIn指向EFI_SIMPLE_TEXT_INPUT_PROTOCOL,而支持组合键的SIMPLE_TEXT_INPUT_EX_PROTOCOL则是另外的协议。

对于SIMPLE TEXT的协议来说,如下:

#define EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID \
 {0x387477c1,0x69c7,0x11d2,\
  {0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}}

typedef struct _EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
 EFI_INPUT_RESET                       Reset;
 EFI_INPUT_READ_KEY                    ReadKeyStroke;
 EFI_EVENT                             WaitForKey;
} EFI_SIMPLE_TEXT_INPUT_PROTOCOL;

其中key的解析与事件的解析如下:

typedef
EFI_STATUS
(EFIAPI \*EFI_INPUT_READ_KEY) (
 IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL              *This,
 OUT EFI_INPUT_KEY                              *Key
 );

typedef struct {
    UINT16     ScanCode;      // 扫描码:标识特殊功能键
    CHAR16     UnicodeChar;   // Unicode字符:标识普通可打印字符
} EFI_INPUT_KEY;

//这里是使用方法举例,需要首先使用waitforevent进行阻滞,然后再在后面写阻滞相关的处理内容
Status = gBS->WaitForEvent(1, &ConIn->WaitForKey, &Index);
Status = ConIn->ReadKeyStroke(ConIn, &Key);
if (EFI_ERROR(Status)) {
  SystemTable->ConOut->OutputString(SystemTable->ConOut, L"WaitForEvent failed.\r\n");
  return Status;
}
if (!EFI_ERROR(Status)) {
  CHAR16 Buffer;
  Buffer = Key.UnicodeChar;
}

有关于输入KEY的内容类别需要单独新开一篇文章来细说了,目前它来源于UEFI协议的附录阶段,在B.1 EFI_SIMPLE_TEXT_INPUT_PROTOCOL中有关于具体键盘KEY类型的描述。


评论