]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sd-boot/graphics.c
sd-boot: add EFI boot manager and stub loader
[thirdparty/systemd.git] / src / sd-boot / graphics.c
CommitLineData
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
26EFI_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
93struct 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 */
102struct 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
116struct bmp_map {
117 UINT8 blue;
118 UINT8 green;
119 UINT8 red;
120 UINT8 reserved;
121} __attribute__((packed));
122
123EFI_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
211static 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
233EFI_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
324EFI_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);
385err:
386 FreePool(blt);
387 FreePool(content);
388 return err;
389}