カーネル空間からUEFI Runtime APIを触る
UEFI の Runtime serviceをLinuxのカーネル空間からアクセスする方法について。 手元にあった Linux 4.1.27 で確認しています。
ざっくりまとめ
include/linux/efi.h に efiという構造体が定義されていて、そこにRuntime serviceの各APIの関数ポインタが集約されている。 各APIの仕様はUEFIの仕様書参照のこと。
Unicode変換やユーザー空間からのアクセスはどうやるんだろう?
http://www.uefi.org/specifications
include/linux/efi.h
efi 構造体
include/linux/efi.hに定義されている。メンバはRuntime serviceの各API。runtime_version 変数から実行環境のUEFI Versionを取れたりする。
extern struct efi { efi_system_table_t *systab; /* EFI system table */ unsigned int runtime_version; /* Runtime services version */ unsigned long mps; /* MPS table */ unsigned long acpi; /* ACPI table (IA64 ext 0.71) */ unsigned long acpi20; /* ACPI table (ACPI 2.0) */ unsigned long smbios; /* SMBIOS table (32 bit entry point) */ unsigned long smbios3; /* SMBIOS table (64 bit entry point) */ unsigned long sal_systab; /* SAL system table */ unsigned long boot_info; /* boot info table */ unsigned long hcdp; /* HCDP table */ unsigned long uga; /* UGA table */ unsigned long uv_systab; /* UV system table */ unsigned long fw_vendor; /* fw_vendor */ unsigned long runtime; /* runtime table */ unsigned long config_table; /* config tables */ efi_get_time_t *get_time; efi_set_time_t *set_time; efi_get_wakeup_time_t *get_wakeup_time; efi_set_wakeup_time_t *set_wakeup_time; efi_get_variable_t *get_variable; efi_get_next_variable_t *get_next_variable; efi_set_variable_t *set_variable; efi_set_variable_nonblocking_t *set_variable_nonblocking; efi_query_variable_info_t *query_variable_info; efi_update_capsule_t *update_capsule; efi_query_capsule_caps_t *query_capsule_caps; efi_get_next_high_mono_count_t *get_next_high_mono_count; efi_reset_system_t *reset_system; efi_set_virtual_address_map_t *set_virtual_address_map; struct efi_memory_map *memmap; unsigned long flags; } efi;
APIの実装はdrivers/firmware/efi/runtime-wrappers.c。下記のように各API内部で排他してくれている。
static efi_status_t virt_efi_set_variable(efi_char16_t *name, efi_guid_t *vendor, u32 attr, unsigned long data_size, void *data) { unsigned long flags; efi_status_t status; spin_lock_irqsave(&efi_runtime_lock, flags); status = efi_call_virt(set_variable, name, vendor, attr, data_size, data); spin_unlock_irqrestore(&efi_runtime_lock, flags); return status; }
GUID変換
GUID変換は下記のマクロを使う。
#define EFI_GUID(a,b,c,d0,d1,d2,d3,d4,d5,d6,d7) \ ((efi_guid_t) \ {{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \ (b) & 0xff, ((b) >> 8) & 0xff, \ (c) & 0xff, ((c) >> 8) & 0xff, \ (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }})
同ヘッダ内で下記のように使用されている。
#define ACPI_TABLE_GUID \ EFI_GUID( 0xeb9d2d30, 0x2d88, 0x11d3, 0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d )
文字列変換
UEFI使用上はUnicodeを使用する必要がある。カーネル内部的にはefi_char16_t。
typedef u16 efi_char16_t; /* UNICODE character */
efi_char16_t から ascii への変換は、include/linux/ucs2_string.h の ucs2_as_utf8 を使えばいけそう。 ascii から efi_char16_t への変換はどうするのがよいのだろう?
ユーザー空間では
ユーザー空間からUEFI変数を操作する場合は、efivarfs 経由でいける。Rumtime serviceを触るにはどうすれば良いのだろう?