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