2019年3月14日 星期四

uefi os loader (2) - 使用 GOP 來秀字

囝仔放尿泉過溪,老人放尿滴著鞋。
在 uefi 環境下, 有對應的 print 可以使用, uefi 提供的服務有 boot service, runtime service, print 相關的函式屬於 boot service, 但是一旦呼叫 ExitBootServices() 之後, 就無法使用 print function 這些 boot service, 需要自己使用 framebuffer 來秀字。而在載入 kernel 之前, 需要呼叫 ExitBootServices()。

Graphics Output Protocol (GOP) 就是提供繪圖模式的功能, 所以可以拿來畫圖形, 自然也能拿來畫字。

PI_Spec_1_6.pdf 11.2.3 Additional Classifications
Boot service drivers provide services that are available until the ExitBootServices() function is called. When ExitBootServices() is called, all the memory used by boot service drivers is released for use by an operating system.

UEFI OS loader 需要呼叫 ExitBootServices() 之後才能載入 os kernel, 所以在 os kernel 中, 該怎麼秀訊息?

在 legacy bios 下, 文字模式可以寫入 0xb8000 來印出字元, 繪圖模式是寫入 0xa0000, 這些在 uefi 都已經不能用, 所以連秀字都會是一個問題, 有一個簡單的方式是使用 com1, uefi 會出初始化 com1, 設定 baud rate 115200, 但需要用個 minicom 來看輸出, 會有點麻煩, 而且 notebook 也沒有 com1 可以用。

就算在 uefi loader 階段可以接受 com1 輸出, 但你不想在 os kernel 也用 com1 輸出吧! 感覺這 os kernel 很弱, 觀感不好。所以在這階段, 要把 frame buffer 資訊存起來, 傳遞給 os kernel。

這裡使用的流程是:
  1. 使用 gEfiGraphicsOutputProtocolGuid protocol 取得 framebuffer 相關資訊。
  2. 使用 Gop->QueryMode 找出想要設定的顯示模式。
  3. 再用 Gop->SetMode 設定顯示模式。
vga.c L117 ~ L146 就是在做這樣的事情, 以這個範例來說, 會設定為螢幕為 640X480X32bit color, 3 原色順序是: blue, green, red, reserve, 共 4 byte。

vga.c L276 gop->mode->fb_base 就是 framebuffer 開始位址, 對應到 (0,0) 這個螢幕座標。這就是 legacy bios 0xa0000 的對應值, 把顏色值寫到這區, 就可以畫圖了。

