chiark / gitweb /
boot: efi - support embedded splash image
[elogind.git] / src / boot / efi / graphics.c
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, &current, &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(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) {
325         EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {};
326         EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
327         EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
328         struct bmp_dib *dib;
329         struct bmp_map *map;
330         UINT8 *pixmap;
331         UINT64 blt_size;
332         VOID *blt = NULL;
333         UINTN x_pos = 0;
334         UINTN y_pos = 0;
335         EFI_STATUS err;
336
337         if (!background) {
338                 if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) {
339                         pixel.Red = 0xc0;
340                         pixel.Green = 0xc0;
341                         pixel.Blue = 0xc0;
342                 }
343                 background = &pixel;
344         }
345
346         err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput);
347         if (EFI_ERROR(err))
348                 return err;
349
350         err = bmp_parse_header(content, len, &dib, &map, &pixmap);
351         if (EFI_ERROR(err))
352                 goto err;
353
354         if(dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
355                 x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
356         if(dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
357                 y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
358
359         uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
360                           (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background,
361                           EfiBltVideoFill, 0, 0, 0, 0,
362                           GraphicsOutput->Mode->Info->HorizontalResolution,
363                           GraphicsOutput->Mode->Info->VerticalResolution, 0);
364
365         /* EFI buffer */
366         blt_size = dib->x * dib->y * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL);
367         blt = AllocatePool(blt_size);
368         if (!blt)
369                 return EFI_OUT_OF_RESOURCES;
370
371         err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
372                                 blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
373                                 dib->x, dib->y, 0);
374         if (EFI_ERROR(err))
375                 goto err;
376
377         err = bmp_to_blt(blt, dib, map, pixmap);
378         if (EFI_ERROR(err))
379                 goto err;
380
381         err = graphics_mode(TRUE);
382         if (EFI_ERROR(err))
383                 goto err;
384
385         err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
386                                 blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
387                                 dib->x, dib->y, 0);
388 err:
389         FreePool(blt);
390         return err;
391 }