]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/splash.c
ba4a2c5da0a808f504040184368d674752851021
[thirdparty/systemd.git] / src / boot / efi / splash.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <efi.h>
4 #include <efilib.h>
5
6 #include "graphics.h"
7 #include "splash.h"
8 #include "util.h"
9
10 struct bmp_file {
11 CHAR8 signature[2];
12 UINT32 size;
13 UINT16 reserved[2];
14 UINT32 offset;
15 } __attribute__((packed));
16
17 /* we require at least BITMAPINFOHEADER, later versions are
18 accepted, but their features ignored */
19 struct bmp_dib {
20 UINT32 size;
21 UINT32 x;
22 UINT32 y;
23 UINT16 planes;
24 UINT16 depth;
25 UINT32 compression;
26 UINT32 image_size;
27 INT32 x_pixel_meter;
28 INT32 y_pixel_meter;
29 UINT32 colors_used;
30 UINT32 colors_important;
31 } __attribute__((packed));
32
33 struct bmp_map {
34 UINT8 blue;
35 UINT8 green;
36 UINT8 red;
37 UINT8 reserved;
38 } __attribute__((packed));
39
40 EFI_STATUS bmp_parse_header(UINT8 *bmp, UINTN size, struct bmp_dib **ret_dib,
41 struct bmp_map **ret_map, UINT8 **pixmap) {
42 struct bmp_file *file;
43 struct bmp_dib *dib;
44 struct bmp_map *map;
45 UINTN row_size;
46
47 if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib))
48 return EFI_INVALID_PARAMETER;
49
50 /* check file header */
51 file = (struct bmp_file *)bmp;
52 if (file->signature[0] != 'B' || file->signature[1] != 'M')
53 return EFI_INVALID_PARAMETER;
54 if (file->size != size)
55 return EFI_INVALID_PARAMETER;
56 if (file->size < file->offset)
57 return EFI_INVALID_PARAMETER;
58
59 /* check device-independent bitmap */
60 dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file));
61 if (dib->size < sizeof(struct bmp_dib))
62 return EFI_UNSUPPORTED;
63
64 switch (dib->depth) {
65 case 1:
66 case 4:
67 case 8:
68 case 24:
69 if (dib->compression != 0)
70 return EFI_UNSUPPORTED;
71
72 break;
73
74 case 16:
75 case 32:
76 if (dib->compression != 0 && dib->compression != 3)
77 return EFI_UNSUPPORTED;
78
79 break;
80
81 default:
82 return EFI_UNSUPPORTED;
83 }
84
85 row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4;
86 if (file->size - file->offset < dib->y * row_size)
87 return EFI_INVALID_PARAMETER;
88 if (row_size * dib->y > 64 * 1024 * 1024)
89 return EFI_INVALID_PARAMETER;
90
91 /* check color table */
92 map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size);
93 if (file->offset < sizeof(struct bmp_file) + dib->size)
94 return EFI_INVALID_PARAMETER;
95
96 if (file->offset > sizeof(struct bmp_file) + dib->size) {
97 UINT32 map_count;
98 UINTN map_size;
99
100 if (dib->colors_used)
101 map_count = dib->colors_used;
102 else {
103 switch (dib->depth) {
104 case 1:
105 case 4:
106 case 8:
107 map_count = 1 << dib->depth;
108 break;
109
110 default:
111 map_count = 0;
112 break;
113 }
114 }
115
116 map_size = file->offset - (sizeof(struct bmp_file) + dib->size);
117 if (map_size != sizeof(struct bmp_map) * map_count)
118 return EFI_INVALID_PARAMETER;
119 }
120
121 *ret_map = map;
122 *ret_dib = dib;
123 *pixmap = bmp + file->offset;
124
125 return EFI_SUCCESS;
126 }
127
128 static VOID pixel_blend(UINT32 *dst, const UINT32 source) {
129 UINT32 alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g;
130
131 alpha = (source & 0xff);
132
133 /* convert src from RGBA to XRGB */
134 src = source >> 8;
135
136 /* decompose into RB and G components */
137 src_rb = (src & 0xff00ff);
138 src_g = (src & 0x00ff00);
139
140 dst_rb = (*dst & 0xff00ff);
141 dst_g = (*dst & 0x00ff00);
142
143 /* blend */
144 rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff;
145 g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00;
146
147 *dst = (rb | g);
148 }
149
150 EFI_STATUS bmp_to_blt(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf,
151 struct bmp_dib *dib, struct bmp_map *map,
152 UINT8 *pixmap) {
153 UINT8 *in;
154 UINTN y;
155
156 /* transform and copy pixels */
157 in = pixmap;
158 for (y = 0; y < dib->y; y++) {
159 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out;
160 UINTN row_size;
161 UINTN x;
162
163 out = &buf[(dib->y - y - 1) * dib->x];
164 for (x = 0; x < dib->x; x++, in++, out++) {
165 switch (dib->depth) {
166 case 1: {
167 UINTN i;
168
169 for (i = 0; i < 8 && x < dib->x; i++) {
170 out->Red = map[((*in) >> (7 - i)) & 1].red;
171 out->Green = map[((*in) >> (7 - i)) & 1].green;
172 out->Blue = map[((*in) >> (7 - i)) & 1].blue;
173 out++;
174 x++;
175 }
176 out--;
177 x--;
178 break;
179 }
180
181 case 4: {
182 UINTN i;
183
184 i = (*in) >> 4;
185 out->Red = map[i].red;
186 out->Green = map[i].green;
187 out->Blue = map[i].blue;
188 if (x < (dib->x - 1)) {
189 out++;
190 x++;
191 i = (*in) & 0x0f;
192 out->Red = map[i].red;
193 out->Green = map[i].green;
194 out->Blue = map[i].blue;
195 }
196 break;
197 }
198
199 case 8:
200 out->Red = map[*in].red;
201 out->Green = map[*in].green;
202 out->Blue = map[*in].blue;
203 break;
204
205 case 16: {
206 UINT16 i = *(UINT16 *) in;
207
208 out->Red = (i & 0x7c00) >> 7;
209 out->Green = (i & 0x3e0) >> 2;
210 out->Blue = (i & 0x1f) << 3;
211 in += 1;
212 break;
213 }
214
215 case 24:
216 out->Red = in[2];
217 out->Green = in[1];
218 out->Blue = in[0];
219 in += 2;
220 break;
221
222 case 32: {
223 UINT32 i = *(UINT32 *) in;
224
225 pixel_blend((UINT32 *)out, i);
226
227 in += 3;
228 break;
229 }
230 }
231 }
232
233 /* add row padding; new lines always start at 32 bit boundary */
234 row_size = in - pixmap;
235 in += ((row_size + 3) & ~3) - row_size;
236 }
237
238 return EFI_SUCCESS;
239 }
240
241 EFI_STATUS graphics_splash(UINT8 *content, UINTN len, const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background) {
242 EFI_GRAPHICS_OUTPUT_BLT_PIXEL pixel = {};
243 EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
244 EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
245 struct bmp_dib *dib;
246 struct bmp_map *map;
247 UINT8 *pixmap;
248 UINT64 blt_size;
249 _cleanup_freepool_ VOID *blt = NULL;
250 UINTN x_pos = 0;
251 UINTN y_pos = 0;
252 EFI_STATUS err;
253
254 if (!background) {
255 if (StriCmp(L"Apple", ST->FirmwareVendor) == 0) {
256 pixel.Red = 0xc0;
257 pixel.Green = 0xc0;
258 pixel.Blue = 0xc0;
259 }
260 background = &pixel;
261 }
262
263 err = LibLocateProtocol(&GraphicsOutputProtocolGuid, (VOID **)&GraphicsOutput);
264 if (EFI_ERROR(err))
265 return err;
266
267 err = bmp_parse_header(content, len, &dib, &map, &pixmap);
268 if (EFI_ERROR(err))
269 return err;
270
271 if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
272 x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
273 if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
274 y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
275
276 uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
277 (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)background,
278 EfiBltVideoFill, 0, 0, 0, 0,
279 GraphicsOutput->Mode->Info->HorizontalResolution,
280 GraphicsOutput->Mode->Info->VerticalResolution, 0);
281
282 /* EFI buffer */
283 blt_size = sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * dib->x * dib->y;
284 blt = AllocatePool(blt_size);
285 if (!blt)
286 return EFI_OUT_OF_RESOURCES;
287
288 err = uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
289 blt, EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
290 dib->x, dib->y, 0);
291 if (EFI_ERROR(err))
292 return err;
293
294 err = bmp_to_blt(blt, dib, map, pixmap);
295 if (EFI_ERROR(err))
296 return err;
297
298 err = graphics_mode(TRUE);
299 if (EFI_ERROR(err))
300 return err;
301
302 return uefi_call_wrapper(GraphicsOutput->Blt, 10, GraphicsOutput,
303 blt, EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
304 dib->x, dib->y, 0);
305 }