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