summaryrefslogtreecommitdiffstats
path: root/src/boot/efi/splash.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/boot/efi/splash.c')
-rw-r--r--src/boot/efi/splash.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/src/boot/efi/splash.c b/src/boot/efi/splash.c
new file mode 100644
index 0000000..5bc1084
--- /dev/null
+++ b/src/boot/efi/splash.c
@@ -0,0 +1,322 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <efi.h>
+#include <efilib.h>
+
+#include "graphics.h"
+#include "splash.h"
+#include "util.h"
+
+struct bmp_file {
+ char signature[2];
+ uint32_t size;
+ uint16_t reserved[2];
+ uint32_t offset;
+} _packed_;
+
+/* we require at least BITMAPINFOHEADER, later versions are
+ accepted, but their features ignored */
+struct bmp_dib {
+ uint32_t size;
+ uint32_t x;
+ uint32_t y;
+ uint16_t planes;
+ uint16_t depth;
+ uint32_t compression;
+ uint32_t image_size;
+ int32_t x_pixel_meter;
+ int32_t y_pixel_meter;
+ uint32_t colors_used;
+ uint32_t colors_important;
+} _packed_;
+
+struct bmp_map {
+ uint8_t blue;
+ uint8_t green;
+ uint8_t red;
+ uint8_t reserved;
+} _packed_;
+
+static EFI_STATUS bmp_parse_header(
+ const uint8_t *bmp,
+ UINTN size,
+ struct bmp_dib **ret_dib,
+ struct bmp_map **ret_map,
+ const uint8_t **pixmap) {
+
+ struct bmp_file *file;
+ struct bmp_dib *dib;
+ struct bmp_map *map;
+ UINTN row_size;
+
+ assert(bmp);
+ assert(ret_dib);
+ assert(ret_map);
+ assert(pixmap);
+
+ if (size < sizeof(struct bmp_file) + sizeof(struct bmp_dib))
+ return EFI_INVALID_PARAMETER;
+
+ /* check file header */
+ file = (struct bmp_file *)bmp;
+ if (file->signature[0] != 'B' || file->signature[1] != 'M')
+ return EFI_INVALID_PARAMETER;
+ if (file->size != size)
+ return EFI_INVALID_PARAMETER;
+ if (file->size < file->offset)
+ return EFI_INVALID_PARAMETER;
+
+ /* check device-independent bitmap */
+ dib = (struct bmp_dib *)(bmp + sizeof(struct bmp_file));
+ if (dib->size < sizeof(struct bmp_dib))
+ return EFI_UNSUPPORTED;
+
+ switch (dib->depth) {
+ case 1:
+ case 4:
+ case 8:
+ case 24:
+ if (dib->compression != 0)
+ return EFI_UNSUPPORTED;
+
+ break;
+
+ case 16:
+ case 32:
+ if (dib->compression != 0 && dib->compression != 3)
+ return EFI_UNSUPPORTED;
+
+ break;
+
+ default:
+ return EFI_UNSUPPORTED;
+ }
+
+ row_size = ((UINTN) dib->depth * dib->x + 31) / 32 * 4;
+ if (file->size - file->offset < dib->y * row_size)
+ return EFI_INVALID_PARAMETER;
+ if (row_size * dib->y > 64 * 1024 * 1024)
+ return EFI_INVALID_PARAMETER;
+
+ /* check color table */
+ map = (struct bmp_map *)(bmp + sizeof(struct bmp_file) + dib->size);
+ if (file->offset < sizeof(struct bmp_file) + dib->size)
+ return EFI_INVALID_PARAMETER;
+
+ if (file->offset > sizeof(struct bmp_file) + dib->size) {
+ uint32_t map_count;
+ UINTN map_size;
+
+ if (dib->colors_used)
+ map_count = dib->colors_used;
+ else {
+ switch (dib->depth) {
+ case 1:
+ case 4:
+ case 8:
+ map_count = 1 << dib->depth;
+ break;
+
+ default:
+ map_count = 0;
+ break;
+ }
+ }
+
+ map_size = file->offset - (sizeof(struct bmp_file) + dib->size);
+ if (map_size != sizeof(struct bmp_map) * map_count)
+ return EFI_INVALID_PARAMETER;
+ }
+
+ *ret_map = map;
+ *ret_dib = dib;
+ *pixmap = bmp + file->offset;
+
+ return EFI_SUCCESS;
+}
+
+static void pixel_blend(uint32_t *dst, const uint32_t source) {
+ uint32_t alpha, src, src_rb, src_g, dst_rb, dst_g, rb, g;
+
+ assert(dst);
+
+ alpha = (source & 0xff);
+
+ /* convert src from RGBA to XRGB */
+ src = source >> 8;
+
+ /* decompose into RB and G components */
+ src_rb = (src & 0xff00ff);
+ src_g = (src & 0x00ff00);
+
+ dst_rb = (*dst & 0xff00ff);
+ dst_g = (*dst & 0x00ff00);
+
+ /* blend */
+ rb = ((((src_rb - dst_rb) * alpha + 0x800080) >> 8) + dst_rb) & 0xff00ff;
+ g = ((((src_g - dst_g) * alpha + 0x008000) >> 8) + dst_g) & 0x00ff00;
+
+ *dst = (rb | g);
+}
+
+static EFI_STATUS bmp_to_blt(
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf,
+ struct bmp_dib *dib,
+ struct bmp_map *map,
+ const uint8_t *pixmap) {
+
+ const uint8_t *in;
+
+ assert(buf);
+ assert(dib);
+ assert(map);
+ assert(pixmap);
+
+ /* transform and copy pixels */
+ in = pixmap;
+ for (UINTN y = 0; y < dib->y; y++) {
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out;
+ UINTN row_size;
+
+ out = &buf[(dib->y - y - 1) * dib->x];
+ for (UINTN x = 0; x < dib->x; x++, in++, out++) {
+ switch (dib->depth) {
+ case 1: {
+ for (UINTN i = 0; i < 8 && x < dib->x; i++) {
+ out->Red = map[((*in) >> (7 - i)) & 1].red;
+ out->Green = map[((*in) >> (7 - i)) & 1].green;
+ out->Blue = map[((*in) >> (7 - i)) & 1].blue;
+ out++;
+ x++;
+ }
+ out--;
+ x--;
+ break;
+ }
+
+ case 4: {
+ UINTN i;
+
+ i = (*in) >> 4;
+ out->Red = map[i].red;
+ out->Green = map[i].green;
+ out->Blue = map[i].blue;
+ if (x < (dib->x - 1)) {
+ out++;
+ x++;
+ i = (*in) & 0x0f;
+ out->Red = map[i].red;
+ out->Green = map[i].green;
+ out->Blue = map[i].blue;
+ }
+ break;
+ }
+
+ case 8:
+ out->Red = map[*in].red;
+ out->Green = map[*in].green;
+ out->Blue = map[*in].blue;
+ break;
+
+ case 16: {
+ uint16_t i = *(uint16_t *) in;
+
+ out->Red = (i & 0x7c00) >> 7;
+ out->Green = (i & 0x3e0) >> 2;
+ out->Blue = (i & 0x1f) << 3;
+ in += 1;
+ break;
+ }
+
+ case 24:
+ out->Red = in[2];
+ out->Green = in[1];
+ out->Blue = in[0];
+ in += 2;
+ break;
+
+ case 32: {
+ uint32_t i = *(uint32_t *) in;
+
+ pixel_blend((uint32_t *)out, i);
+
+ in += 3;
+ break;
+ }
+ }
+ }
+
+ /* add row padding; new lines always start at 32 bit boundary */
+ row_size = in - pixmap;
+ in += ((row_size + 3) & ~3) - row_size;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS graphics_splash(const uint8_t *content, UINTN len) {
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL background = {};
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
+ struct bmp_dib *dib;
+ struct bmp_map *map;
+ const uint8_t *pixmap;
+ _cleanup_free_ void *blt = NULL;
+ UINTN x_pos = 0;
+ UINTN y_pos = 0;
+ EFI_STATUS err;
+
+ if (len == 0)
+ return EFI_SUCCESS;
+
+ assert(content);
+
+ if (strcaseeq16(ST->FirmwareVendor, u"Apple")) {
+ background.Red = 0xc0;
+ background.Green = 0xc0;
+ background.Blue = 0xc0;
+ }
+
+ err = BS->LocateProtocol(&GraphicsOutputProtocol, NULL, (void **) &GraphicsOutput);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = bmp_parse_header(content, len, &dib, &map, &pixmap);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ if (dib->x < GraphicsOutput->Mode->Info->HorizontalResolution)
+ x_pos = (GraphicsOutput->Mode->Info->HorizontalResolution - dib->x) / 2;
+ if (dib->y < GraphicsOutput->Mode->Info->VerticalResolution)
+ y_pos = (GraphicsOutput->Mode->Info->VerticalResolution - dib->y) / 2;
+
+ err = GraphicsOutput->Blt(
+ GraphicsOutput, &background,
+ EfiBltVideoFill, 0, 0, 0, 0,
+ GraphicsOutput->Mode->Info->HorizontalResolution,
+ GraphicsOutput->Mode->Info->VerticalResolution, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ /* EFI buffer */
+ blt = xnew(EFI_GRAPHICS_OUTPUT_BLT_PIXEL, dib->x * dib->y);
+
+ err = GraphicsOutput->Blt(
+ GraphicsOutput, blt,
+ EfiBltVideoToBltBuffer, x_pos, y_pos, 0, 0,
+ dib->x, dib->y, 0);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = bmp_to_blt(blt, dib, map, pixmap);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ err = graphics_mode(true);
+ if (err != EFI_SUCCESS)
+ return err;
+
+ return GraphicsOutput->Blt(
+ GraphicsOutput, blt,
+ EfiBltBufferToVideo, 0, 0, x_pos, y_pos,
+ dib->x, dib->y, 0);
+}