vga.c
  1 // http://www.lab-z.com/51gfxuefi1/
  2 // http://f.osdev.org/viewtopic.php?f=1&t=25587&start=0
  3  
  4 // the code is reference: http://forum.osdev.org/viewtopic.php?f=1&t=26796
  5 
 81 
 82 efi_status_t efi_main(efi_handle_t image, struct efi_system_table *sys_table)
 83 {   
 84   efi_uintn_t mode_num;
 85   efi_status_t status;
 86   efi_uintn_t key, desc_size, size;
 87   struct efi_mem_desc *desc;
 88   //static struct efi_mem_desc desc[200];
 89   efi_handle_t op_handle;
 90 
 91     efi_uintn_t handle_count = 0;
 92     efi_handle_t* handle_buffer;
 93     struct efi_gop* gop;
 94     struct efi_gop_mode_info* gop_mode_info;
 95     efi_uintn_t size_of_info;
 96 
 97   u32 version;
 98   efi_status_t ret;
 99 
100   init_serial();
101 #if 1
102   efi_guid_t loaded_image_guid = LOADED_IMAGE_PROTOCOL_GUID;
103   struct efi_loaded_image *loaded_image;
104 
105   ret = sys_table->boottime->open_protocol(image, &loaded_image_guid,
106       (void **)&loaded_image, image,
107       NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
108 
109   if (ret) 
110   {
111     sys_table->con_out->output_string(sys_table->con_out, L"Failed to get loaded image protocol\n\r");
112 
113     return ret;
114   }
115 #endif
116 
117 #if 1
118     status = sys_table->boottime->locate_handle_buffer( BY_PROTOCOL,
119                                       &EFI_GOP_GUID,
120                                       NULL,
121                                       &handle_count,
122                                       &handle_buffer );
123 
124     if (status != EFI_SUCCESS)
125         PreBootHalt( sys_table->con_out, L"LocateHandleBuffer() failed" );
126     status = sys_table->boottime->handle_protocol( handle_buffer[0],
127                                   &EFI_GOP_GUID,
128                                   (void **)&gop );
129     if (status != EFI_SUCCESS)
130         PreBootHalt( sys_table->con_out, L"HandleProtocol() failed" );
131 
132     for (mode_num = 0;
133          (status = gop->query_mode( gop, mode_num, &size_of_info, &gop_mode_info )) == EFI_SUCCESS;
134          mode_num++) {
135         if (gop_mode_info->width == DESIRED_HREZ &&
136               gop_mode_info->height == DESIRED_VREZ &&
137               gop_mode_info->pixel_format == DESIRED_PIXEL_FORMAT)
138             break;
139     }
140 
141     if (status != EFI_SUCCESS)
142         PreBootHalt( sys_table->con_out, L"Failed to find desired mode" );
143 
144     if (gop->set_mode( gop, mode_num) != EFI_SUCCESS)
145         PreBootHalt( sys_table->con_out, L"SetMode() failed" );
146 #endif
147 
148   sys_table->con_out->output_string(sys_table->con_out, L"005 test vga\n\r");
149 
150   /* Get the memory map so we can switch off EFI */
151   size = 0;
152 
153   ret = sys_table->boottime->get_memory_map(&size, NULL, &key, &desc_size, &version);
154   if (ret != EFI_BUFFER_TOO_SMALL) 
155   {
156     sys_table->con_out->output_string(sys_table->con_out, L"\r\nret is not EFI_BUFFER_TOO_SMALL\n\r");
157     return ret;
158   }
159 
160 #if 1
161         void *buf = NULL;
162         ret = sys_table->boottime->allocate_pool(loaded_image->image_data_type, size, &buf);
163 
164  desc = buf;
165  if (!desc) 
166         {
167           sys_table->con_out->output_string(sys_table->con_out, L"\r\nNo memory for memory descriptor\n\r");
168           return ret;
169  }
170 #endif
176 
177   size += 1024; /* Since doing a malloc() may change the memory map! */
178         //ret = sys_table->boottime->get_memory_map(&size, &desc[0], &key, &desc_size, &version);
179         ret = sys_table->boottime->get_memory_map(&size, desc, &key, &desc_size, &version);
180 
181         if (ret != EFI_SUCCESS) 
182         {
183           sys_table->con_out->output_string(sys_table->con_out, L"\r\nget memory map fail\n\r");
184           return ret;
185         }
186 
187         if (sys_table && image)
188           ret = sys_table->boottime->exit_boot_services(image, key);
189         #if 1
190  if (ret) 
191         {
192   /*
193    * Unfortunately it happens that we cannot exit boot services
194    * the first time. But the second time it work. I don't know
195    * why but this seems to be a repeatable problem. To get
196    * around it, just try again.
197    */
198             print_str("11 exit_boot_services fail\r\n");
199   size = sizeof(desc);
200   ret = sys_table->boottime->get_memory_map(&size, desc, &key, &desc_size,
201         &version);
202   if (ret) {
203             print_str("22 get_memory_map fail\r\n");
204    return ret;
205   }
206   ret = sys_table->boottime->exit_boot_services(image, key);
207   if (ret) {
208             print_str("22 exit_boot_services fail\r\n");
209    return ret;
210   }
211  }
212         #endif
213         print_str("exit_boot_services ok\r\n");
214 
271   draw_rect(gop->mode->fb_base, 0x0000ff00);
272   u8 ch = 'A';
273   for (int y = 0 ; y < 30 ; ++y)
274     for (int x = 0 ; x < 80 ; ++x)
275     {
276       draw_char(gop->mode->fb_base, 0 + x*8, 0+y*16, 0x00ff0000, ch);
277       ch = ch + 1;
278       if ('Z' <  ch)
279         ch = 'A';
280     }
283   while(1);
284   return 0; // cannot return, after call exit_boot_services, cannot return to uefi environment,
285             // in qemu, it will dump fault message.
286             // so I put while(1); before return 0;
287 }
288 

既然可以畫一個點, 就可以畫任何東西, 那該怎麼畫中/英文字呢? 中文麻煩了不只一點, 但英文已經有人做了, 我從 u-boot/include/video_font.h 取得 8X16 英文字形檔, 然後畫出所有英文字母。

fig1/fig2 分別是在模擬器和真實機器上的測試畫面。

fig 1 qemu 環境, 640*480*32bit color

fig 2 在實際的 pc 上測試

ref:
  1. Ditching legacy BIOS in favor of UEFI
  2. UEFI Video after ExitBootServices()
  3. Get EFI/UEFI Memory Map
  4. set 80 x 25 text mode by UEFI
  5. Check Available Text And Graphic Modes From UEFI Shell
  6. Replacing VGA, GOP implementation for UEFI

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。