xref: /openbmc/u-boot/lib/efi_loader/efi_gop.c (revision 8e51c0f2)
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 }
483