uboot/lib/efi_loader/efi_gop.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 *  EFI application disk support
   4 *
   5 *  Copyright (c) 2016 Alexander Graf
   6 */
   7
   8#include <common.h>
   9#include <dm.h>
  10#include <efi_loader.h>
  11#include <lcd.h>
  12#include <log.h>
  13#include <malloc.h>
  14#include <video.h>
  15#include <asm/global_data.h>
  16
  17DECLARE_GLOBAL_DATA_PTR;
  18
  19static const efi_guid_t efi_gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
  20
  21/**
  22 * struct efi_gop_obj - graphical output protocol object
  23 *
  24 * @header:     EFI object header
  25 * @ops:        graphical output protocol interface
  26 * @info:       graphical output mode information
  27 * @mode:       graphical output mode
  28 * @bpix:       bits per pixel
  29 * @fb:         frame buffer
  30 */
  31struct efi_gop_obj {
  32        struct efi_object header;
  33        struct efi_gop ops;
  34        struct efi_gop_mode_info info;
  35        struct efi_gop_mode mode;
  36        /* Fields we only have access to during init */
  37        u32 bpix;
  38        void *fb;
  39};
  40
  41static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number,
  42                                          efi_uintn_t *size_of_info,
  43                                          struct efi_gop_mode_info **info)
  44{
  45        struct efi_gop_obj *gopobj;
  46        efi_status_t ret = EFI_SUCCESS;
  47
  48        EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info);
  49
  50        if (!this || !size_of_info || !info || mode_number) {
  51                ret = EFI_INVALID_PARAMETER;
  52                goto out;
  53        }
  54
  55        gopobj = container_of(this, struct efi_gop_obj, ops);
  56        ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, sizeof(gopobj->info),
  57                                (void **)info);
  58        if (ret != EFI_SUCCESS)
  59                goto out;
  60        *size_of_info = sizeof(gopobj->info);
  61        memcpy(*info, &gopobj->info, sizeof(gopobj->info));
  62
  63out:
  64        return EFI_EXIT(ret);
  65}
  66
  67static __always_inline struct efi_gop_pixel efi_vid30_to_blt_col(u32 vid)
  68{
  69        struct efi_gop_pixel blt = {
  70                .reserved = 0,
  71        };
  72
  73        blt.blue  = (vid & 0x3ff) >> 2;
  74        vid >>= 10;
  75        blt.green = (vid & 0x3ff) >> 2;
  76        vid >>= 10;
  77        blt.red   = (vid & 0x3ff) >> 2;
  78        return blt;
  79}
  80
  81static __always_inline u32 efi_blt_col_to_vid30(struct efi_gop_pixel *blt)
  82{
  83        return (u32)(blt->red   << 2) << 20 |
  84               (u32)(blt->green << 2) << 10 |
  85               (u32)(blt->blue  << 2);
  86}
  87
  88static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid)
  89{
  90        struct efi_gop_pixel blt = {
  91                .reserved = 0,
  92        };
  93
  94        blt.blue  = (vid & 0x1f) << 3;
  95        vid >>= 5;
  96        blt.green = (vid & 0x3f) << 2;
  97        vid >>= 6;
  98        blt.red   = (vid & 0x1f) << 3;
  99        return blt;
 100}
 101
 102static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt)
 103{
 104        return (u16)(blt->red   >> 3) << 11 |
 105               (u16)(blt->green >> 2) <<  5 |
 106               (u16)(blt->blue  >> 3);
 107}
 108
 109static __always_inline efi_status_t gop_blt_int(struct efi_gop *this,
 110                                                struct efi_gop_pixel *bufferp,
 111                                                u32 operation, efi_uintn_t sx,
 112                                                efi_uintn_t sy, efi_uintn_t dx,
 113                                                efi_uintn_t dy,
 114                                                efi_uintn_t width,
 115                                                efi_uintn_t height,
 116                                                efi_uintn_t delta,
 117                                                efi_uintn_t vid_bpp)
 118{
 119        struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
 120        efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth;
 121        u32 *fb32 = gopobj->fb;
 122        u16 *fb16 = gopobj->fb;
 123        struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4);
 124
 125        if (delta) {
 126                /* Check for 4 byte alignment */
 127                if (delta & 3)
 128                        return EFI_INVALID_PARAMETER;
 129                linelen = delta >> 2;
 130        } else {
 131                linelen = width;
 132        }
 133
 134        /* Check source rectangle */
 135        switch (operation) {
 136        case EFI_BLT_VIDEO_FILL:
 137                break;
 138        case EFI_BLT_BUFFER_TO_VIDEO:
 139                if (sx + width > linelen)
 140                        return EFI_INVALID_PARAMETER;
 141                break;
 142        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 143        case EFI_BLT_VIDEO_TO_VIDEO:
 144                if (sx + width > gopobj->info.width ||
 145                    sy + height > gopobj->info.height)
 146                        return EFI_INVALID_PARAMETER;
 147                break;
 148        default:
 149                return EFI_INVALID_PARAMETER;
 150        }
 151
 152        /* Check destination rectangle */
 153        switch (operation) {
 154        case EFI_BLT_VIDEO_FILL:
 155        case EFI_BLT_BUFFER_TO_VIDEO:
 156        case EFI_BLT_VIDEO_TO_VIDEO:
 157                if (dx + width > gopobj->info.width ||
 158                    dy + height > gopobj->info.height)
 159                        return EFI_INVALID_PARAMETER;
 160                break;
 161        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 162                if (dx + width > linelen)
 163                        return EFI_INVALID_PARAMETER;
 164                break;
 165        }
 166
 167        /* Calculate line width */
 168        switch (operation) {
 169        case EFI_BLT_BUFFER_TO_VIDEO:
 170                swidth = linelen;
 171                break;
 172        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 173        case EFI_BLT_VIDEO_TO_VIDEO:
 174                swidth = gopobj->info.width;
 175                if (!vid_bpp)
 176                        return EFI_UNSUPPORTED;
 177                break;
 178        case EFI_BLT_VIDEO_FILL:
 179                swidth = 0;
 180                break;
 181        }
 182
 183        switch (operation) {
 184        case EFI_BLT_BUFFER_TO_VIDEO:
 185        case EFI_BLT_VIDEO_FILL:
 186        case EFI_BLT_VIDEO_TO_VIDEO:
 187                dwidth = gopobj->info.width;
 188                if (!vid_bpp)
 189                        return EFI_UNSUPPORTED;
 190                break;
 191        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 192                dwidth = linelen;
 193                break;
 194        }
 195
 196        slineoff = swidth * sy;
 197        dlineoff = dwidth * dy;
 198        for (i = 0; i < height; i++) {
 199                for (j = 0; j < width; j++) {
 200                        struct efi_gop_pixel pix;
 201
 202                        /* Read source pixel */
 203                        switch (operation) {
 204                        case EFI_BLT_VIDEO_FILL:
 205                                pix = *buffer;
 206                                break;
 207                        case EFI_BLT_BUFFER_TO_VIDEO:
 208                                pix = buffer[slineoff + j + sx];
 209                                break;
 210                        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 211                        case EFI_BLT_VIDEO_TO_VIDEO:
 212                                if (vid_bpp == 32)
 213                                        pix = *(struct efi_gop_pixel *)&fb32[
 214                                                slineoff + j + sx];
 215                                else if (vid_bpp == 30)
 216                                        pix = efi_vid30_to_blt_col(fb32[
 217                                                slineoff + j + sx]);
 218                                else
 219                                        pix = efi_vid16_to_blt_col(fb16[
 220                                                slineoff + j + sx]);
 221                                break;
 222                        }
 223
 224                        /* Write destination pixel */
 225                        switch (operation) {
 226                        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 227                                buffer[dlineoff + j + dx] = pix;
 228                                break;
 229                        case EFI_BLT_BUFFER_TO_VIDEO:
 230                        case EFI_BLT_VIDEO_FILL:
 231                        case EFI_BLT_VIDEO_TO_VIDEO:
 232                                if (vid_bpp == 32)
 233                                        fb32[dlineoff + j + dx] = *(u32 *)&pix;
 234                                else if (vid_bpp == 30)
 235                                        fb32[dlineoff + j + dx] =
 236                                                efi_blt_col_to_vid30(&pix);
 237                                else
 238                                        fb16[dlineoff + j + dx] =
 239                                                efi_blt_col_to_vid16(&pix);
 240                                break;
 241                        }
 242                }
 243                slineoff += swidth;
 244                dlineoff += dwidth;
 245        }
 246
 247        return EFI_SUCCESS;
 248}
 249
 250static efi_uintn_t gop_get_bpp(struct efi_gop *this)
 251{
 252        struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
 253        efi_uintn_t vid_bpp = 0;
 254
 255        switch (gopobj->bpix) {
 256#ifdef CONFIG_DM_VIDEO
 257        case VIDEO_BPP32:
 258#else
 259        case LCD_COLOR32:
 260#endif
 261                if (gopobj->info.pixel_format == EFI_GOT_BGRA8)
 262                        vid_bpp = 32;
 263                else
 264                        vid_bpp = 30;
 265                break;
 266#ifdef CONFIG_DM_VIDEO
 267        case VIDEO_BPP16:
 268#else
 269        case LCD_COLOR16:
 270#endif
 271                vid_bpp = 16;
 272                break;
 273        }
 274
 275        return vid_bpp;
 276}
 277
 278/*
 279 * GCC can't optimize our BLT function well, but we need to make sure that
 280 * our 2-dimensional loop gets executed very quickly, otherwise the system
 281 * will feel slow.
 282 *
 283 * By manually putting all obvious branch targets into functions which call
 284 * our generic BLT function with constants, the compiler can successfully
 285 * optimize for speed.
 286 */
 287static efi_status_t gop_blt_video_fill(struct efi_gop *this,
 288                                       struct efi_gop_pixel *buffer,
 289                                       u32 foo, efi_uintn_t sx,
 290                                       efi_uintn_t sy, efi_uintn_t dx,
 291                                       efi_uintn_t dy, efi_uintn_t width,
 292                                       efi_uintn_t height, efi_uintn_t delta,
 293                                       efi_uintn_t vid_bpp)
 294{
 295        return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx,
 296                           dy, width, height, delta, vid_bpp);
 297}
 298
 299static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this,
 300                                         struct efi_gop_pixel *buffer,
 301                                         u32 foo, efi_uintn_t sx,
 302                                         efi_uintn_t sy, efi_uintn_t dx,
 303                                         efi_uintn_t dy, efi_uintn_t width,
 304                                         efi_uintn_t height, efi_uintn_t delta)
 305{
 306        return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
 307                           dy, width, height, delta, 16);
 308}
 309
 310static efi_status_t gop_blt_buf_to_vid30(struct efi_gop *this,
 311                                         struct efi_gop_pixel *buffer,
 312                                         u32 foo, efi_uintn_t sx,
 313                                         efi_uintn_t sy, efi_uintn_t dx,
 314                                         efi_uintn_t dy, efi_uintn_t width,
 315                                         efi_uintn_t height, efi_uintn_t delta)
 316{
 317        return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
 318                           dy, width, height, delta, 30);
 319}
 320
 321static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this,
 322                                         struct efi_gop_pixel *buffer,
 323                                         u32 foo, efi_uintn_t sx,
 324                                         efi_uintn_t sy, efi_uintn_t dx,
 325                                         efi_uintn_t dy, efi_uintn_t width,
 326                                         efi_uintn_t height, efi_uintn_t delta)
 327{
 328        return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
 329                           dy, width, height, delta, 32);
 330}
 331
 332static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this,
 333                                       struct efi_gop_pixel *buffer,
 334                                       u32 foo, efi_uintn_t sx,
 335                                       efi_uintn_t sy, efi_uintn_t dx,
 336                                       efi_uintn_t dy, efi_uintn_t width,
 337                                       efi_uintn_t height, efi_uintn_t delta,
 338                                       efi_uintn_t vid_bpp)
 339{
 340        return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx,
 341                           dy, width, height, delta, vid_bpp);
 342}
 343
 344static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this,
 345                                       struct efi_gop_pixel *buffer,
 346                                       u32 foo, efi_uintn_t sx,
 347                                       efi_uintn_t sy, efi_uintn_t dx,
 348                                       efi_uintn_t dy, efi_uintn_t width,
 349                                       efi_uintn_t height, efi_uintn_t delta,
 350                                       efi_uintn_t vid_bpp)
 351{
 352        return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy,
 353                           dx, dy, width, height, delta, vid_bpp);
 354}
 355
 356/**
 357 * gop_set_mode() - set graphical output mode
 358 *
 359 * This function implements the SetMode() service.
 360 *
 361 * See the Unified Extensible Firmware Interface (UEFI) specification for
 362 * details.
 363 *
 364 * @this:               the graphical output protocol
 365 * @mode_number:        the mode to be set
 366 * Return:              status code
 367 */
 368static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number)
 369{
 370        struct efi_gop_obj *gopobj;
 371        struct efi_gop_pixel buffer = {0, 0, 0, 0};
 372        efi_uintn_t vid_bpp;
 373        efi_status_t ret = EFI_SUCCESS;
 374
 375        EFI_ENTRY("%p, %x", this, mode_number);
 376
 377        if (!this) {
 378                ret = EFI_INVALID_PARAMETER;
 379                goto out;
 380        }
 381        if (mode_number) {
 382                ret = EFI_UNSUPPORTED;
 383                goto out;
 384        }
 385        gopobj = container_of(this, struct efi_gop_obj, ops);
 386        vid_bpp = gop_get_bpp(this);
 387        ret = gop_blt_video_fill(this, &buffer, EFI_BLT_VIDEO_FILL, 0, 0, 0, 0,
 388                                 gopobj->info.width, gopobj->info.height, 0,
 389                                 vid_bpp);
 390out:
 391        return EFI_EXIT(ret);
 392}
 393
 394/*
 395 * Copy rectangle.
 396 *
 397 * This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL.
 398 * See the Unified Extensible Firmware Interface (UEFI) specification for
 399 * details.
 400 *
 401 * @this:       EFI_GRAPHICS_OUTPUT_PROTOCOL
 402 * @buffer:     pixel buffer
 403 * @sx:         source x-coordinate
 404 * @sy:         source y-coordinate
 405 * @dx:         destination x-coordinate
 406 * @dy:         destination y-coordinate
 407 * @width:      width of rectangle
 408 * @height:     height of rectangle
 409 * @delta:      length in bytes of a line in the pixel buffer (optional)
 410 * @return:     status code
 411 */
 412efi_status_t EFIAPI gop_blt(struct efi_gop *this, struct efi_gop_pixel *buffer,
 413                            u32 operation, efi_uintn_t sx,
 414                            efi_uintn_t sy, efi_uintn_t dx,
 415                            efi_uintn_t dy, efi_uintn_t width,
 416                            efi_uintn_t height, efi_uintn_t delta)
 417{
 418        efi_status_t ret = EFI_INVALID_PARAMETER;
 419        efi_uintn_t vid_bpp;
 420
 421        EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this,
 422                  buffer, operation, sx, sy, dx, dy, width, height, delta);
 423
 424        vid_bpp = gop_get_bpp(this);
 425
 426        /* Allow for compiler optimization */
 427        switch (operation) {
 428        case EFI_BLT_VIDEO_FILL:
 429                ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx,
 430                                         dy, width, height, delta, vid_bpp);
 431                break;
 432        case EFI_BLT_BUFFER_TO_VIDEO:
 433                /* This needs to be super-fast, so duplicate for 16/32bpp */
 434                if (vid_bpp == 32)
 435                        ret = gop_blt_buf_to_vid32(this, buffer, operation, sx,
 436                                                   sy, dx, dy, width, height,
 437                                                   delta);
 438                else if (vid_bpp == 30)
 439                        ret = gop_blt_buf_to_vid30(this, buffer, operation, sx,
 440                                                   sy, dx, dy, width, height,
 441                                                   delta);
 442                else
 443                        ret = gop_blt_buf_to_vid16(this, buffer, operation, sx,
 444                                                   sy, dx, dy, width, height,
 445                                                   delta);
 446                break;
 447        case EFI_BLT_VIDEO_TO_VIDEO:
 448                ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx,
 449                                         dy, width, height, delta, vid_bpp);
 450                break;
 451        case EFI_BLT_VIDEO_TO_BLT_BUFFER:
 452                ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx,
 453                                         dy, width, height, delta, vid_bpp);
 454                break;
 455        default:
 456                ret = EFI_INVALID_PARAMETER;
 457        }
 458
 459        if (ret != EFI_SUCCESS)
 460                return EFI_EXIT(ret);
 461
 462#ifdef CONFIG_DM_VIDEO
 463        video_sync_all();
 464#else
 465        lcd_sync();
 466#endif
 467
 468        return EFI_EXIT(EFI_SUCCESS);
 469}
 470
 471/*
 472 * Install graphical output protocol.
 473 *
 474 * If no supported video device exists this is not considered as an
 475 * error.
 476 */
 477efi_status_t efi_gop_register(void)
 478{
 479        struct efi_gop_obj *gopobj;
 480        u32 bpix, format, col, row;
 481        u64 fb_base, fb_size;
 482        void *fb;
 483        efi_status_t ret;
 484
 485#ifdef CONFIG_DM_VIDEO
 486        struct udevice *vdev;
 487        struct video_priv *priv;
 488
 489        /* We only support a single video output device for now */
 490        if (uclass_first_device(UCLASS_VIDEO, &vdev) || !vdev) {
 491                debug("WARNING: No video device\n");
 492                return EFI_SUCCESS;
 493        }
 494
 495        priv = dev_get_uclass_priv(vdev);
 496        bpix = priv->bpix;
 497        format = priv->format;
 498        col = video_get_xsize(vdev);
 499        row = video_get_ysize(vdev);
 500        fb_base = (uintptr_t)priv->fb;
 501        fb_size = priv->fb_size;
 502        fb = priv->fb;
 503#else
 504        int line_len;
 505
 506        bpix = panel_info.vl_bpix;
 507        format = VIDEO_UNKNOWN;
 508        col = panel_info.vl_col;
 509        row = panel_info.vl_row;
 510        fb_base = gd->fb_base;
 511        fb_size = lcd_get_size(&line_len);
 512        fb = (void*)gd->fb_base;
 513#endif
 514
 515        switch (bpix) {
 516#ifdef CONFIG_DM_VIDEO
 517        case VIDEO_BPP16:
 518        case VIDEO_BPP32:
 519#else
 520        case LCD_COLOR32:
 521        case LCD_COLOR16:
 522#endif
 523                break;
 524        default:
 525                /* So far, we only work in 16 or 32 bit mode */
 526                debug("WARNING: Unsupported video mode\n");
 527                return EFI_SUCCESS;
 528        }
 529
 530        gopobj = calloc(1, sizeof(*gopobj));
 531        if (!gopobj) {
 532                printf("ERROR: Out of memory\n");
 533                return EFI_OUT_OF_RESOURCES;
 534        }
 535
 536        /* Hook up to the device list */
 537        efi_add_handle(&gopobj->header);
 538
 539        /* Fill in object data */
 540        ret = efi_add_protocol(&gopobj->header, &efi_gop_guid,
 541                               &gopobj->ops);
 542        if (ret != EFI_SUCCESS) {
 543                printf("ERROR: Failure adding GOP protocol\n");
 544                return ret;
 545        }
 546        gopobj->ops.query_mode = gop_query_mode;
 547        gopobj->ops.set_mode = gop_set_mode;
 548        gopobj->ops.blt = gop_blt;
 549        gopobj->ops.mode = &gopobj->mode;
 550
 551        gopobj->mode.max_mode = 1;
 552        gopobj->mode.info = &gopobj->info;
 553        gopobj->mode.info_size = sizeof(gopobj->info);
 554
 555        gopobj->mode.fb_base = fb_base;
 556        gopobj->mode.fb_size = fb_size;
 557
 558        gopobj->info.version = 0;
 559        gopobj->info.width = col;
 560        gopobj->info.height = row;
 561#ifdef CONFIG_DM_VIDEO
 562        if (bpix == VIDEO_BPP32)
 563#else
 564        if (bpix == LCD_COLOR32)
 565#endif
 566        {
 567                if (format == VIDEO_X2R10G10B10) {
 568                        gopobj->info.pixel_format = EFI_GOT_BITMASK;
 569                        gopobj->info.pixel_bitmask[0] = 0x3ff00000; /* red */
 570                        gopobj->info.pixel_bitmask[1] = 0x000ffc00; /* green */
 571                        gopobj->info.pixel_bitmask[2] = 0x000003ff; /* blue */
 572                        gopobj->info.pixel_bitmask[3] = 0xc0000000; /* reserved */
 573                } else {
 574                        gopobj->info.pixel_format = EFI_GOT_BGRA8;
 575                }
 576        } else {
 577                gopobj->info.pixel_format = EFI_GOT_BITMASK;
 578                gopobj->info.pixel_bitmask[0] = 0xf800; /* red */
 579                gopobj->info.pixel_bitmask[1] = 0x07e0; /* green */
 580                gopobj->info.pixel_bitmask[2] = 0x001f; /* blue */
 581        }
 582        gopobj->info.pixels_per_scanline = col;
 583        gopobj->bpix = bpix;
 584        gopobj->fb = fb;
 585
 586        return EFI_SUCCESS;
 587}
 588