]>
Commit | Line | Data |
---|---|---|
0fa2cac4 KS |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /* | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU Lesser General Public License as published by | |
6 | * the Free Software Foundation; either version 2.1 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * Lesser General Public License for more details. | |
13 | * | |
14 | * Copyright (C) 2012-2013 Kay Sievers <kay@vrfy.org> | |
15 | * Copyright (C) 2012 Harald Hoyer <harald@redhat.com> | |
16 | * Copyright (C) 2013 Intel Corporation | |
17 | * Authored by Joonas Lahtinen <joonas.lahtinen@linux.intel.com> | |
18 | */ | |
19 | ||
20 | #include <efi.h> | |
21 | #include <efilib.h> | |
22 | ||
23 | #include "util.h" | |
24 | #include "graphics.h" | |
25 | ||
26 | EFI_STATUS graphics_mode(BOOLEAN on) { | |
27 | #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \ | |
28 | { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 } }; | |
29 | ||
30 | struct _EFI_CONSOLE_CONTROL_PROTOCOL; | |
31 | ||
32 | typedef enum { | |
33 | EfiConsoleControlScreenText, | |
34 | EfiConsoleControlScreenGraphics, | |
35 | EfiConsoleControlScreenMaxValue, | |
36 | } EFI_CONSOLE_CONTROL_SCREEN_MODE; | |
37 | ||
38 | typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)( | |
39 | struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, | |
40 | EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode, | |
41 | BOOLEAN *UgaExists, | |
42 | BOOLEAN *StdInLocked | |
43 | ); | |
44 | ||
45 | typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)( | |
46 | struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, | |
47 | EFI_CONSOLE_CONTROL_SCREEN_MODE Mode | |
48 | ); | |
49 | ||
50 | typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)( | |
51 | struct _EFI_CONSOLE_CONTROL_PROTOCOL *This, | |
52 | CHAR16 *Password | |
53 | ); | |
54 | ||
55 | typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL { | |
56 | EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode; | |
57 | EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode; | |
58 | EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn; | |
59 | } EFI_CONSOLE_CONTROL_PROTOCOL; | |
60 | ||
61 | EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID; | |
62 | EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL; | |
63 | EFI_CONSOLE_CONTROL_SCREEN_MODE new; | |
64 | EFI_CONSOLE_CONTROL_SCREEN_MODE current; | |
65 | BOOLEAN uga_exists; | |
66 | BOOLEAN stdin_locked; | |
67 | EFI_STATUS err; | |
68 | ||
69 | err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl); | |
70 | if (EFI_ERROR(err)) { | |
71 | /* console control protocol is nonstandard and might not exist. */ | |
72 | return err == EFI_NOT_FOUND ? EFI_SUCCESS : err; | |
73 | } | |
74 | ||
75 | /* check current mode */ | |
76 | err = uefi_call_wrapper(ConsoleControl->GetMode, 4, ConsoleControl, ¤t, &uga_exists, &stdin_locked); | |
77 | if (EFI_ERROR(err)) | |
78 | return err; | |
79 | ||
80 | /* do not touch the mode */ | |
81 | new = on ? EfiConsoleControlScreenGraphics : EfiConsoleControlScreenText; | |
82 | if (new == current) | |
83 | return EFI_SUCCESS; | |
84 | ||
85 | err = uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, new); | |
86 | ||
87 | /* some firmware enables the cursor when switching modes */ | |
88 | uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE); | |
89 | ||
90 | return err; | |
91 | } | |
92 | ||
93 | struct bmp_file { | |
94 | CHAR8 signature[2]; | |
95 | UINT32 size; | |
96 | UINT16 reserved[2]; | |
97 | UINT32 offset; | |
98 | } __attribute__((packed)); | |
99 | ||
100 | /* we require at least BITMAPINFOHEADER, later versions are | |
101 | accepted, but their features ignored */ | |
102 | struct bmp_dib { | |
103 | UINT32 size; | |
104 | UINT32 x; | |
105 | UINT32 y; | |
106 | UINT16 planes; | |
107 | UINT16 depth; | |
108 | UINT32 compression; | |
109 | UINT32 image_size; | |
110 | INT32 x_pixel_meter; | |
111 | INT32 y_pixel_meter; | |
112 | UINT32 colors_used; | |
113 | UINT32 colors_important; | |
114 | } __attribute__((packed)); | |
115 | ||
116 | struct bmp_map { | |
117 | UINT8 blue; | |
118 | UINT8 green; | |
119 | UINT8 red; | |
120 | UINT8 reserved; | |
121 | } __attribute__((packed)); | |
122 | ||
123 | EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib, | |
124 | struct bmp_map **ret_map, UINT8 **pixmap) { | |
125 | struct bmp_file *file; | |
126 | struct bmp_dib *dib; | |
127 | struct bmp_map *map; | |
128 | UINTN row_size; | |
129 | ||
130 | if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib)) | |
131 | return EFI_INVALID_PARAMETER; | |
132 | ||
133 | /* check file header */ | |
134 | file = (struct bmp_file *)bmp; | |
135 | if (file->signature[0] != 'B' || file->signature[1] != 'M') | |
136 | return EFI_INVALID_PARAMETER; | |
137 | if (file->size != size) | |
138 | return EFI_INVALID_PARAMETER; | |
139 | if (file->size < file->offset) | |
140 | return EFI_INVALID_PARAMETER; | |
141 | ||
142 | /* check device-independent bitmap */ | |
143 | dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file)); | |
144 | if (dib->size < sizeof(struct bmp_dib)) | |
145 | return EFI_UNSUPPORTED; | |
146 | ||
147 | switch (dib->depth) { | |
148 | case 1: | |
149 | case 4: | |
150 | case 8: | |
151 | case 24: | |
152 | if (dib->compression != 0) | |
153 | return EFI_UNSUPPORTED; | |
154 | ||
155 | break; | |
156 | ||
157 | case 16: | |
158 | case 32: | |
159 | if (dib->compression != 0 && dib->compression != 3) | |
160 | return EFI_UNSUPPORTED; | |
161 | ||
162 | break; | |
163 | ||
164 | default: | |
165 | return EFI_UNSUPPORTED; | |
166 | } | |
167 | ||
168 | row_size = (((dib->depth * dib->x) + 31) / 32) * 4; | |
169 | if (file->size - file->offset < dib->y * row_size) | |
170 | return EFI_INVALID_PARAMETER; | |
171 | if (row_size * dib->y > 64 * 1024 * 1024) | |
172 | return EFI_INVALID_PARAMETER; | |
173 | ||
174 | /* check color table */ | |
175 | map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size); | |
176 | if (file->offset < sizeof(struct bmp_file) + dib->size) | |
177 | return EFI_INVALID_PARAMETER; | |
178 | ||
179 | if (file->offset > sizeof(struct bmp_file) + dib->size) { | |
180 | UINT32 map_count; | |
181 | UINTN map_size; | |
182 | ||
183 | if (dib->colors_used) | |
184 | map_count = dib->colors_used; | |
185 | else { | |
186 | switch (dib->depth) { | |
187 | case 1: | |
188 | case 4: | |
189 | case 8: | |
190 | map_count = 1 << dib->depth; | |
191 | break; | |
192 | ||
193 | default: | |
194 | map_count = 0; | |
195 | break; | |
196 | } | |
197 | } | |
198 | ||
199 | map_size = file->offset - (sizeof(struct bmp_file) + dib->size); | |
200 | if (map_size != sizeof(struct bmp_map) * map_count) | |
201 | return EFI_INVALID_PARAMETER; | |
202 | } | |
203 | ||
204 | *ret_map = map; | |
205 | *ret_dib = dib; | |
206 | *pixmap = bmp + file->offset; | |
207 | ||
208 | return EFI_SUCCESS; | |
209 | } | |
210 | ||
211 | static VOID pixel_blend(UINT32 *dst, const UINT32 source) { | |
212 | UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g; | |
213 | ||
214 | alpha = (source & 0xff); | |
215 | ||
216 | /* convert src from RGBA to XRGB */ | |
217 | src = source >> 8; | |
218 | ||
219 | /* decompose into RB and G components */ | |
220 | src_rb = (src & 0xff00ff); | |
221 | src_g = (src & 0x00ff00); | |
222 | ||
223 | dst_rb = (*dst & 0xff00ff); | |
224 | dst_g = (*dst & 0x00ff00); | |
225 | ||
226 | /* blend */ | |
227 | rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff; | |
228 | g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00; | |
229 | ||
230 | *dst = (rb | g); | |
231 | } | |
232 | ||
233 | EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf, | |
234 | struct bmp_dib *dib, struct bmp_map *map, | |
235 | UINT8 *pixmap) { | |
236 | UINT8 *in; | |
237 | UINTN y; | |
238 | ||
239 | /* transform and copy pixels */ | |
240 | in = pixmap; | |
241 | for (y = 0; y < dib->y; y++) { | |
242 | EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out; | |
243 | UINTN row_size; | |
244 | UINTN x; | |
245 | ||
246 | out = &buf[(dib->y - y - 1) * dib->x]; | |
247 | for (x = 0; x < dib->x; x++, in++, out++) { | |
248 | switch (dib->depth) { | |
249 | case 1: { | |
250 | UINTN i; | |
251 | ||
252 | for (i = 0; i < 8 && x < dib->x; i++) { | |
253 | out->Red = map[((*in) >> (7 - i)) & 1].red; | |
254 | out->Green = map[((*in) >> (7 - i)) & 1].green; | |
255 | out->Blue = map[((*in) >> (7 - i)) & 1].blue; | |
256 | out++; | |
257 | x++; | |
258 | } | |
259 | out--; | |
260 | x--; | |
261 | break; | |
262 | } | |
263 | ||
264 | case 4: { | |
265 | UINTN i; | |
266 | ||
267 | i = (*in) >> 4; | |
268 | out->Red = map[i].red; | |
269 | out->Green = map[i].green; | |
270 | out->Blue = map[i].blue; | |
271 | if (x < (dib->x - 1)) { | |
272 | out++; | |
273 | x++; | |
274 | i = (*in) & 0x0f; | |
275 | out->Red = map[i].red; | |
276 | out->Green = map[i].green; | |
277 | out->Blue = map[i].blue; | |
278 | } | |
279 | break; | |
280 | } | |
281 | ||
282 | case 8: | |
283 | out->Red = map[*in].red; | |
284 | out->Green = map[*in].green; | |
285 | out->Blue = map[*in].blue; | |
286 | break; | |
287 | ||
288 | case 16: { | |
289 | UINT16 i = *(UINT16 *) in; | |
290 | ||
291 | out->Red = (i & 0x7c00) >> 7; | |
292 | out->Green = (i & 0x3e0) >> 2; | |
293 | out->Blue = (i & 0x1f) << 3; | |
294 | in += 1; | |
295 | break; | |
296 | } | |
297 | ||
298 | case 24: | |
299 | out->Red = in[2]; | |
300 | out->Green = in[1]; | |
301 | out->Blue = in[0]; | |
302 | in += 2; | |
303 | break; | |
304 | ||
305 | case 32: { | |
306 | UINT32 i = *(UINT32 *) in; | |
307 | ||
308 | pixel_blend((UINT32 *)out, i); | |
309 | ||
310 | in += 3; | |
311 | break; | |
312 | } | |
313 | } | |
314 | } | |
315 | ||
316 | /* add row padding; new lines always start at 32 bit boundary */ | |
317 | row_size = in - pixmap; | |
318 | in += ((row_size + 3) & ~3) - row_size; | |
319 | } | |
320 | ||
321 | return EFI_SUCCESS; | |
322 | } | |
323 | ||
324 | EFI_STATUS graphics_splash(EFI_FILE *root_dir, CHAR16 *path, | |
325 | const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) { | |
326 | EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; | |
327 | EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL; | |
328 | UINT8 *content; | |
329 | INTN len; | |
330 | struct bmp_dib *dib; | |
331 | struct bmp_map *map; | |
332 | UINT8 *pixmap; | |
333 | UINT64 blt_size; | |
334 | VOID *blt = NULL; | |
335 | UINTN x_pos = 0; | |
336 | UINTN y_pos = 0; | |
337 | EFI_STATUS err; | |
338 | ||
339 | err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput); | |
340 | if (EFI_ERROR(err)) | |
341 | return err; | |
342 | ||
343 | len = file_read(root_dir, path, 0, 0, &content); | |
344 | if (len < 0) | |
345 | return EFI_LOAD_ERROR; | |
346 | ||
347 | err = bmp_parse_header(content, len, &dib, &map, &pixmap); | |
348 | if (EFI_ERROR(err)) | |
349 | goto err; | |
350 | ||
351 | if(dib->x < GraphicsOutput->Mode->Info->HorizontalResolution) | |
352 | x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2; | |
353 | if(dib->y < GraphicsOutput->Mode->Info->VerticalResolution) | |
354 | y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2; | |
355 | ||
356 | uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, | |
357 | (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background, | |
358 | EfiBltVideoFill, 0, 0, 0, 0, | |
359 | GraphicsOutput->Mode->Info->HorizontalResolution, | |
360 | GraphicsOutput->Mode->Info->VerticalResolution, 0); | |
361 | ||
362 | /* EFI buffer */ | |
363 | blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL); | |
364 | blt = AllocatePool(blt_size); | |
365 | if (!blt) | |
366 | return EFI_OUT_OF_RESOURCES; | |
367 | ||
368 | err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, | |
369 | blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0, | |
370 | dib->x, dib->y, 0); | |
371 | if (EFI_ERROR(err)) | |
372 | goto err; | |
373 | ||
374 | err = bmp_to_blt(blt, dib, map, pixmap); | |
375 | if (EFI_ERROR(err)) | |
376 | goto err; | |
377 | ||
378 | err = graphics_mode(TRUE); | |
379 | if (EFI_ERROR(err)) | |
380 | goto err; | |
381 | ||
382 | err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput, | |
383 | blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos, | |
384 | dib->x, dib->y, 0); | |
385 | err: | |
386 | FreePool(blt); | |
387 | FreePool(content); | |
388 | return err; | |
389 | } |