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 ¶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 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