diff options
Diffstat (limited to 'roms/u-boot/lib/efi_loader/efi_gop.c')
-rw-r--r-- | roms/u-boot/lib/efi_loader/efi_gop.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/roms/u-boot/lib/efi_loader/efi_gop.c b/roms/u-boot/lib/efi_loader/efi_gop.c new file mode 100644 index 000000000..1206b2d7a --- /dev/null +++ b/roms/u-boot/lib/efi_loader/efi_gop.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application disk support + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <dm.h> +#include <efi_loader.h> +#include <lcd.h> +#include <log.h> +#include <malloc.h> +#include <video.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const efi_guid_t efi_gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + +/** + * struct efi_gop_obj - graphical output protocol object + * + * @header: EFI object header + * @ops: graphical output protocol interface + * @info: graphical output mode information + * @mode: graphical output mode + * @bpix: bits per pixel + * @fb: frame buffer + */ +struct efi_gop_obj { + struct efi_object header; + struct efi_gop ops; + struct efi_gop_mode_info info; + struct efi_gop_mode mode; + /* Fields we only have access to during init */ + u32 bpix; + void *fb; +}; + +static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number, + efi_uintn_t *size_of_info, + struct efi_gop_mode_info **info) +{ + struct efi_gop_obj *gopobj; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info); + + if (!this || !size_of_info || !info || mode_number) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + gopobj = container_of(this, struct efi_gop_obj, ops); + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, sizeof(gopobj->info), + (void **)info); + if (ret != EFI_SUCCESS) + goto out; + *size_of_info = sizeof(gopobj->info); + memcpy(*info, &gopobj->info, sizeof(gopobj->info)); + +out: + return EFI_EXIT(ret); +} + +static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid) +{ + struct efi_gop_pixel blt = { + .reserved = 0, + }; + + blt.blue = (vid & 0x1f) << 3; + vid >>= 5; + blt.green = (vid & 0x3f) << 2; + vid >>= 6; + blt.red = (vid & 0x1f) << 3; + return blt; +} + +static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt) +{ + return (u16)(blt->red >> 3) << 11 | + (u16)(blt->green >> 2) << 5 | + (u16)(blt->blue >> 3); +} + +static __always_inline efi_status_t gop_blt_int(struct efi_gop *this, + struct efi_gop_pixel *bufferp, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, + efi_uintn_t width, + efi_uintn_t height, + efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth; + u32 *fb32 = gopobj->fb; + u16 *fb16 = gopobj->fb; + struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4); + + if (delta) { + /* Check for 4 byte alignment */ + if (delta & 3) + return EFI_INVALID_PARAMETER; + linelen = delta >> 2; + } else { + linelen = width; + } + + /* Check source rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + break; + case EFI_BLT_BUFFER_TO_VIDEO: + if (sx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (sx + width > gopobj->info.width || + sy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* Check destination rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_TO_VIDEO: + if (dx + width > gopobj->info.width || + dy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + if (dx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + } + + /* Calculate line width */ + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + swidth = linelen; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + swidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_FILL: + swidth = 0; + break; + } + + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + dwidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + dwidth = linelen; + break; + } + + slineoff = swidth * sy; + dlineoff = dwidth * dy; + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + struct efi_gop_pixel pix; + + /* Read source pixel */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + pix = *buffer; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + pix = buffer[slineoff + j + sx]; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + pix = *(struct efi_gop_pixel *)&fb32[ + slineoff + j + sx]; + else + pix = efi_vid16_to_blt_col(fb16[ + slineoff + j + sx]); + break; + } + + /* Write destination pixel */ + switch (operation) { + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + buffer[dlineoff + j + dx] = pix; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + fb32[dlineoff + j + dx] = *(u32 *)&pix; + else + fb16[dlineoff + j + dx] = + efi_blt_col_to_vid16(&pix); + break; + } + } + slineoff += swidth; + dlineoff += dwidth; + } + + return EFI_SUCCESS; +} + +static efi_uintn_t gop_get_bpp(struct efi_gop *this) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t vid_bpp = 0; + + switch (gopobj->bpix) { +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP32: +#else + case LCD_COLOR32: +#endif + vid_bpp = 32; + break; +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP16: +#else + case LCD_COLOR16: +#endif + vid_bpp = 16; + break; + } + + return vid_bpp; +} + +/* + * GCC can't optimize our BLT function well, but we need to make sure that + * our 2-dimensional loop gets executed very quickly, otherwise the system + * will feel slow. + * + * By manually putting all obvious branch targets into functions which call + * our generic BLT function with constants, the compiler can successfully + * optimize for speed. + */ +static efi_status_t gop_blt_video_fill(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 16); +} + +static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 32); +} + +static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy, + dx, dy, width, height, delta, vid_bpp); +} + +/** + * gop_set_mode() - set graphical output mode + * + * This function implements the SetMode() service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: the graphical output protocol + * @mode_number: the mode to be set + * Return: status code + */ +static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number) +{ + struct efi_gop_obj *gopobj; + struct efi_gop_pixel buffer = {0, 0, 0, 0}; + efi_uintn_t vid_bpp; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x", this, mode_number); + + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (mode_number) { + ret = EFI_UNSUPPORTED; + goto out; + } + gopobj = container_of(this, struct efi_gop_obj, ops); + vid_bpp = gop_get_bpp(this); + ret = gop_blt_video_fill(this, &buffer, EFI_BLT_VIDEO_FILL, 0, 0, 0, 0, + gopobj->info.width, gopobj->info.height, 0, + vid_bpp); +out: + return EFI_EXIT(ret); +} + +/* + * Copy rectangle. + * + * This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: EFI_GRAPHICS_OUTPUT_PROTOCOL + * @buffer: pixel buffer + * @sx: source x-coordinate + * @sy: source y-coordinate + * @dx: destination x-coordinate + * @dy: destination y-coordinate + * @width: width of rectangle + * @height: height of rectangle + * @delta: length in bytes of a line in the pixel buffer (optional) + * @return: status code + */ +efi_status_t EFIAPI gop_blt(struct efi_gop *this, struct efi_gop_pixel *buffer, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + efi_status_t ret = EFI_INVALID_PARAMETER; + efi_uintn_t vid_bpp; + + EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this, + buffer, operation, sx, sy, dx, dy, width, height, delta); + + vid_bpp = gop_get_bpp(this); + + /* Allow for compiler optimization */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_BUFFER_TO_VIDEO: + /* This needs to be super-fast, so duplicate for 16/32bpp */ + if (vid_bpp == 32) + ret = gop_blt_buf_to_vid32(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + else + ret = gop_blt_buf_to_vid16(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + break; + case EFI_BLT_VIDEO_TO_VIDEO: + ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + default: + ret = EFI_INVALID_PARAMETER; + } + + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + +#ifdef CONFIG_DM_VIDEO + video_sync_all(); +#else + lcd_sync(); +#endif + + return EFI_EXIT(EFI_SUCCESS); +} + +/* + * Install graphical output protocol. + * + * If no supported video device exists this is not considered as an + * error. + */ +efi_status_t efi_gop_register(void) +{ + struct efi_gop_obj *gopobj; + u32 bpix, col, row; + u64 fb_base, fb_size; + void *fb; + efi_status_t ret; + +#ifdef CONFIG_DM_VIDEO + struct udevice *vdev; + struct video_priv *priv; + + /* We only support a single video output device for now */ + if (uclass_first_device(UCLASS_VIDEO, &vdev) || !vdev) { + debug("WARNING: No video device\n"); + return EFI_SUCCESS; + } + + priv = dev_get_uclass_priv(vdev); + bpix = priv->bpix; + col = video_get_xsize(vdev); + row = video_get_ysize(vdev); + fb_base = (uintptr_t)priv->fb; + fb_size = priv->fb_size; + fb = priv->fb; +#else + int line_len; + + bpix = panel_info.vl_bpix; + col = panel_info.vl_col; + row = panel_info.vl_row; + fb_base = gd->fb_base; + fb_size = lcd_get_size(&line_len); + fb = (void*)gd->fb_base; +#endif + + switch (bpix) { +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP16: + case VIDEO_BPP32: +#else + case LCD_COLOR32: + case LCD_COLOR16: +#endif + break; + default: + /* So far, we only work in 16 or 32 bit mode */ + debug("WARNING: Unsupported video mode\n"); + return EFI_SUCCESS; + } + + gopobj = calloc(1, sizeof(*gopobj)); + if (!gopobj) { + printf("ERROR: Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + /* Hook up to the device list */ + efi_add_handle(&gopobj->header); + + /* Fill in object data */ + ret = efi_add_protocol(&gopobj->header, &efi_gop_guid, + &gopobj->ops); + if (ret != EFI_SUCCESS) { + printf("ERROR: Failure adding GOP protocol\n"); + return ret; + } + gopobj->ops.query_mode = gop_query_mode; + gopobj->ops.set_mode = gop_set_mode; + gopobj->ops.blt = gop_blt; + gopobj->ops.mode = &gopobj->mode; + + gopobj->mode.max_mode = 1; + gopobj->mode.info = &gopobj->info; + gopobj->mode.info_size = sizeof(gopobj->info); + + gopobj->mode.fb_base = fb_base; + gopobj->mode.fb_size = fb_size; + + gopobj->info.version = 0; + gopobj->info.width = col; + gopobj->info.height = row; +#ifdef CONFIG_DM_VIDEO + if (bpix == VIDEO_BPP32) +#else + if (bpix == LCD_COLOR32) +#endif + { + gopobj->info.pixel_format = EFI_GOT_BGRA8; + } else { + gopobj->info.pixel_format = EFI_GOT_BITMASK; + gopobj->info.pixel_bitmask[0] = 0xf800; /* red */ + gopobj->info.pixel_bitmask[1] = 0x07e0; /* green */ + gopobj->info.pixel_bitmask[2] = 0x001f; /* blue */ + } + gopobj->info.pixels_per_scanline = col; + gopobj->bpix = bpix; + gopobj->fb = fb; + + return EFI_SUCCESS; +} |