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
34struct video_mode {
35	list_link	link;
36	size_t		mode;
37	size_t		width, height, bits_per_pixel, bytes_per_row;
38};
39
40
41static efi_guid sGraphicsOutputGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
42static efi_guid sEdidActiveGuid = EFI_EDID_ACTIVE_PROTOCOL_GUID;
43static efi_graphics_output_protocol *sGraphicsOutput;
44static efi_edid_protocol *sEdidActiveProtocol;
45static size_t sGraphicsMode;
46static struct list sModeList;
47static uint32 sModeCount;
48static bool sModeChosen;
49static bool sSettingsLoaded;
50
51
52static int
53compare_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
67static void
68add_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
88static video_mode*
89closest_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
116static void
117get_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
148out:
149	unload_driver_settings(handle);
150}
151
152
153extern "C" status_t
154platform_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
251extern "C" void
252platform_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
279bool
280video_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
293Menu*
294video_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
321extern "C" void
322platform_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
331extern "C" void
332platform_set_palette(const uint8 *palette)
333{
334	panic("platform_set_palette unsupported");
335	return;
336}
337