EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL関係のTIPSを紹介します。
SetAttribute()を使うことで文字色と文字の背景色を設定できます(リスト1.1)。
サンプルのディレクトリは"010_simple_text_output_set_attribute"です。
リスト1.1: SetAttribute()の定義(efi.hより)
//******************************************************* // 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 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 struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { ・・・ unsigned long long (*SetAttribute)( struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, unsigned long long Attribute /* 文字色と文字の背景色を設定 * 設定できる色は上記defineの通り */ ); ・・・ };
第2引数Attributeに文字色と背景色を1バイトの値で設定します。上位4ビットが背景色で下位4ビットが文字色です。
SetAttribute()を使用して表紙画像を画面表示するサンプルがリスト1.2です。
リスト1.2: SetAttribute()の使用例(main.cより)
1: #include "efi.h" 2: #include "common.h" 3: 4: void efi_main(void *ImageHandle __attribute__ ((unused)), 5: struct EFI_SYSTEM_TABLE *SystemTable) 6: { 7: efi_init(SystemTable); 8: 9: puts(L" "); 10: 11: ST->ConOut->SetAttribute(ST->ConOut, 12: EFI_LIGHTGREEN | EFI_BACKGROUND_LIGHTGRAY); 13: ST->ConOut->ClearScreen(ST->ConOut); 14: 15: puts(L"\r\n\r\n\r\n\r\n\r\n\r\n\r\n"); 16: puts(L" "); 17: puts(L"フルスクラッチで作る!\r\n"); 18: 19: ST->ConOut->SetAttribute(ST->ConOut, 20: EFI_LIGHTRED | EFI_BACKGROUND_LIGHTGRAY); 21: puts(L" "); 22: puts(L"UEFIベアメタルプログラミング\r\n"); 23: 24: ST->ConOut->SetAttribute(ST->ConOut, 25: EFI_LIGHTMAGENTA | EFI_BACKGROUND_LIGHTGRAY); 26: puts(L" "); 27: puts(L"パート2\r\n"); 28: 29: ST->ConOut->SetAttribute(ST->ConOut, 30: EFI_LIGHTBLUE | EFI_BACKGROUND_LIGHTGRAY); 31: puts(L"\r\n\r\n\r\n\r\n\r\n\r\n\r\n"); 32: puts(L" "); 33: puts(L"大ネ申 ネ右真 著\r\n"); 34: 35: ST->ConOut->SetAttribute(ST->ConOut, 36: EFI_WHITE | EFI_BACKGROUND_LIGHTGRAY); 37: puts(L"\r\n\r\n\r\n\r\n\r\n\r\n\r\n"); 38: puts(L" "); 39: puts(L"2017-10-22 版 "); 40: 41: ST->ConOut->SetAttribute(ST->ConOut, 42: EFI_LIGHTCYAN | EFI_BACKGROUND_LIGHTGRAY); 43: puts(L"henyapente "); 44: 45: ST->ConOut->SetAttribute(ST->ConOut, 46: EFI_MAGENTA | EFI_BACKGROUND_LIGHTGRAY); 47: puts(L"発 行\r\n"); 48: 49: while (TRUE); 50: }
リスト1.2では、efi_init()の直後に半角スペースを1つだけ出力しています。これは、実機(筆者の場合Lenovo製)のUEFIファームウェアの挙動に合わせるためで、筆者の実機の場合、起動直後の何も画面出力していない状態のClearScreen()は無視されるようで、たとえその直前でSetAttribute()で属性を設定していたとしても画面を指定した背景色でクリアすることができませんでした。そこで、一度、何らかの画面表示を行う必要があり、SetAttribute()とClearScreen()の直前に半角スペースの出力を入れています*1。
[*1] 「何も画面出力を行っていないのだからClearScreen()は無視する」というファームウェア側の実装は分からなくは無いのですが、SetAttribute()で背景色を変更している場合は新しい背景色で描画しなおして欲しいですね。
UEFIでは文字はUnicodeで扱います。Unicodeの範囲としては日本語なども含みますが、UEFIのファームウェアの実装としてすべての文字をサポートしているとは限りません。
TestString()を使うことで、文字(列)が出力可能か否かを判定できます(リスト1.3)。
サンプルのディレクトリは"011_simple_text_output_test_string"です。
リスト1.3: TestString()の定義(efi.hより)
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { ・・・ unsigned long long (*TestString)( struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, unsigned short *String /* 判定したい文字列の先頭アドレスを指定 */ ); ・・・ };
TestString()は引数に文字列の先頭アドレスを指定し、判定結果は戻り値で受け取ります。出力可能な場合はEFI_SUCCESS(=0)を、出力できない文字が含まれる場合はEFI_UNSUPPORTED(=0x80000000 00000003)を返します。
使用例はリスト1.4の通りです。
リスト1.4: TestString()の使用例(main.cより)
1: #include "efi.h" 2: #include "common.h" 3: 4: void efi_main(void *ImageHandle __attribute__ ((unused)), 5: struct EFI_SYSTEM_TABLE *SystemTable) 6: { 7: efi_init(SystemTable); 8: ST->ConOut->ClearScreen(ST->ConOut); 9: 10: /* test1 */ 11: if (!ST->ConOut->TestString(ST->ConOut, L"Hello")) 12: puts(L"test1: success\r\n"); 13: else 14: puts(L"test1: fail\r\n"); 15: 16: /* test2 */ 17: if (!ST->ConOut->TestString(ST->ConOut, L"こんにちは")) 18: puts(L"test2: success\r\n"); 19: else 20: puts(L"test2: fail\r\n"); 21: 22: /* test3 */ 23: if (!ST->ConOut->TestString(ST->ConOut, L"Hello,こんにちは")) 24: puts(L"test3: success\r\n"); 25: else 26: puts(L"test3: fail\r\n"); 27: 28: while (TRUE); 29: }
QEMU上で実行してみると、OVMFのファームウェアは日本語に対応していない為、図1.1の通り、日本語を含む文字列を指定しているtest2とtest3は失敗する結果となります。なお、QEMU上で実行する際にはMakefileを一部修正する必要があります。詳しくは後述のコラムを参照してください。
図1.1: QEMUでのTestString()の実行例
日本語表示が可能な実機で実行してみると図1.2の通り、test1~test3の全てが成功します。
図1.2: 実機でのTestString()の実行例
QEMU用のUEFIファームウェアであるOVMFには実行バイナリサイズに制限がある様子で、コード量が増え、実行バイナリサイズが大きくなると、同じ実行バイナリが実機では実行できるがQEMU上では実行できない(UEFIファームウェアが実行バイナリを見つけてくれない)事態に陥ります。
当シリーズのUEFIベアメタルプログラミングの対象は主に実機(Lenovo製で動作確認)であるため*2、本書ではQEMU上で動作させる際は、Makefileを修正し、不要なソースコードをコンパイル・リンクの対象に含めないようにしています。
[*2] 言い訳ですが。。
例えば、リスト1.4をQEMU上で動作させる際は、Makefileをリスト1.5のように修正し、makeを行って下さい。
リスト1.5: QEMU(OVMF)上で実行させる際のMakefile
・・・ # fs/EFI/BOOT/BOOTX64.EFI: efi.c common.c file.c graphics.c shell.c gui.c # ↑コメントアウトか削除 fs/EFI/BOOT/BOOTX64.EFI: efi.c common.c main.c <= 追加 mkdir -p fs/EFI/BOOT x86_64-w64-mingw32-gcc -Wall -Wextra -e efi_main -nostdinc \ -nostdlib -fno-builtin -Wl,--subsystem,10 -o $@ $+ ・・・
コンソールへのテキスト出力に関して、これまではUEFIのファームウェアが認識するデフォルトのモード(テキストモード)で使用してきました。ただし、コンソールデバイスによってはその他のテキストモードへ切り替えることで、画面あたりの列数・行数を変更できます。
サンプルのディレクトリは"012_simple_text_output_query_mode"です。
QueryMode()でテキストモード番号に対する画面あたりの列数・行数を取得できます(リスト1.6)。
リスト1.6: QueryMode()の定義(efi.hより)
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { ・・・ unsigned long long (*QueryMode)( struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, unsigned long long ModeNumber, /* 列数・行数を確認したいモード番号を指定 */ unsigned long long *Columns, /* 列数を格納する変数へのポインタ */ unsigned long long *Rows); /* 行数を格納する変数へのポインタ */ ・・・ };
QueryMode()は、第2引数で指定されたモード番号がコンソールデバイスでサポート外の場合、EFI_UNSUPPORTED(0x80000000 00000003)を返します。
なお、コンソールデバイスで使用できるモード番号の範囲はEFI_SIMPLE_TEXT_OUTPUT_PROTOCOL->Mode->MaxModeで確認できます。EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL->Modeは、SIMPLE_TEXT_OUTPUT_MODEという構造体で、定義はリスト1.7の通りです。
リスト1.7: SIMPLE_TEXT_OUTPUT_MODE構造体の定義(efi.hより)
#define EFI_SUCCESS 0 #define EFI_ERROR 0x8000000000000000 #define EFI_UNSUPPORTED (EFI_ERROR | 3) struct SIMPLE_TEXT_OUTPUT_MODE { int MaxMode; /* コンソールデバイスで使用できるモード番号の最大値 */ int Mode; /* 現在のモード番号 */ int Attribute; /* 現在設定されている属性値(文字色・背景色) */ int CursorColumn; /* カーソル位置(列) */ int CursorRow; /* カーソル位置(行) */ unsigned char CursorVisible; /* 現在のカーソル表示設定(表示=1/非表示=0) */ };
以上を踏まえ、QueryMode()の使用例はリスト1.8の通りです。
リスト1.8: QueryMode()の使用例(main.cより)
1: #include "efi.h" 2: #include "common.h" 3: 4: void efi_main(void *ImageHandle __attribute__ ((unused)), 5: struct EFI_SYSTEM_TABLE *SystemTable) 6: { 7: int mode; 8: unsigned long long status; 9: unsigned long long col, row; 10: 11: efi_init(SystemTable); 12: ST->ConOut->ClearScreen(ST->ConOut); 13: 14: for (mode = 0; mode < ST->ConOut->Mode->MaxMode; mode++) { 15: status = ST->ConOut->QueryMode(ST->ConOut, mode, &col, &row); 16: switch (status) { 17: case EFI_SUCCESS: 18: puth(mode, 2); 19: puts(L": "); 20: puth(col, 4); 21: puts(L" x "); 22: puth(row, 4); 23: puts(L"\r\n"); 24: break; 25: 26: case EFI_UNSUPPORTED: 27: puth(mode, 2); 28: puts(L": unsupported\r\n"); 29: break; 30: 31: default: 32: assert(status, L"QueryMode"); 33: break; 34: } 35: } 36: 37: while (TRUE); 38: }
実行例は図1.3の通りです。
図1.3: QueryMode()の実行例
SetMode()でテキストモードを変更できます(リスト1.9)。
サンプルのディレクトリは"013_simple_text_output_set_mode"です。
リスト1.9: SetMode()の定義(efi.hより)
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL { ・・・ unsigned long long (*SetMode)( struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This, unsigned long long ModeNumber /* テキストモード番号 */ ); ・・・
使用例はリスト1.10の通りです。
リスト1.10: SetMode()の使用例(main.cより)
1: #include "efi.h" 2: #include "common.h" 3: 4: void efi_main(void *ImageHandle __attribute__ ((unused)), 5: struct EFI_SYSTEM_TABLE *SystemTable) 6: { 7: int mode; 8: unsigned long long status; 9: unsigned long long col, row; 10: 11: efi_init(SystemTable); 12: ST->ConOut->ClearScreen(ST->ConOut); 13: 14: while (TRUE) { 15: for (mode = 0; mode < ST->ConOut->Mode->MaxMode; mode++) { 16: status = ST->ConOut->QueryMode(ST->ConOut, mode, &col, 17: &row); 18: if (status) 19: continue; 20: 21: ST->ConOut->SetMode(ST->ConOut, mode); 22: ST->ConOut->ClearScreen(SystemTable->ConOut); 23: 24: puts(L"mode="); 25: puth(mode, 1); 26: puts(L", col=0x"); 27: puth(col, 2); 28: puts(L", row=0x"); 29: puth(row, 2); 30: puts(L"\r\n"); 31: puts(L"\r\n"); 32: puts(L"Hello UEFI!こんにちは、せかい!"); 33: 34: getc(); 35: } 36: } 37: }
リスト1.10では、コンソールデバイスが対応しているテキストモードを順に切り替え、テキストモードの情報と簡単なテキスト("Hello UEFI!こんにちは、世界!")を表示します。表示後、getc()で待ちますので、何らかのキーを入力してください(すると、次のテキストモードへ切り替わります)。
実行例は図1.4と図1.5の通りです。テキストモードによって見た目がどう変わるのかは、ぜひご自身の実機で試してみてください。
図1.4: SetModeの実行例:モード0(列数80(0x50),行数25(0x19))の場合
図1.5: SetModeの実行例:モード5(列数170(0xAA),行数40(0x28))の場合
EFI_GRAPHICS_OUTPUT_PROTOCOLもQueryMode()とSetMode()を持っています。そのため、フレームバッファのグラフィックモードについても、QueryMode()でモード番号に対する情報(解像度)を取得し、SetMode()で指定したモード番号へ変更することができます。
参考までに、EFI_GRAPHICS_OUTPUT_PROTOCOLのQueryMode()とSetMode()の定義をリスト1.11に示します。
リスト1.11: QueryMode()とSetMode()の定義
#define EFI_SUCCESS 0 #define EFI_ERROR 0x8000000000000000 #define EFI_INVALID_PARAMETER (EFI_ERROR | 2) struct EFI_GRAPHICS_OUTPUT_PROTOCOL { unsigned long long (*QueryMode)( struct EFI_GRAPHICS_OUTPUT_PROTOCOL *This, unsigned int ModeNumber, /* 解像度等を確認したいモード番号を指定 */ unsigned long long *SizeOfInfo, /* 第3引数"info"のサイズが設定される * 変数のポインタを指定 */ struct EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info /* グラフィックモードの情報を格納する構造体 * EFI_GRAPHICS_OUTPUT_MODE_INFORMATION への * ポインタのポインタを指定 */ ); unsigned long long (*SetMode)( struct EFI_GRAPHICS_OUTPUT_PROTOCOL *This, unsigned int ModeNumber /* グラフィックモード番号 */ ); ・・・ };
なお、EFI_GRAPHICS_OUTPUT_PROTOCOLのQueryMode()は、第2引数で無効なモード番号を指定した時、EFI_INVALID_PARAMETERを返します。