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