カーネル空間から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を触るにはどうすれば良いのだろう?