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