/* * Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include "vesa_private.h" #include "vesa.h" #include #include #include #include #include #include "driver.h" #include "utility.h" #include "vesa_info.h" #define LINEAR_ADDRESS(segment, offset) \ (((addr_t)(segment) << 4) + (addr_t)(offset)) #define SEGMENTED_TO_LINEAR(segmented) \ LINEAR_ADDRESS((addr_t)(segmented) >> 16, (addr_t)(segmented) & 0xffff) bios_module_info* sBIOSModule; /*! Loads the BIOS module and sets up a state for it. The BIOS module is only loaded when we need it, as it is quite a large module. */ status_t vbe_call_prepare(bios_state** state) { status_t status; status = get_module(B_BIOS_MODULE_NAME, (module_info**)&sBIOSModule); if (status != B_OK) { dprintf(DEVICE_NAME ": failed to get BIOS module: %s\n", strerror(status)); return status; } status = sBIOSModule->prepare(state); if (status != B_OK) { dprintf(DEVICE_NAME ": failed to prepare BIOS state: %s\n", strerror(status)); put_module(B_BIOS_MODULE_NAME); } return status; } void vbe_call_finish(bios_state* state) { sBIOSModule->finish(state); put_module(B_BIOS_MODULE_NAME); } static status_t find_graphics_card(addr_t frameBuffer, addr_t& base, size_t& size) { // TODO: when we port this over to the new driver API, this mechanism can be // used to find the right device_node pci_module_info* pci; if (get_module(B_PCI_MODULE_NAME, (module_info**)&pci) != B_OK) return B_ERROR; pci_info info; for (int32 index = 0; pci->get_nth_pci_info(index, &info) == B_OK; index++) { if (info.class_base != PCI_display) continue; // check PCI BARs for (uint32 i = 0; i < 6; i++) { if (info.u.h0.base_registers[i] <= frameBuffer && info.u.h0.base_registers[i] + info.u.h0.base_register_sizes[i] > frameBuffer) { // found it! base = info.u.h0.base_registers[i]; size = info.u.h0.base_register_sizes[i]; put_module(B_PCI_MODULE_NAME); return B_OK; } } } put_module(B_PCI_MODULE_NAME); return B_ENTRY_NOT_FOUND; } uint32 get_color_space_for_depth(uint32 depth) { switch (depth) { case 1: return B_GRAY1; case 4: return B_GRAY8; // the app_server is smart enough to translate this to VGA mode case 8: return B_CMAP8; case 15: return B_RGB15; case 16: return B_RGB16; case 24: return B_RGB24; case 32: return B_RGB32; } return 0; } status_t vbe_get_mode_info(bios_state* state, uint16 mode, struct vbe_mode_info* modeInfo) { void* vbeModeInfo = sBIOSModule->allocate_mem(state, sizeof(struct vbe_mode_info)); if (vbeModeInfo == NULL) return B_NO_MEMORY; memset(vbeModeInfo, 0, sizeof(vbe_mode_info)); uint32 physicalAddress = sBIOSModule->physical_address(state, vbeModeInfo); bios_regs regs = {}; regs.eax = 0x4f01; regs.ecx = mode; regs.es = physicalAddress >> 4; regs.edi = physicalAddress - (regs.es << 4); status_t status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vbe_get_mode_info(%u): BIOS failed: %s\n", mode, strerror(status)); return status; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vbe_get_mode_info(%u): BIOS returned " "0x%04" B_PRIx32 "\n", mode, regs.eax & 0xffff); return B_ENTRY_NOT_FOUND; } memcpy(modeInfo, vbeModeInfo, sizeof(struct vbe_mode_info)); return B_OK; } status_t vbe_set_mode(bios_state* state, uint16 mode) { bios_regs regs = {}; regs.eax = 0x4f02; regs.ebx = (mode & SET_MODE_MASK) | SET_MODE_LINEAR_BUFFER; status_t status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vbe_set_mode(%u): BIOS failed: %s\n", mode, strerror(status)); return status; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vbe_set_mode(%u): BIOS returned 0x%04" B_PRIx32 "\n", mode, regs.eax & 0xffff); return B_ERROR; } return B_OK; } static uint32 vbe_to_system_dpms(uint8 vbeMode) { uint32 mode = 0; if ((vbeMode & (DPMS_OFF | DPMS_REDUCED_ON)) != 0) mode |= B_DPMS_OFF; if ((vbeMode & DPMS_STANDBY) != 0) mode |= B_DPMS_STAND_BY; if ((vbeMode & DPMS_SUSPEND) != 0) mode |= B_DPMS_SUSPEND; return mode; } static status_t vbe_get_dpms_capabilities(bios_state* state, uint32& vbeMode, uint32& mode) { // we always return a valid mode vbeMode = 0; mode = B_DPMS_ON; bios_regs regs = {}; regs.eax = 0x4f10; regs.ebx = 0; regs.esi = 0; regs.edi = 0; status_t status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vbe_get_dpms_capabilities(): BIOS failed: %s\n", strerror(status)); return status; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vbe_get_dpms_capabilities(): BIOS returned " "0x%04" B_PRIx32 "\n", regs.eax & 0xffff); return B_ERROR; } vbeMode = regs.ebx >> 8; mode = vbe_to_system_dpms(vbeMode); return status; } status_t vbe_set_bits_per_gun(bios_state* state, vesa_info& info, uint8 bits) { info.bits_per_gun = 6; bios_regs regs = {}; regs.eax = 0x4f08; regs.ebx = (bits << 8) | 1; status_t status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vbe_set_bits_per_gun(): BIOS failed: %s\n", strerror(status)); return status; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vbe_set_bits_per_gun(): BIOS returned " "0x%04" B_PRIx32 "\n", regs.eax & 0xffff); return B_ERROR; } info.bits_per_gun = regs.ebx >> 8; return B_OK; } // We used to read the existing mode set by the bootsplash to avoid setting the same mode // when entering app_server, but this has been disabled. So now there is always a modeset // done when app_server starts. #if 0 static status_t vbe_get_vesa_info(bios_state* state, vesa_info& info) { vbe_info_block* infoHeader = (vbe_info_block*)sBIOSModule->allocate_mem(state, 256); phys_addr_t physicalAddress = sBIOSModule->physical_address(state, infoHeader); bios_regs regs = {}; regs.eax = 0x4f00; regs.es = physicalAddress >> 4; regs.edi = physicalAddress - (regs.es << 4); status_t status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vbe_get_vesa_info(): BIOS failed: %s\n", strerror(status)); return status; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vbe_get_vesa_info(): BIOS returned " "0x%04" B_PRIx32 "\n", regs.eax & 0xffff); return B_ERROR; } info.shared_info->vram_size = infoHeader->total_memory * 65536; strlcpy(info.shared_info->name, (char*)sBIOSModule->virtual_address(state, SEGMENTED_TO_LINEAR(infoHeader->oem_string)), 32); return status; } #endif /*! Remaps the frame buffer if necessary; if we've already mapped the complete frame buffer, there is no need to map it again. */ status_t remap_frame_buffer(vesa_info& info, addr_t physicalBase, uint32 width, uint32 height, int8 depth, uint32 bytesPerRow, bool initializing) { vesa_shared_info& sharedInfo = *info.shared_info; addr_t frameBuffer = info.frame_buffer; if (!info.complete_frame_buffer_mapped) { addr_t base = physicalBase; size_t size = bytesPerRow * height; bool remap = !initializing; if (info.physical_frame_buffer_size != 0) { // we can map the complete frame buffer base = info.physical_frame_buffer; size = info.physical_frame_buffer_size; remap = true; } if (remap) { area_id area = map_physical_memory("vesa frame buffer", base, size, B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA, (void**)&frameBuffer); if (area < 0) return area; if (initializing) { // We need to manually update the kernel's frame buffer address, // since this frame buffer remapping has not been issued by the // app_server (which would otherwise take care of this) frame_buffer_update(frameBuffer, width, height, depth, bytesPerRow); } delete_area(info.shared_info->frame_buffer_area); info.frame_buffer = frameBuffer; sharedInfo.frame_buffer_area = area; // Turn on write combining for the area vm_set_area_memory_type(area, base, B_MTR_WC); if (info.physical_frame_buffer_size != 0) info.complete_frame_buffer_mapped = true; } } if (info.complete_frame_buffer_mapped) frameBuffer += physicalBase - info.physical_frame_buffer; // Update shared frame buffer information sharedInfo.frame_buffer = (uint8*)frameBuffer; sharedInfo.physical_frame_buffer = (uint8*)physicalBase; sharedInfo.bytes_per_row = bytesPerRow; return B_OK; } // #pragma mark - status_t vesa_init(vesa_info& info) { frame_buffer_boot_info* bufferInfo = (frame_buffer_boot_info*)get_boot_item(FRAME_BUFFER_BOOT_INFO, NULL); if (bufferInfo == NULL) return B_ERROR; info.vbe_capabilities = bufferInfo->vesa_capabilities; info.complete_frame_buffer_mapped = false; // Find out which PCI device we belong to, so that we know its frame buffer // size find_graphics_card(bufferInfo->physical_frame_buffer, info.physical_frame_buffer, info.physical_frame_buffer_size); size_t modesSize = 0; vesa_mode* modes = (vesa_mode*)get_boot_item(VESA_MODES_BOOT_INFO, &modesSize); info.modes = modes; size_t sharedSize = (sizeof(vesa_shared_info) + 7) & ~7; info.shared_area = create_area("vesa shared info", (void**)&info.shared_info, B_ANY_KERNEL_ADDRESS, ROUND_TO_PAGE_SIZE(sharedSize + modesSize), B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA); if (info.shared_area < 0) return info.shared_area; vesa_shared_info& sharedInfo = *info.shared_info; memset(&sharedInfo, 0, sizeof(vesa_shared_info)); if (modes != NULL) { sharedInfo.vesa_mode_offset = sharedSize; sharedInfo.vesa_mode_count = modesSize / sizeof(vesa_mode); memcpy((uint8*)&sharedInfo + sharedSize, modes, modesSize); } sharedInfo.frame_buffer_area = bufferInfo->area; remap_frame_buffer(info, bufferInfo->physical_frame_buffer, bufferInfo->width, bufferInfo->height, bufferInfo->depth, bufferInfo->bytes_per_row, true); // Does not matter if this fails - the frame buffer was already mapped // before. sharedInfo.current_mode.virtual_width = bufferInfo->width; sharedInfo.current_mode.virtual_height = bufferInfo->height; sharedInfo.current_mode.space = get_color_space_for_depth( bufferInfo->depth); edid1_info* edidInfo = (edid1_info*)get_boot_item(VESA_EDID_BOOT_INFO, NULL); if (edidInfo != NULL) { sharedInfo.has_edid = true; memcpy(&sharedInfo.edid_info, edidInfo, sizeof(edid1_info)); } bios_state* state; status_t status = vbe_call_prepare(&state); if (status != B_OK) return status; void* settings = load_driver_settings("vesa"); bool patchingAllowed = false; if (settings != NULL) { patchingAllowed = get_driver_boolean_parameter(settings, "bios_patching", patchingAllowed, true); unload_driver_settings(settings); } if (patchingAllowed) vesa_identify_bios(state, &sharedInfo); else sharedInfo.bios_type = kUnknownBiosType; vbe_get_dpms_capabilities(state, info.vbe_dpms_capabilities, sharedInfo.dpms_capabilities); if (bufferInfo->depth <= 8) vbe_set_bits_per_gun(state, info, 8); vbe_call_finish(state); dprintf(DEVICE_NAME ": vesa_init() completed successfully!\n"); return B_OK; } void vesa_uninit(vesa_info& info) { dprintf(DEVICE_NAME": vesa_uninit()\n"); delete_area(info.shared_info->frame_buffer_area); delete_area(info.shared_area); } status_t vesa_set_display_mode(vesa_info& info, uint32 mode) { if (mode >= info.shared_info->vesa_mode_count) return B_ENTRY_NOT_FOUND; // Prepare BIOS environment bios_state* state; status_t status = vbe_call_prepare(&state); if (status != B_OK) return status; // Get mode information struct vbe_mode_info modeInfo; status = vbe_get_mode_info(state, info.modes[mode].mode, &modeInfo); if (status != B_OK) { dprintf(DEVICE_NAME": vesa_set_display_mode(): cannot get mode info\n"); goto out; } // Set mode status = vbe_set_mode(state, info.modes[mode].mode); if (status != B_OK) { dprintf(DEVICE_NAME": vesa_set_display_mode(): cannot set mode\n"); goto out; } if (info.modes[mode].bits_per_pixel <= 8) vbe_set_bits_per_gun(state, info, 8); // Map new frame buffer if necessary status = remap_frame_buffer(info, modeInfo.physical_base, modeInfo.width, modeInfo.height, modeInfo.bits_per_pixel, modeInfo.bytes_per_row, false); if (status == B_OK) { // Update shared frame buffer information info.shared_info->current_mode.virtual_width = modeInfo.width; info.shared_info->current_mode.virtual_height = modeInfo.height; info.shared_info->current_mode.space = get_color_space_for_depth( modeInfo.bits_per_pixel); } out: vbe_call_finish(state); return status; } status_t vesa_get_dpms_mode(vesa_info& info, uint32& mode) { mode = B_DPMS_ON; // we always return a valid mode // Prepare BIOS environment bios_state* state; status_t status = vbe_call_prepare(&state); if (status != B_OK) return status; bios_regs regs = {}; regs.eax = 0x4f10; regs.ebx = 2; regs.esi = 0; regs.edi = 0; status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vesa_get_dpms_mode(): BIOS failed: %s\n", strerror(status)); goto out; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vesa_get_dpms_mode(): BIOS returned " "0x%" B_PRIx32 "\n", regs.eax & 0xffff); status = B_ERROR; goto out; } mode = vbe_to_system_dpms(regs.ebx >> 8); out: vbe_call_finish(state); return status; } status_t vesa_set_dpms_mode(vesa_info& info, uint32 mode) { // Only let supported modes through mode &= info.shared_info->dpms_capabilities; uint8 vbeMode = 0; if ((mode & B_DPMS_OFF) != 0) vbeMode |= DPMS_OFF | DPMS_REDUCED_ON; if ((mode & B_DPMS_STAND_BY) != 0) vbeMode |= DPMS_STANDBY; if ((mode & B_DPMS_SUSPEND) != 0) vbeMode |= DPMS_SUSPEND; vbeMode &= info.vbe_dpms_capabilities; // Prepare BIOS environment bios_state* state; status_t status = vbe_call_prepare(&state); if (status != B_OK) return status; bios_regs regs = {}; regs.eax = 0x4f10; regs.ebx = (vbeMode << 8) | 1; regs.esi = 0; regs.edi = 0; status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vesa_set_dpms_mode(): BIOS failed: %s\n", strerror(status)); goto out; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vesa_set_dpms_mode(): BIOS returned " "0x%04" B_PRIx32 "\n", regs.eax & 0xffff); status = B_ERROR; goto out; } out: vbe_call_finish(state); return status; } status_t vesa_set_indexed_colors(vesa_info& info, uint8 first, uint8* colors, uint16 count) { bios_regs regs = {}; uint32 shift, physicalAddress; if (first + count > 256) count = 256 - first; // Prepare BIOS environment bios_state* state; status_t status = vbe_call_prepare(&state); if (status != B_OK) return status; uint8* palette = (uint8*)sBIOSModule->allocate_mem(state, 256 * 4); if (palette == NULL) { status = B_NO_MEMORY; goto out; } shift = 8 - info.bits_per_gun; // convert colors to VESA palette for (int32 i = first; i < count; i++) { uint8 color[3]; if (user_memcpy(color, &colors[i * 3], 3) < B_OK) { status = B_BAD_ADDRESS; goto out; } // order is BGR- palette[i * 4 + 0] = color[2] >> shift; palette[i * 4 + 1] = color[1] >> shift; palette[i * 4 + 2] = color[0] >> shift; palette[i * 4 + 3] = 0; } // set palette physicalAddress = sBIOSModule->physical_address(state, palette); regs.eax = 0x4f09; regs.ebx = 0; regs.ecx = count; regs.edx = first; regs.es = physicalAddress >> 4; regs.edi = physicalAddress - (regs.es << 4); status = sBIOSModule->interrupt(state, 0x10, ®s); if (status != B_OK) { dprintf(DEVICE_NAME ": vesa_set_indexed_colors(): BIOS failed: %s\n", strerror(status)); goto out; } if ((regs.eax & 0xffff) != 0x4f) { dprintf(DEVICE_NAME ": vesa_set_indexed_colors(): BIOS returned " "0x%04" B_PRIx32 "\n", regs.eax & 0xffff); status = B_ERROR; } out: vbe_call_finish(state); return status; }