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.c334
1 files changed, 334 insertions, 0 deletions
diff --git a/src/boot/efi/splash.c b/src/boot/efi/splash.c
new file mode 100644
index 0000000..8daeb71
--- /dev/null
+++ b/src/boot/efi/splash.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "graphics.h"
+#include "logarithm.h"
+#include "proto/graphics-output.h"
+#include "splash.h"
+#include "unaligned-fundamental.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;
+ uint32_t channel_mask_r;
+ uint32_t channel_mask_g;
+ uint32_t channel_mask_b;
+ uint32_t channel_mask_a;
+} _packed_;
+
+#define SIZEOF_BMP_DIB offsetof(struct bmp_dib, channel_mask_r)
+#define SIZEOF_BMP_DIB_RGB offsetof(struct bmp_dib, channel_mask_a)
+#define SIZEOF_BMP_DIB_RGBA sizeof(struct bmp_dib)
+
+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,
+ size_t size,
+ struct bmp_dib **ret_dib,
+ struct bmp_map **ret_map,
+ const uint8_t **pixmap) {
+
+ assert(bmp);
+ assert(ret_dib);
+ assert(ret_map);
+ assert(pixmap);
+
+ if (size < sizeof(struct bmp_file) + SIZEOF_BMP_DIB)
+ return EFI_INVALID_PARAMETER;
+
+ /* check file header */
+ struct bmp_file *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 */
+ struct bmp_dib *dib = (struct bmp_dib *) (bmp + sizeof(struct bmp_file));
+ if (dib->size < SIZEOF_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;
+ }
+
+ size_t row_size = ((size_t) 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 */
+ struct bmp_map *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 = 0;
+
+ if (dib->colors_used)
+ map_count = dib->colors_used;
+ else if (IN_SET(dib->depth, 1, 4, 8))
+ map_count = 1 << dib->depth;
+
+ size_t 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;
+}
+
+enum Channels { R, G, B, A, _CHANNELS_MAX };
+static void read_channel_maks(
+ const struct bmp_dib *dib,
+ uint32_t channel_mask[static _CHANNELS_MAX],
+ uint8_t channel_shift[static _CHANNELS_MAX],
+ uint8_t channel_scale[static _CHANNELS_MAX]) {
+
+ assert(dib);
+
+ if (IN_SET(dib->depth, 16, 32) && dib->size >= SIZEOF_BMP_DIB_RGB) {
+ channel_mask[R] = dib->channel_mask_r;
+ channel_mask[G] = dib->channel_mask_g;
+ channel_mask[B] = dib->channel_mask_b;
+ channel_shift[R] = __builtin_ctz(dib->channel_mask_r);
+ channel_shift[G] = __builtin_ctz(dib->channel_mask_g);
+ channel_shift[B] = __builtin_ctz(dib->channel_mask_b);
+ channel_scale[R] = 0xff / ((1 << popcount(dib->channel_mask_r)) - 1);
+ channel_scale[G] = 0xff / ((1 << popcount(dib->channel_mask_g)) - 1);
+ channel_scale[B] = 0xff / ((1 << popcount(dib->channel_mask_b)) - 1);
+
+ if (dib->size >= SIZEOF_BMP_DIB_RGBA && dib->channel_mask_a != 0) {
+ channel_mask[A] = dib->channel_mask_a;
+ channel_shift[A] = __builtin_ctz(dib->channel_mask_a);
+ channel_scale[A] = 0xff / ((1 << popcount(dib->channel_mask_a)) - 1);
+ } else {
+ channel_mask[A] = 0;
+ channel_shift[A] = 0;
+ channel_scale[A] = 0;
+ }
+ } else {
+ bool bpp16 = dib->depth == 16;
+ channel_mask[R] = bpp16 ? 0x7C00 : 0xFF0000;
+ channel_mask[G] = bpp16 ? 0x03E0 : 0x00FF00;
+ channel_mask[B] = bpp16 ? 0x001F : 0x0000FF;
+ channel_mask[A] = bpp16 ? 0x0000 : 0x000000;
+ channel_shift[R] = bpp16 ? 0xA : 0x10;
+ channel_shift[G] = bpp16 ? 0x5 : 0x08;
+ channel_shift[B] = bpp16 ? 0x0 : 0x00;
+ channel_shift[A] = bpp16 ? 0x0 : 0x00;
+ channel_scale[R] = bpp16 ? 0x08 : 0x1;
+ channel_scale[G] = bpp16 ? 0x08 : 0x1;
+ channel_scale[B] = bpp16 ? 0x08 : 0x1;
+ channel_scale[A] = bpp16 ? 0x00 : 0x0;
+ }
+}
+
+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);
+
+ uint32_t channel_mask[_CHANNELS_MAX];
+ uint8_t channel_shift[_CHANNELS_MAX], channel_scale[_CHANNELS_MAX];
+ read_channel_maks(dib, channel_mask, channel_shift, channel_scale);
+
+ /* transform and copy pixels */
+ in = pixmap;
+ for (uint32_t y = 0; y < dib->y; y++) {
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *out = &buf[(dib->y - y - 1) * dib->x];
+
+ for (uint32_t x = 0; x < dib->x; x++, in++, out++) {
+ switch (dib->depth) {
+ case 1: {
+ for (unsigned 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: {
+ unsigned 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 24:
+ out->Red = in[2];
+ out->Green = in[1];
+ out->Blue = in[0];
+ in += 2;
+ break;
+
+ case 16:
+ case 32: {
+ uint32_t i = dib->depth == 16 ? unaligned_read_ne16(in) :
+ unaligned_read_ne32(in);
+
+ uint8_t r = ((i & channel_mask[R]) >> channel_shift[R]) * channel_scale[R],
+ g = ((i & channel_mask[G]) >> channel_shift[G]) * channel_scale[G],
+ b = ((i & channel_mask[B]) >> channel_shift[B]) * channel_scale[B],
+ a = 0xFFu;
+ if (channel_mask[A] != 0)
+ a = ((i & channel_mask[A]) >> channel_shift[A]) * channel_scale[A];
+
+ out->Red = (out->Red * (0xFFu - a) + r * a) >> 8;
+ out->Green = (out->Green * (0xFFu - a) + g * a) >> 8;
+ out->Blue = (out->Blue * (0xFFu - a) + b * a) >> 8;
+
+ in += dib->depth == 16 ? 1 : 3;
+ break;
+ }
+ }
+ }
+
+ /* add row padding; new lines always start at 32 bit boundary */
+ size_t row_size = in - pixmap;
+ in += ((row_size + 3) & ~3) - row_size;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS graphics_splash(const uint8_t *content, size_t 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;
+ size_t x_pos = 0, 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(MAKE_GUID_PTR(EFI_GRAPHICS_OUTPUT_PROTOCOL), 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;
+
+ /* Read in current screen content to perform proper alpha blending. */
+ _cleanup_free_ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *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);
+}