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
compare_video_modes(video_mode * a,video_mode * b)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
add_video_mode(video_mode * videoMode)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*
closest_video_mode(uint32 width,uint32 height,uint32 depth)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
get_mode_from_settings(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 ¶meter = 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
platform_init_video(void)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 if (info->PixelFormat == PixelBitMask
201 && info->PixelInformation.RedMask == 0xF800
202 && info->PixelInformation.GreenMask == 0x07E0
203 && info->PixelInformation.BlueMask == 0x001F
204 && info->PixelInformation.ReservedMask == 0) {
205 depth = 16;
206 } else {
207 TRACE((" pixel format: %x unsupported\n",
208 info->PixelFormat));
209 continue;
210 }
211 TRACE((" depth: %lu\n", depth));
212
213 video_mode *videoMode = (video_mode*)malloc(sizeof(struct video_mode));
214 if (videoMode != NULL) {
215 videoMode->mode = mode;
216 videoMode->width = info->HorizontalResolution;
217 videoMode->height = info->VerticalResolution;
218 videoMode->bits_per_pixel = depth;
219 videoMode->bytes_per_row = info->PixelsPerScanLine * depth / 8;
220 add_video_mode(videoMode);
221 }
222
223 area *= depth;
224 TRACE((" area (w/depth): %lu\n", area));
225 if (area >= bestArea) {
226 TRACE(("selected new best mode: %lu\n", mode));
227 bestArea = area;
228 bestDepth = depth;
229 sGraphicsMode = mode;
230 }
231 }
232
233 if (bestArea == 0 || bestDepth == 0) {
234 sGraphicsOutput = NULL;
235 gKernelArgs.frame_buffer.enabled = false;
236 return B_ERROR;
237 }
238
239 gKernelArgs.frame_buffer.enabled = true;
240 sModeChosen = false;
241 sSettingsLoaded = false;
242
243 status = kBootServices->LocateProtocol(&sEdidActiveGuid, NULL, (void **)&sEdidActiveProtocol);
244 if ((sEdidActiveProtocol != NULL) && (status == EFI_SUCCESS)
245 && (sEdidActiveProtocol->SizeOfEdid) != 0) {
246 edid1_info* edid_info = (edid1_info*)kernel_args_malloc(sizeof(edid1_info));
247 if (edid_info != NULL) {
248 edid_decode(edid_info, (edid1_raw*)sEdidActiveProtocol->Edid);
249 gKernelArgs.edid_info = edid_info;
250 }
251 }
252
253 return B_OK;
254 }
255
256
257 extern "C" void
platform_switch_to_logo(void)258 platform_switch_to_logo(void)
259 {
260 if (sGraphicsOutput == NULL || !gKernelArgs.frame_buffer.enabled)
261 return;
262
263 if (!sModeChosen)
264 get_mode_from_settings();
265
266 sGraphicsOutput->SetMode(sGraphicsOutput, sGraphicsMode);
267 gKernelArgs.frame_buffer.physical_buffer.start =
268 sGraphicsOutput->Mode->FrameBufferBase;
269 gKernelArgs.frame_buffer.physical_buffer.size =
270 sGraphicsOutput->Mode->FrameBufferSize;
271 gKernelArgs.frame_buffer.width =
272 sGraphicsOutput->Mode->Info->HorizontalResolution;
273 gKernelArgs.frame_buffer.height =
274 sGraphicsOutput->Mode->Info->VerticalResolution;
275 gKernelArgs.frame_buffer.depth =
276 sGraphicsOutput->Mode->Info->PixelFormat == PixelBitMask ? 24 : 32;
277 gKernelArgs.frame_buffer.bytes_per_row =
278 sGraphicsOutput->Mode->Info->PixelsPerScanLine
279 * gKernelArgs.frame_buffer.depth / 8;
280
281 video_display_splash(gKernelArgs.frame_buffer.physical_buffer.start);
282 }
283
284
285 bool
video_mode_hook(Menu * menu,MenuItem * item)286 video_mode_hook(Menu *menu, MenuItem *item)
287 {
288 Menu* submenu = item->Submenu();
289 MenuItem* subitem = submenu->FindMarked();
290 if (subitem != NULL) {
291 sGraphicsMode = (size_t)subitem->Data();
292 sModeChosen = true;
293 }
294
295 return true;
296 }
297
298
299 Menu*
video_mode_menu()300 video_mode_menu()
301 {
302 Menu *menu = new(std::nothrow)Menu(CHOICE_MENU, "Select Video Mode");
303 MenuItem *item;
304
305 video_mode *mode = NULL;
306 while ((mode = (video_mode*)list_get_next_item(&sModeList, mode)) != NULL) {
307 char label[64];
308 snprintf(label, sizeof(label), "%lux%lu %lu bit", mode->width,
309 mode->height, mode->bits_per_pixel);
310
311 menu->AddItem(item = new (std::nothrow)MenuItem(label));
312 item->SetData((const void*)mode->mode);
313 if (mode->mode == sGraphicsMode) {
314 item->SetMarked(true);
315 item->Select(true);
316 }
317 }
318
319 menu->AddSeparatorItem();
320 menu->AddItem(item = new(std::nothrow)MenuItem("Return to main menu"));
321 item->SetType(MENU_ITEM_NO_CHOICE);
322
323 return menu;
324 }
325
326
327 extern "C" void
platform_blit4(addr_t frameBuffer,const uint8 * data,uint16 width,uint16 height,uint16 imageWidth,uint16 left,uint16 top)328 platform_blit4(addr_t frameBuffer, const uint8 *data,
329 uint16 width, uint16 height, uint16 imageWidth,
330 uint16 left, uint16 top)
331 {
332 panic("platform_blit4 unsupported");
333 return;
334 }
335
336
337 extern "C" void
platform_set_palette(const uint8 * palette)338 platform_set_palette(const uint8 *palette)
339 {
340 panic("platform_set_palette unsupported");
341 return;
342 }
343