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