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
31struct video_mode {
32	list_link	link;
33	UINTN		mode;
34	UINTN		width, height, bits_per_pixel, bytes_per_row;
35};
36
37
38static EFI_GUID sGraphicsOutputGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
39static EFI_GRAPHICS_OUTPUT_PROTOCOL *sGraphicsOutput;
40static UINTN sGraphicsMode;
41static struct list sModeList;
42static uint32 sModeCount;
43static bool sModeChosen;
44static bool sSettingsLoaded;
45
46
47static int
48compare_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
62static void
63add_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
83static video_mode*
84closest_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
111static void
112get_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
143out:
144	unload_driver_settings(handle);
145}
146
147
148extern "C" status_t
149platform_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
233extern "C" void
234platform_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
261bool
262video_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
275Menu*
276video_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
303extern "C" void
304platform_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
313extern "C" void
314platform_set_palette(const uint8 *palette)
315{
316	panic("platform_set_palette unsupported");
317	return;
318}
319