xref: /haiku/src/system/boot/platform/efi/video.cpp (revision 1f52c921e27aa442370e1bd4adc021acf2b78b64)
1 /*
2  * Copyright 2016, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "video.h"
8 
9 #include <stdlib.h>
10 
11 #include <boot/kernel_args.h>
12 #include <boot/menu.h>
13 #include <boot/platform.h>
14 #include <boot/platform/generic/video.h>
15 #include <boot/stage2.h>
16 #include <boot/stdio.h>
17 #include <drivers/driver_settings.h>
18 #include <util/list.h>
19 
20 #include "efi_platform.h"
21 
22 
23 //#define TRACE_VIDEO
24 #ifdef TRACE_VIDEO
25 #	define TRACE(x) dprintf x
26 #else
27 #	define TRACE(x) ;
28 #endif
29 
30 
31 struct video_mode {
32 	list_link	link;
33 	UINTN		mode;
34 	UINTN		width, height, bits_per_pixel, bytes_per_row;
35 };
36 
37 
38 static EFI_GUID sGraphicsOutputGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
39 static EFI_GRAPHICS_OUTPUT_PROTOCOL *sGraphicsOutput;
40 static UINTN sGraphicsMode;
41 static struct list sModeList;
42 static uint32 sModeCount;
43 static bool sModeChosen;
44 static bool sSettingsLoaded;
45 
46 
47 static int
48 compare_video_modes(video_mode *a, video_mode *b)
49 {
50 	int compare = a->width - b->width;
51 	if (compare != 0)
52 		return compare;
53 
54 	compare = a->height - b->height;
55 	if (compare != 0)
56 		return compare;
57 
58 	return a->bits_per_pixel - b->bits_per_pixel;
59 }
60 
61 
62 static void
63 add_video_mode(video_mode *videoMode)
64 {
65 	video_mode *mode = NULL;
66 	while ((mode = (video_mode*)list_get_next_item(&sModeList, mode))
67 			!= NULL) {
68 		int compare = compare_video_modes(videoMode, mode);
69 		if (compare == 0) {
70 			// mode already exists
71 			return;
72 		}
73 
74 		if (compare > 0)
75 			break;
76 	}
77 
78 	list_insert_item_before(&sModeList, mode, videoMode);
79 	sModeCount++;
80 }
81 
82 
83 static video_mode*
84 closest_video_mode(uint32 width, uint32 height, uint32 depth)
85 {
86 	video_mode *bestMode = NULL;
87 	int64 bestDiff = 0;
88 
89 	video_mode *mode = NULL;
90 	while ((mode = (video_mode*)list_get_next_item(&sModeList, mode)) != NULL) {
91 		if (mode->width > width) {
92 			// Only choose modes with a width less or equal than the searched
93 			// one; or else it might well be that the monitor cannot keep up.
94 			continue;
95 		}
96 
97 		int64 diff = 2 * abs((int64)mode->width - width)
98 			+ abs((int64)mode->height - height)
99 			+ abs((int64)mode->bits_per_pixel - depth);
100 
101 		if (bestMode == NULL || bestDiff > diff) {
102 			bestMode = mode;
103 			bestDiff = diff;
104 		}
105 	}
106 
107 	return bestMode;
108 }
109 
110 
111 static void
112 get_mode_from_settings(void)
113 {
114 	if (sSettingsLoaded)
115 		return;
116 
117 	void *handle = load_driver_settings("vesa");
118 	if (handle == NULL)
119 		return;
120 
121 	const driver_settings *settings = get_driver_settings(handle);
122 	if (settings == NULL)
123 		goto out;
124 
125 	sSettingsLoaded = true;
126 
127 	for (int32 i = 0; i < settings->parameter_count; i++) {
128 		driver_parameter &parameter = settings->parameters[i];
129 
130 		if (parameter.value_count < 3 || strcmp(parameter.name, "mode") != 0) continue;
131 		uint32 width = strtoul(parameter.values[0], NULL, 0);
132 		uint32 height = strtoul(parameter.values[1], NULL, 0);
133 		uint32 depth = strtoul(parameter.values[2], NULL, 0);
134 
135 		// search mode that fits
136 		video_mode *mode = closest_video_mode(width, height, depth);
137 		if (mode != NULL) {
138 			sGraphicsMode = mode->mode;
139 			break;
140 		}
141 	}
142 
143 out:
144 	unload_driver_settings(handle);
145 }
146 
147 
148 extern "C" status_t
149 platform_init_video(void)
150 {
151 	list_init(&sModeList);
152 
153 	// we don't support VESA modes or EDID
154 	gKernelArgs.vesa_modes = NULL;
155 	gKernelArgs.vesa_modes_size = 0;
156 	gKernelArgs.edid_info = NULL;
157 
158 	// make a guess at the best video mode to use, and save the mode ID
159 	// for switching to graphics mode
160 	EFI_STATUS status = kBootServices->LocateProtocol(&sGraphicsOutputGuid,
161 		NULL, (void **)&sGraphicsOutput);
162 	if (sGraphicsOutput == NULL || status != EFI_SUCCESS) {
163 		gKernelArgs.frame_buffer.enabled = false;
164 		sGraphicsOutput = NULL;
165 		return B_ERROR;
166 	}
167 
168 	UINTN bestArea = 0;
169 	UINTN bestDepth = 0;
170 
171 	TRACE(("looking for best graphics mode...\n"));
172 
173 	for (UINTN mode = 0; mode < sGraphicsOutput->Mode->MaxMode; ++mode) {
174 		EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
175 		UINTN size, depth;
176 		sGraphicsOutput->QueryMode(sGraphicsOutput, mode, &size, &info);
177 		UINTN area = info->HorizontalResolution * info->VerticalResolution;
178 		TRACE(("  mode: %lu\n", mode));
179 		TRACE(("  width: %u\n", info->HorizontalResolution));
180 		TRACE(("  height: %u\n", info->VerticalResolution));
181 		TRACE(("  area: %lu\n", area));
182 		if (info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
183 			depth = 32;
184 		} else if (info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
185 			// seen this in the wild, but acts like RGB, go figure...
186 			depth = 32;
187 		} else if (info->PixelFormat == PixelBitMask
188 			&& info->PixelInformation.RedMask == 0xFF0000
189 			&& info->PixelInformation.GreenMask == 0x00FF00
190 			&& info->PixelInformation.BlueMask == 0x0000FF
191 			&& info->PixelInformation.ReservedMask == 0) {
192 			depth = 24;
193 		} else {
194 			TRACE(("  pixel format: %x unsupported\n",
195 				info->PixelFormat));
196 			continue;
197 		}
198 		TRACE(("  depth: %lu\n", depth));
199 
200 		video_mode *videoMode = (video_mode*)malloc(sizeof(struct video_mode));
201 		if (videoMode != NULL) {
202 			videoMode->mode = mode;
203 			videoMode->width = info->HorizontalResolution;
204 			videoMode->height = info->VerticalResolution;
205 			videoMode->bits_per_pixel = info->PixelFormat == PixelBitMask ? 24 : 32;
206 			videoMode->bytes_per_row = info->PixelsPerScanLine * depth / 8;
207 			add_video_mode(videoMode);
208 		}
209 
210 		area *= depth;
211 		TRACE(("  area (w/depth): %lu\n", area));
212 		if (area >= bestArea) {
213 			TRACE(("selected new best mode: %lu\n", mode));
214 			bestArea = area;
215 			bestDepth = depth;
216 			sGraphicsMode = mode;
217 		}
218 	}
219 
220 	if (bestArea == 0 || bestDepth == 0) {
221 		sGraphicsOutput = NULL;
222 		gKernelArgs.frame_buffer.enabled = false;
223 		return B_ERROR;
224 	}
225 
226 	gKernelArgs.frame_buffer.enabled = true;
227 	sModeChosen = false;
228 	sSettingsLoaded = false;
229 	return B_OK;
230 }
231 
232 
233 extern "C" void
234 platform_switch_to_logo(void)
235 {
236 	if (sGraphicsOutput == NULL || !gKernelArgs.frame_buffer.enabled)
237 		return;
238 
239 	if (!sModeChosen)
240 		get_mode_from_settings();
241 
242 	sGraphicsOutput->SetMode(sGraphicsOutput, sGraphicsMode);
243 	gKernelArgs.frame_buffer.physical_buffer.start =
244 		sGraphicsOutput->Mode->FrameBufferBase;
245 	gKernelArgs.frame_buffer.physical_buffer.size =
246 		sGraphicsOutput->Mode->FrameBufferSize;
247 	gKernelArgs.frame_buffer.width =
248 		sGraphicsOutput->Mode->Info->HorizontalResolution;
249 	gKernelArgs.frame_buffer.height =
250 		sGraphicsOutput->Mode->Info->VerticalResolution;
251 	gKernelArgs.frame_buffer.depth =
252 		sGraphicsOutput->Mode->Info->PixelFormat == PixelBitMask ? 24 : 32;
253 	gKernelArgs.frame_buffer.bytes_per_row =
254 		sGraphicsOutput->Mode->Info->PixelsPerScanLine
255 			* gKernelArgs.frame_buffer.depth / 8;
256 
257 	video_display_splash(gKernelArgs.frame_buffer.physical_buffer.start);
258 }
259 
260 
261 bool
262 video_mode_hook(Menu *menu, MenuItem *item)
263 {
264 	Menu* submenu = item->Submenu();
265 	MenuItem* subitem = submenu->FindMarked();
266 	if (subitem != NULL) {
267 		sGraphicsMode = (UINTN)subitem->Data();
268 		sModeChosen = true;
269 	}
270 
271 	return true;
272 }
273 
274 
275 Menu*
276 video_mode_menu()
277 {
278 	Menu *menu = new(std::nothrow)Menu(CHOICE_MENU, "Select Video Mode");
279 	MenuItem *item;
280 
281 	video_mode *mode = NULL;
282 	while ((mode = (video_mode*)list_get_next_item(&sModeList, mode)) != NULL) {
283 		char label[64];
284 		snprintf(label, sizeof(label), "%lux%lu %lu bit", mode->width,
285 			mode->height, mode->bits_per_pixel);
286 
287 		menu->AddItem(item = new (std::nothrow)MenuItem(label));
288 		item->SetData((const void*)mode->mode);
289 		if (mode->mode == sGraphicsMode) {
290 			item->SetMarked(true);
291 			item->Select(true);
292 		}
293 	}
294 
295 	menu->AddSeparatorItem();
296 	menu->AddItem(item = new(std::nothrow)MenuItem("Return to main menu"));
297 	item->SetType(MENU_ITEM_NO_CHOICE);
298 
299 	return menu;
300 }
301 
302 
303 extern "C" void
304 platform_blit4(addr_t frameBuffer, const uint8 *data,
305 	uint16 width, uint16 height, uint16 imageWidth,
306 	uint16 left, uint16 top)
307 {
308 	panic("platform_blit4 unsupported");
309 	return;
310 }
311 
312 
313 extern "C" void
314 platform_set_palette(const uint8 *palette)
315 {
316 	panic("platform_set_palette unsupported");
317 	return;
318 }
319