EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL関係のTIPSを紹介します。
SetAttribute()を使うことで文字色と文字の背景色を設定できます(リスト1.1)。
サンプルのディレクトリは"010_simple_text_output_set_attribute"です。
第2引数Attributeに文字色と背景色を1バイトの値で設定します。上位4ビットが背景色で下位4ビットが文字色です。
SetAttribute()を使用して表紙画像を画面表示するサンプルがリスト1.2です。
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"です。
TestString()は引数に文字列の先頭アドレスを指定し、判定結果は戻り値で受け取ります。出力可能な場合はEFI_SUCCESS(=0)を、出力できない文字が含まれる場合はEFI_UNSUPPORTED(=0x80000000 00000003)を返します。
使用例はリスト1.4の通りです。
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.2の通り、test1~test3の全てが成功します。
QEMU用のUEFIファームウェアであるOVMFには実行バイナリサイズに制限がある様子で、コード量が増え、実行バイナリサイズが大きくなると、同じ実行バイナリが実機では実行できるがQEMU上では実行できない(UEFIファームウェアが実行バイナリを見つけてくれない)事態に陥ります。
当シリーズのUEFIベアメタルプログラミングの対象は主に実機(Lenovo製で動作確認)であるため*2、本書ではQEMU上で動作させる際は、Makefileを修正し、不要なソースコードをコンパイル・リンクの対象に含めないようにしています。
[*2] 言い訳ですが。。
例えば、リスト1.4をQEMU上で動作させる際は、Makefileをリスト1.5のように修正し、makeを行って下さい。
コンソールへのテキスト出力に関して、これまではUEFIのファームウェアが認識するデフォルトのモード(テキストモード)で使用してきました。ただし、コンソールデバイスによってはその他のテキストモードへ切り替えることで、画面あたりの列数・行数を変更できます。
サンプルのディレクトリは"012_simple_text_output_query_mode"です。
QueryMode()でテキストモード番号に対する画面あたりの列数・行数を取得できます(リスト1.6)。
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の通りです。
以上を踏まえ、QueryMode()の使用例はリスト1.8の通りです。
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の通りです。
SetMode()でテキストモードを変更できます(リスト1.9)。
サンプルのディレクトリは"013_simple_text_output_set_mode"です。
使用例はリスト1.10の通りです。
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の通りです。テキストモードによって見た目がどう変わるのかは、ぜひご自身の実機で試してみてください。
EFI_GRAPHICS_OUTPUT_PROTOCOLもQueryMode()とSetMode()を持っています。そのため、フレームバッファのグラフィックモードについても、QueryMode()でモード番号に対する情報(解像度)を取得し、SetMode()で指定したモード番号へ変更することができます。
参考までに、EFI_GRAPHICS_OUTPUT_PROTOCOLのQueryMode()とSetMode()の定義をリスト1.11に示します。
なお、EFI_GRAPHICS_OUTPUT_PROTOCOLのQueryMode()は、第2引数で無効なモード番号を指定した時、EFI_INVALID_PARAMETERを返します。