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