UEFI固件本体向efi文件传输参数时,会向入口函数传递两个核心参数:ImageHandle和SystemTable。
GRUB主要还是与UEFI打配合的,UEFI固件本身需要遵从UEFI协议,从下文的描述中也可以看出UEFI协议的一些核心概念,比如protocol的概念。UEFI固件本身的源代码编辑的话,一般可以参考开源的EDK2项目来进行学习。
由objdump -S 命令可以查看.text代码段的最前面部分为

这与startup.S文件中的描述是一样的:(/grub-core/kern/arm64/efi/startup.S)

这里就可以明确,作为efi文件的开头,实际上是一个用于接收来自UEFI固件两个参数,并跳转到main函数所在位置的一个整体封装的函数。
从代码中可以确认,会给定efi_image_handle_val与efi_system_table_val两个变量。
其中efi image handle起始就是efi这个程序本身的句柄;efi system table这个变量最终会宏展开为如下的一个struct
struct grub_efi_system_table
{
grub_efi_table_header_t hdr; // 一个用于UEFI格式表头的数字签名相关字段
grub_efi_char16_t *firmware_revision; // 固件供应商名称
grub_efi_uint32_t firmware_revision; // 固件版本号
grub_efi_handle_t console_in_handler; // 输入控制台
grub_efi_simple_input_interface_t *con_in; // 输入指针
grub_efi_handle_t console_out_handler; // 输出控制台
grub_efi_simple_text_output_interface_t *std_err; // 输出指针
grub_efi_runtime_services_t *runtime_services; // 运行时服务表
grub_efi_boot_services_t *boot_services; // 启动服务表
grub_efi_uintn_t num_table_entries; // 系统配置表数目
grub_efi_configuration_table_t *configuration_table; // 系统配置表
}这里进一步涉及到一系列表项的更具象化的展开。
表头结构体
首先是介绍一下表头的结构:
struct grub_efi_table_header
{
grub_efi_uint64_t signature; // 一个64位宽的数字签名
grub_efi_uint32_t revision; // 表明这个表所遵循的UEFI版本号
grub_efi_uint32_t header_size; // 整固件表的大小
grub_efi_uint32_t crc32; // 整表的CRC32校验码
grub_efi_uint32_t reserved; // 保留字段
}grub_efi_handle_t 一般认为是在grub中标识一个void的意思,所以这里的输入控制台应该含义为一个函数的地址。
输入interface结构体
grub_efi_simple_input_interface这个指针的struct为
struct grub_efi_simple_input_interface
{
grub_efi_status_t (__grub_efi_api *reset) (struct grub_efi_simple_input_interface *this, grub_efi_boolean_t extended_verification); // 一个将控制台输入设备重置为初始状态的按钮
grub_eft_status_t (__grub_efi_api *read_key_stroke) (struct grub_efi_simple_input_interface *this, grub_efi_input_key_t *key); // 从输入队列中读取下一个按键数据
grub_efi_event_t wait_for_key; // 这是一个事件对象,当输入队列中有按键可以用时,该事件会触发
}grub_efi_status_t本身可以被展开成为一个uint64;__grub_efi_api是一个宏定义,这里就是空的,所以这个含义可以写为:
uint64 *reset (struct grub_efi_simple_input_interface this, grub_efi_boolean_t extended_verification);
这样的一个函数定义表达式。
grub_efi_boolean_t 就是一个char
grub_efi_input_key是一个如下的二元结构:
strict grub_efi_input_key
{
grub_efi_uint16_t scan_code; // 扫描码,用于标识不可打印字符按键
grub_efi_char16_t unicode_char; // Unicode字符,用于表示可打印字符按键
}这里实际上对应的是 UEFI规范中的EFI INPUT KEY的结构体,这里还可以进一步放一个可以引用的扫描码与Unicode码的表:
// 扫描码:
SCAN_UP 0x0001 # 方向上键
SCAN_DOWN 0x0002 # 方向下键
SCAN_RIGHT 0x0003 # 方向右键
SCAN_LEFT 0x0004 # 方向左键
SCAN_HOME 0x0005 # Home 键
SCAN_END 0x0006 # End 键
SCAN_INSERT 0x0007 # Insert 键
SCAN_DELETE 0x0008 # Delete 键
SCAN_PAGE_UP 0x0009 # Page Up 键
SCAN_PAGE_DOWN 0x000A # Page Down 键
SCAN_F1 0x000B # F1 键
SCAN_F2 0x000C # F2 键
SCAN_F3 0x000D # F3 键
SCAN_F4 0x000E # F4 键
SCAN_F5 0x000F # F5 键
SCAN_F6 0x0010 # F6 键
SCAN_F7 0x0011 # F7 键
SCAN_F8 0x0012 # F8 键
SCAN_F9 0x0013 # F9 键
SCAN_F10 0x0014 # F10 键
SCAN_ESC 0x0017 # ESC 键
// Unicode码
CHAR_NULL 0x0000 # 空字符
CHAR_BACKSPACE 0x0008 # 退格键
CHAR_TAB 0x0009 # Tab 键
CHAR_LINEFEED 0x000A # 换行符
CHAR_CARRIAGE_RETURN 0x000D # 回车键值得注意的事情是,这里的reset、read_key_stroke、wait_for_key共2个函数1个时间,此时并不在efi文件内完成实例化,需要等待UEFI固件实例这几个函数后,由system table表格传递进来的时候,才能正式的使用调用这些函数。
总结一下就是,输入指针对应一个reset函数、一个read key stroke函数,一个wait for key事件。
输出interface结构体
输出interface结构体可以展开为grub_efi_simple_text_output_interface
struct grub_efi_simple_text_output_interface
{
grub_efi_status_t (__grub_efi_api *reset) (struct grub_efi_simple_text_output_interface *this, grub_efi_boolean_t extended_verification); // reset函数,意思就是执行重启
grub_efi_status_t (__grub_efi_api *output_string) (struct grub_efi_simple_text_output_interface *this, grub_efi_char16_t *string); // 输出字符串的函数,需要给定一个具体的char16
grub_efi_status_t (__grub_efi_api *test_string) (struct grub_efi_simple_text_output_interface *this, grub_efi_char16_t *string); // 一个用于测试所有字符是否可以被正常显示的函数,如果所有字符都显示正确,返回success
grub_efi_status_t (__grub_efi_api *query_mode) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t mode_number, grub_efi_uintn_t *columns, grub_efi_uintn_t *rows); // 查询文本模式信息
grub_efi_status_t (__grub_efi_api *set_mode) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t mode_number); // 设置文本模式
grub_efi_status_t (__grub_efi_api *set_attributes) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t attribute); // 设置文本属性
grub_efi_status_t (__grub_efi_api *clear_screen) (struct grub_efi_simple_text_output_interface *this); // 清屏
grub_efi_status_t (__grub_efi_api *set_cursor_position) (struct grub_efi_simple_text_output_interface *this, grub_efi_uintn_t column, grub_efi_uintn_t row); // 设置光标位置
grub_efi_status_t (__grub_efi_api *enable_cursor) (struct grub_efi_simple_text_output_interface *this, grub_efi_boolean_t visible); // 控制光标可见性
grub_efi_simple_text_output_mode_t *mode; //这是一个指向模式结构体的指针
}这里output的interface笼统的涵盖reset、output string、test string、query mode、set mode、set attributes、clear screen、set cursor position、enable cursor共8个函数与一个mode的结构体。
这里的mode结构体如下:
struct grub_efi_simple_text_output_mode
{
grub_efi_int32_t max_mode; // 最大模式数目
grub_efi_int32_t mode; // 当前模式编号
grub_efi_int32_t attribute; // 当前使用的文本属性,包含4位的前景颜色与4位的后景颜色
grub_efi_int32_t cursor_column; // 当前光标所在列位置
grub_efi_int32_t cursor_row; // 当前光标所在行位置
grub_efi_boolean_t cursor_visible; // 光标是否可见
};可以看出,这里由system table给定的一个output结构体,可以在不描述具体内部函数实现的前提下把一个terminal一样的东西抽象一个struct出来了。具体的system table中interface的实现,可以在main函数中随着machine init函数的展开逐步具体的讲一讲。
启动服务表
grub_efi_boot_services,启动服务接口,在GRUB运行操作系统内核时,提供内存管理、设备访问、程序加载等关键服务。
下面是这个表中描述的具体内容:
struct grub_efi_boot_services
{
grub_efi_table_header_t hdr;
grub_efi_tpl_t (__grub_efi_api *raise_tpl) (grub_efi_tpl_t new tpl);
viod (__grub_efi_api *restore_tpl) (grub_efi_tpl_t old_tpl);
grub_efi_status_t (__grub_efi_api *allocate_pages) (grub_efi_allocate_type_t type, grub_efi_memory_type_t memory_type, grub_efi_uintn_t pages, grub_efi_physical_address_t *memory);
grub_efi_status_t (__grub_efi_api *free_pages) (grub_efi_physical_address_t memory, grub_efi_uintn_t pages);
grub_efi_status_t (__grub_efi_api *get_memory_map) (grub_efi_uintn_t *memory_map_size, grub_efi_memory_descriptor_t *memory_map, grub_efi_uintn_t *map_key, grub_efi_uintn_t *descriptor_size, grub_efi_uint32_t *descriptor_version);
grub_efi_status_t (__grub_efi_api *allocate_pool) (grub_efi_memory_type_t pool_type, grub_efi_uintn_t size, void **buffer);
grub_efi_status_t (__grub_efi_api *free_pool) (void *buffer);
grub_efi_status_t (__grub_efi_api *create_event) (grub_efi_uint32_t type, grub_efi_tpl_t notify_tpl, void (__grub_efi_api *notify_function) (grub_efi_event_t, void *context), void *notify_context, grub_efi_event_t *event);
grub_efi_status_t (__grub_efi_api *set_timer) (grub_efi_event_t event, grub_efi_timer_delay_t type, grub_efi_uint64_t trigger_time);
grub_efi_status_t (__grub_efi_api *wait_for_event) (grub_efi_uintn_t num_events, grub_efi_event_t *event, grub_efi_uintn_t *index);
grub_efi_status_t (__grub_efi_api *signal_event) (grub_efi_event_t event);
grub_efi_status_t (__grub_efi_api *close_event) (grub_efi_event_t event);
grub_efi_status_t (__grub_efi_api *check_event) (grub_efi_event_t event);
grub_efi_status_t (__grub_efi_api *install_protocol_interface) (grub_efi_handle_t *handle, grub_guid_t *protocol, grub_efi_interface_type_t protocol_interface_type, void *protocol_interface);
。。。。。。
}由于boot services的表中内容过于多了(共有45中方法),这里先简单总结一下启动服务表能干啥,未来在新的文章中再详细解释。
总的来说,boot services表提供了如下内容:
1、对pages的操作方法,允许你占用或释放pages,允许你获取内存地图,并以pool的方式进行内存的占用或者释放
2、提供3中直接方法:设置时间、直接修改内存、直接复制内存
3、提供事件机制,包括等待事件、关闭事件、检查事件等
4、提供了protocol协议的安装方法或者卸载方法,包括单个的方法与多个的方法
5、提供了image相关的操作,比如加载、卸载、开始等
目前主要总结为上述方法。
系统配置表
grub_efi_configuration_table主要用于向操作系统传递固件级别的系统信息。
这里给出GRUB中的struct:
struct grub_efi_configuration_table
{
grub_packed_guid_t vendor_guid;
void *vendor_table;
} GRUB_PACKED;其中vendor guid代表分区的GUID标识,vendor table指向实际数据结构。
GUID本身是一种配置表项的类型描述。
一般认为系统配置表会指向一个类似ACPI表的结构,这一点在日后分析ACPI模块时再展开讲,目前可以理解系统配置表为:一个用于描述硬件信息的配置表。