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