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