]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/splash.c
Update version and finalize NEWS for 256~rc4
[thirdparty/systemd.git] / src / boot / efi / splash.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "graphics.h"
4 #include "logarithm.h"
5 #include "proto/graphics-output.h"
6 #include "splash.h"
7 #include "unaligned-fundamental.h"
8 #include "util.h"
9
10 struct bmp_file {
11 char signature[2];
12 uint32_t size;
13 uint16_t reserved[2];
14 uint32_t offset;
15 } _packed_;
16
17 /* we require at least BITMAPINFOHEADER, later versions are
18 accepted, but their features ignored */
19 struct bmp_dib {
20 uint32_t size;
21 uint32_t x;
22 uint32_t y;
23 uint16_t planes;
24 uint16_t depth;
25 uint32_t compression;
26 uint32_t image_size;
27 int32_t x_pixel_meter;
28 int32_t y_pixel_meter;
29 uint32_t colors_used;
30 uint32_t colors_important;
31 uint32_t channel_mask_r;
32 uint32_t channel_mask_g;
33 uint32_t channel_mask_b;
34 uint32_t channel_mask_a;
35 } _packed_;
36
37 #define SIZEOF_BMP_DIB offsetof(struct bmp_dib, channel_mask_r)
38 #define SIZEOF_BMP_DIB_RGB offsetof(struct bmp_dib, channel_mask_a)
39 #define SIZEOF_BMP_DIB_RGBA sizeof(struct bmp_dib)
40
41 struct bmp_map {
42 uint8_t blue;
43 uint8_t green;
44 uint8_t red;
45 uint8_t reserved;
46 } _packed_;
47
48 static EFI_STATUS bmp_parse_header(
49 const uint8_t *bmp,
50 size_t size,
51 struct bmp_dib **ret_dib,
52 struct bmp_map **ret_map,
53 const uint8_t **pixmap) {
54
55 assert(bmp);
56 assert(ret_dib);
57 assert(ret_map);
58 assert(pixmap);
59
60 if (size < sizeof(struct bmp_file) + SIZEOF_BMP_DIB)
61 return EFI_INVALID_PARAMETER;
62
63 /* check file header */
64 struct bmp_file *file = (struct bmp_file *) bmp;
65 if (file->signature[0] != 'B' || file->signature[1] != 'M')
66 return EFI_INVALID_PARAMETER;
67 if (file->size != size)
68 return EFI_INVALID_PARAMETER;
69 if (file->size < file->offset)
70 return EFI_INVALID_PARAMETER;
71
72 /* check device-independent bitmap */
73 struct bmp_dib *dib = (struct bmp_dib *) (bmp + sizeof(struct bmp_file));
74 if (dib->size < SIZEOF_BMP_DIB)
75 return EFI_UNSUPPORTED;
76
77 switch (dib->depth) {
78 case 1:
79 case 4:
80 case 8:
81 case 24:
82 if (dib->compression != 0)
83 return EFI_UNSUPPORTED;
84
85 break;
86
87 case 16:
88 case 32:
89 if (dib->compression != 0 && dib->compression != 3)
90 return EFI_UNSUPPORTED;
91
92 break;
93
94 default:
95 return EFI_UNSUPPORTED;
96 }
97
98 size_t row_size = ((size_t) dib->depth * dib->x + 31) / 32 * 4;
99 if (file->size - file->offset < dib->y * row_size)
100 return EFI_INVALID_PARAMETER;
101 if (row_size * dib->y > 64 * 1024 * 1024)
102 return EFI_INVALID_PARAMETER;
103
104 /* check color table */
105 struct bmp_map *map = (struct bmp_map *) (bmp + sizeof(struct bmp_file) + dib->size);
106 if (file->offset < sizeof(struct bmp_file) + dib->size)
107 return EFI_INVALID_PARAMETER;
108
109 if (file->offset > sizeof(struct bmp_file) + dib->size) {
110 uint32_t map_count = 0;
111
112 if (dib->colors_used)
113 map_count = dib->colors_used;
114 else if (IN_SET(dib->depth, 1, 4, 8))
115 map_count = 1 << dib->depth;
116
117 size_t map_size = file->offset - (sizeof(struct bmp_file) + dib->size);
118 if (map_size != sizeof(struct bmp_map) * map_count)
119 return EFI_INVALID_PARAMETER;
120 }
121
122 *ret_map = map;
123 *ret_dib = dib;
124 *pixmap = bmp + file->offset;
125
126 return EFI_SUCCESS;
127 }
128
129 enum Channels { R, G, B, A, _CHANNELS_MAX };
130 static void read_channel_maks(
131 const struct bmp_dib *dib,
132 uint32_t channel_mask[static _CHANNELS_MAX],
133 uint8_t channel_shift[static _CHANNELS_MAX],
134 uint8_t channel_scale[static _CHANNELS_MAX]) {
135
136 assert(dib);
137
138 if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) {
139 channel_mask[R] = dib->channel_mask_r;
140 channel_mask[G] = dib->channel_mask_g;
141 channel_mask[B] = dib->channel_mask_b;
142 channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
143 channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
144 channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
145 channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
146 channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
147 channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
148
149 if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
150 channel_mask[A] = dib->channel_mask_a;
151 channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
152 channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
153 } else {
154 channel_mask[A] = 0;
155 channel_shift[A] = 0;
156 channel_scale[A] = 0;
157 }
158 } else {
159 bool bpp16 = dib->depth == 16;
160 channel_mask[R] = bpp16 ? 0x7C00 : 0xFF0000;
161 channel_mask[G] = bpp16 ? 0x03E0 : 0x00FF00;
162 channel_mask[B] = bpp16 ? 0x001F : 0x0000FF;
163 channel_mask[A] = bpp16 ? 0x0000 : 0x000000;
164 channel_shift[R] = bpp16 ? 0xA : 0x10;
165 channel_shift[G] = bpp16 ? 0x5 : 0x08;
166 channel_shift[B] = bpp16 ? 0x0 : 0x00;
167 channel_shift[A] = bpp16 ? 0x0 : 0x00;
168 channel_scale[R] = bpp16 ? 0x08 : 0x1;
169 channel_scale[G] = bpp16 ? 0x08 : 0x1;
170 channel_scale[B] = bpp16 ? 0x08 : 0x1;
171 channel_scale[A] = bpp16 ? 0x00 : 0x0;
172 }
173 }
174
175 static EFI_STATUS bmp_to_blt(
176 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf,
177 struct bmp_dib *dib,
178 struct bmp_map *map,
179 const uint8_t *pixmap) {
180
181 const uint8_t *in;
182
183 assert(buf);
184 assert(dib);
185 assert(map);
186 assert(pixmap);
187
188 uint32_t channel_mask[_CHANNELS_MAX];
189 uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX];
190 read_channel_maks(dib, channel_mask, channel_shift, channel_scale);
191
192 /* transform and copy pixels */
193 in = pixmap;
194 for (uint32_t y = 0; y < dib->y; y++) {
195 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out = &buf[(dib->y - y - 1) * dib->x];
196
197 for (uint32_t x = 0; x < dib->x; x++, in++, out++) {
198 switch (dib->depth) {
199 case 1: {
200 for (unsigned i = 0; i < 8 && x < dib->x; i++) {
201 out->Red = map[((*in) >> (7 - i)) & 1].red;
202 out->Green = map[((*in) >> (7 - i)) & 1].green;
203 out->Blue = map[((*in) >> (7 - i)) & 1].blue;
204 out++;
205 x++;
206 }
207 out--;
208 x--;
209 break;
210 }
211
212 case 4: {
213 unsigned i = (*in) >> 4;
214 out->Red = map[i].red;
215 out->Green = map[i].green;
216 out->Blue = map[i].blue;
217 if (x < (dib->x - 1)) {
218 out++;
219 x++;
220 i = (*in) & 0x0f;
221 out->Red = map[i].red;
222 out->Green = map[i].green;
223 out->Blue = map[i].blue;
224 }
225 break;
226 }
227
228 case 8:
229 out->Red = map[*in].red;
230 out->Green = map[*in].green;
231 out->Blue = map[*in].blue;
232 break;
233
234 case 24:
235 out->Red = in[2];
236 out->Green = in[1];
237 out->Blue = in[0];
238 in += 2;
239 break;
240
241 case 16:
242 case 32: {
243 uint32_t i = dib->depth == 16 ? unaligned_read_ne16(in) :
244 unaligned_read_ne32(in);
245
246 uint8_t r = ((i & channel_mask[R]) >> channel_shift[R]) * channel_scale[R],
247 g = ((i & channel_mask[G]) >> channel_shift[G]) * channel_scale[G],
248 b = ((i & channel_mask[B]) >> channel_shift[B]) * channel_scale[B],
249 a = 0xFFu;
250 if (channel_mask[A] != 0)
251 a = ((i & channel_mask[A]) >> channel_shift[A]) * channel_scale[A];
252
253 out->Red = (out->Red * (0xFFu - a) + r * a) >> 8;
254 out->Green = (out->Green * (0xFFu - a) + g * a) >> 8;
255 out->Blue = (out->Blue * (0xFFu - a) + b * a) >> 8;
256
257 in += dib->depth == 16 ? 1 : 3;
258 break;
259 }
260 }
261 }
262
263 /* add row padding; new lines always start at 32 bit boundary */
264 size_t row_size = in - pixmap;
265 in += ((row_size + 3) & ~3) - row_size;
266 }
267
268 return EFI_SUCCESS;
269 }
270
271 EFI_STATUS graphics_splash(const uint8_t *content, size_t len) {
272 EFI_GRAPHICS_OUTPUT_BLT_PIXEL background = {};
273 EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
274 struct bmp_dib *dib;
275 struct bmp_map *map;
276 const uint8_t *pixmap;
277 size_t x_pos = 0, y_pos = 0;
278 EFI_STATUS err;
279
280 if (len == 0)
281 return EFI_SUCCESS;
282
283 assert(content);
284
285 if (strcaseeq16(ST->FirmwareVendor, u"Apple")) {
286 background.Red = 0xc0;
287 background.Green = 0xc0;
288 background.Blue = 0xc0;
289 }
290
291 err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), NULL, (void **) &GraphicsOutput);
292 if (err != EFI_SUCCESS)
293 return err;
294
295 err = bmp_parse_header(content, len, &dib, &map, &pixmap);
296 if (err != EFI_SUCCESS)
297 return err;
298
299 if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
300 x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
301 if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
302 y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
303
304 err = GraphicsOutput->Blt(
305 GraphicsOutput, &background,
306 EfiBltVideoFill, 0, 0, 0, 0,
307 GraphicsOutput->Mode->Info->HorizontalResolution,
308 GraphicsOutput->Mode->Info->VerticalResolution, 0);
309 if (err != EFI_SUCCESS)
310 return err;
311
312 /* Read in current screen content to perform proper alpha blending. */
313 _cleanup_free_ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *blt = xnew(
314 EFI_GRAPHICS_OUTPUT_BLT_PIXEL, dib->x * dib->y);
315 err = GraphicsOutput->Blt(
316 GraphicsOutput, blt,
317 EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
318 dib->x, dib->y, 0);
319 if (err != EFI_SUCCESS)
320 return err;
321
322 err = bmp_to_blt(blt, dib, map, pixmap);
323 if (err != EFI_SUCCESS)
324 return err;
325
326 err = graphics_mode(true);
327 if (err != EFI_SUCCESS)
328 return err;
329
330 return GraphicsOutput->Blt(
331 GraphicsOutput, blt,
332 EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
333 dib->x, dib->y, 0);
334 }