vesa: live BIOS patching for Intel video devices
The VESA standard does not define any way for software to set a custom video mode, which means normally we would be constrained to whichever modes the video card manufacturer decided to provide. However, since we run the BIOS in an emulated environment, it is possible (and even quite easy) to patch it and inject any video mode we want, provided we know the format to use and where to put the info in. This approach was used in the NewOS VESA driver, as well as in 915resolution (a tool that predates the availability of native drivers for Linux for Intel videocards). Later on it was also used in Chameleon and Clover, bootloaders that are used for hackintoshes (running MacOS on unsupported hardware). This commit implements full support for Intel cards only, AMD and NVidia will be added later (but there is preliminary code to detect them) Change-Id: I2c528ba18b3863f486da694860a10761efcbfb3f Reviewed-on: https://review.haiku-os.org/c/haiku/+/4624 Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org> Reviewed-by: waddlesplash <waddlesplash@gmail.com>
This commit is contained in:
parent
6804f6c764
commit
1005a27603
@ -23,8 +23,15 @@ struct vesa_mode {
|
||||
uint8 bits_per_pixel;
|
||||
};
|
||||
|
||||
enum bios_type_enum {
|
||||
kUnknownBiosType = 0,
|
||||
kIntelBiosType,
|
||||
kNVidiaBiosType,
|
||||
kAtomBiosType1,
|
||||
kAtomBiosType2
|
||||
};
|
||||
|
||||
struct vesa_shared_info {
|
||||
int32 type;
|
||||
area_id mode_list_area; // area containing display mode list
|
||||
uint32 mode_count;
|
||||
display_mode current_mode;
|
||||
@ -40,6 +47,10 @@ struct vesa_shared_info {
|
||||
|
||||
edid1_info edid_info;
|
||||
bool has_edid;
|
||||
bios_type_enum bios_type;
|
||||
uint16 mode_table_offset;
|
||||
// Atombios only: offset to the table of video modes in the bios, used for patching in
|
||||
// extra video modes.
|
||||
uint32 dpms_capabilities;
|
||||
};
|
||||
|
||||
@ -53,6 +64,7 @@ enum {
|
||||
VESA_GET_DPMS_MODE,
|
||||
VESA_SET_DPMS_MODE,
|
||||
VESA_SET_INDEXED_COLORS,
|
||||
VESA_SET_CUSTOM_DISPLAY_MODE,
|
||||
|
||||
VGA_PLANAR_BLIT,
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ typedef struct accelerant_info {
|
||||
area_id mode_list_area;
|
||||
// cloned list of standard display modes
|
||||
display_mode *mode_list;
|
||||
uint16 current_mode;
|
||||
int16 current_mode; // index in the mode_list, or -1 if using a custom mode
|
||||
|
||||
vesa_mode *vesa_modes;
|
||||
} accelerant_info;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "accelerant_protos.h"
|
||||
#include "accelerant.h"
|
||||
#include "utility.h"
|
||||
#include "vesa_info.h"
|
||||
|
||||
|
||||
//#define TRACE_MODE
|
||||
@ -53,6 +54,11 @@ is_mode_supported(display_mode* mode)
|
||||
{
|
||||
vesa_mode* modes = gInfo->vesa_modes;
|
||||
|
||||
if (gInfo->shared_info->bios_type == kIntelBiosType) {
|
||||
// We know how to patch the BIOS, so we can set any mode we want
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32 i = gInfo->shared_info->vesa_mode_count; i-- > 0;) {
|
||||
// search mode in VESA mode list
|
||||
// TODO: list is ordered, we could use binary search
|
||||
@ -136,7 +142,8 @@ vesa_propose_display_mode(display_mode* target, const display_mode* low,
|
||||
{
|
||||
TRACE(("vesa_propose_display_mode()\n"));
|
||||
|
||||
// just search for the specified mode in the list
|
||||
// Search for the specified mode in the list. If it's in there, we don't need a custom mode and
|
||||
// we just normalize it to the info provided by the VESA BIOS.
|
||||
|
||||
for (uint32 i = 0; i < gInfo->shared_info->mode_count; i++) {
|
||||
display_mode* current = &gInfo->mode_list[i];
|
||||
@ -149,6 +156,17 @@ vesa_propose_display_mode(display_mode* target, const display_mode* low,
|
||||
*target = *current;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
if (gInfo->shared_info->bios_type == kIntelBiosType) {
|
||||
// The driver says it knows the BIOS type, and therefore how to patch it to apply custom
|
||||
// modes. However, it only knows how to do so for 32bit modes.
|
||||
// TODO: for nVidia there is actually an hardcoded list of possible modes (because no one
|
||||
// figured out how to generate the required values for an arbitrary mode), so we should
|
||||
// check against that.
|
||||
if (target->space == B_RGB32)
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
return B_BAD_VALUE;
|
||||
}
|
||||
|
||||
@ -163,7 +181,7 @@ vesa_set_display_mode(display_mode* _mode)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
vesa_mode* modes = gInfo->vesa_modes;
|
||||
for (uint32 i = gInfo->shared_info->vesa_mode_count; i-- > 0;) {
|
||||
for (int32 i = gInfo->shared_info->vesa_mode_count; i-- > 0;) {
|
||||
// search mode in VESA mode list
|
||||
// TODO: list is ordered, we could use binary search
|
||||
if (modes[i].width == mode.virtual_width
|
||||
@ -179,7 +197,14 @@ vesa_set_display_mode(display_mode* _mode)
|
||||
}
|
||||
}
|
||||
|
||||
return B_UNSUPPORTED;
|
||||
// If the mode is not found in the list of standard mode, live patch the BIOS to get it anyway
|
||||
status_t result = ioctl(gInfo->device, VESA_SET_CUSTOM_DISPLAY_MODE,
|
||||
&mode, sizeof(display_mode));
|
||||
if (result == B_OK) {
|
||||
gInfo->current_mode = -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
SubDir HAIKU_TOP src add-ons kernel drivers graphics vesa ;
|
||||
|
||||
UsePrivateHeaders [ FDirName graphics common ] ;
|
||||
UsePrivateHeaders [ FDirName graphics radeon_hd atombios ] ;
|
||||
UsePrivateHeaders [ FDirName graphics vesa ] ;
|
||||
UseHeaders [ FDirName $(HAIKU_TOP) headers compatibility gnu ]
|
||||
[ FDirName $(HAIKU_TOP) headers posix ] : true ;
|
||||
UsePrivateKernelHeaders ;
|
||||
|
||||
KernelAddon vesa :
|
||||
|
@ -137,6 +137,18 @@ device_ioctl(void* cookie, uint32 msg, void* buffer, size_t bufferLength)
|
||||
return vesa_set_display_mode(*info, mode);
|
||||
}
|
||||
|
||||
case VESA_SET_CUSTOM_DISPLAY_MODE:
|
||||
{
|
||||
if (bufferLength != sizeof(display_mode))
|
||||
return B_BAD_VALUE;
|
||||
|
||||
display_mode mode;
|
||||
if (user_memcpy(&mode, buffer, sizeof(display_mode)) != B_OK)
|
||||
return B_BAD_ADDRESS;
|
||||
|
||||
return vesa_set_custom_display_mode(*info, mode);
|
||||
}
|
||||
|
||||
case VESA_GET_DPMS_MODE:
|
||||
{
|
||||
if (bufferLength != sizeof(uint32))
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "vesa_private.h"
|
||||
#include "vesa.h"
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <string.h>
|
||||
|
||||
#include <drivers/bios.h>
|
||||
@ -20,6 +21,8 @@
|
||||
#include "utility.h"
|
||||
#include "vesa_info.h"
|
||||
|
||||
#include "atombios.h"
|
||||
|
||||
|
||||
static bios_module_info* sBIOSModule;
|
||||
|
||||
@ -276,6 +279,65 @@ vbe_set_bits_per_gun(vesa_info& info, uint8 bits)
|
||||
}
|
||||
|
||||
|
||||
/*! Identify the BIOS type if it's one of the common ones, and locate where the BIOS store its
|
||||
* allowed video modes table. We can then patch this table to add extra video modes that the
|
||||
* manufacturer didn't allow.
|
||||
*/
|
||||
static void
|
||||
vbe_identify_bios(vesa_shared_info* sharedInfo)
|
||||
{
|
||||
// TODO vbe_call_prepare/vbe_call_finish is a costly operation (loading and unloading the
|
||||
// BIOS module all the time). We should try to do it less often.
|
||||
bios_state* state;
|
||||
status_t status = vbe_call_prepare(&state);
|
||||
if (status != B_OK)
|
||||
return;
|
||||
|
||||
// Get a pointer to the BIOS
|
||||
const uintptr_t kBiosBase = 0xc0000;
|
||||
uint8_t* bios = (uint8_t*)sBIOSModule->virtual_address(state, kBiosBase);
|
||||
|
||||
const size_t kAtomBiosHeaderOffset = 0x48;
|
||||
const char kAtomSignature[] = {'A', 'T', 'O', 'M'};
|
||||
|
||||
ATOM_ROM_HEADER* atomRomHeader = (ATOM_ROM_HEADER*)(bios + kAtomBiosHeaderOffset);
|
||||
|
||||
sharedInfo->bios_type = kUnknownBiosType;
|
||||
|
||||
if (*(uint16*)(bios + 0x44) == 0x8086) {
|
||||
dprintf(DEVICE_NAME ": detected Intel BIOS\n");
|
||||
|
||||
// TODO check if we can find the mode table
|
||||
sharedInfo->bios_type = kIntelBiosType;
|
||||
} else if (memcmp(atomRomHeader->uaFirmWareSignature, kAtomSignature, 4) == 0) {
|
||||
dprintf(DEVICE_NAME ": detected ATOM BIOS\n");
|
||||
|
||||
ATOM_MASTER_DATA_TABLE* masterDataTable = (ATOM_MASTER_DATA_TABLE*)(bios
|
||||
+ atomRomHeader->usMasterDataTableOffset);
|
||||
dprintf(DEVICE_NAME ": list of data tables: %p", &masterDataTable->ListOfDataTables);
|
||||
ATOM_ANALOG_TV_INFO* standardVesaTable = (ATOM_ANALOG_TV_INFO*)(bios
|
||||
+ masterDataTable->ListOfDataTables.StandardVESA_Timing);
|
||||
dprintf(DEVICE_NAME ": std_vesa: %p", standardVesaTable);
|
||||
if (standardVesaTable->aModeTimings == NULL) {
|
||||
dprintf(DEVICE_NAME ": unable to locate the mode table\n");
|
||||
} else {
|
||||
sharedInfo->bios_type = kAtomBiosType1;
|
||||
// TODO detect kAtomBiosType2
|
||||
// TODO set sharedInfo->mode_table_offset
|
||||
}
|
||||
} else if (memmem(bios, 512, "NVID", 4) != NULL) {
|
||||
dprintf(DEVICE_NAME ": detected nVidia BIOS\n");
|
||||
|
||||
// TODO check if we can find the mode table
|
||||
sharedInfo->bios_type = kNVidiaBiosType;
|
||||
} else {
|
||||
dprintf(DEVICE_NAME ": unknown BIOS type, custom video modes will not be available\n");
|
||||
}
|
||||
|
||||
vbe_call_finish(state);
|
||||
}
|
||||
|
||||
|
||||
/*! Remaps the frame buffer if necessary; if we've already mapped the complete
|
||||
frame buffer, there is no need to map it again.
|
||||
*/
|
||||
@ -402,6 +464,8 @@ vesa_init(vesa_info& info)
|
||||
memcpy(&sharedInfo.edid_info, edidInfo, sizeof(edid1_info));
|
||||
}
|
||||
|
||||
vbe_identify_bios(&sharedInfo);
|
||||
|
||||
vbe_get_dpms_capabilities(info.vbe_dpms_capabilities,
|
||||
sharedInfo.dpms_capabilities);
|
||||
if (bufferInfo->depth <= 8)
|
||||
@ -471,6 +535,171 @@ out:
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
vbe_patch_intel_bios(bios_state* state, display_mode& mode)
|
||||
{
|
||||
edid1_detailed_timing_raw timing;
|
||||
|
||||
timing.pixel_clock = mode.timing.pixel_clock / 10;
|
||||
|
||||
timing.h_active = mode.timing.h_display & 0xFF;
|
||||
timing.h_active_high = (mode.timing.h_display >> 8) & 0xF;
|
||||
|
||||
uint16 h_blank = mode.timing.h_total - mode.timing.h_display;
|
||||
timing.h_blank = h_blank & 0xff;
|
||||
timing.h_blank_high = (h_blank >> 8) & 0xF;
|
||||
|
||||
timing.v_active = mode.timing.v_display & 0xFF;
|
||||
timing.v_active_high = (mode.timing.v_display >> 8) & 0xF;
|
||||
|
||||
uint16 v_blank = mode.timing.v_total - mode.timing.v_display;
|
||||
timing.v_blank = v_blank & 0xff;
|
||||
timing.v_blank_high = (v_blank >> 8) & 0xF;
|
||||
|
||||
uint16 h_sync_off = mode.timing.h_sync_start - mode.timing.h_display;
|
||||
timing.h_sync_off = h_sync_off & 0xFF;
|
||||
timing.h_sync_off_high = (h_sync_off >> 8) & 0x3;
|
||||
|
||||
uint16 h_sync_width = mode.timing.h_sync_end - mode.timing.h_sync_start;
|
||||
timing.h_sync_width = h_sync_width & 0xFF;
|
||||
timing.h_sync_width_high = (h_sync_width >> 8) & 0x3;
|
||||
|
||||
uint16 v_sync_off = mode.timing.v_sync_start - mode.timing.v_display;
|
||||
timing.v_sync_off = v_sync_off & 0xF;
|
||||
timing.v_sync_off_high = (v_sync_off >> 4) & 0x3;
|
||||
|
||||
uint16 v_sync_width = mode.timing.v_sync_end - mode.timing.v_sync_start;
|
||||
timing.v_sync_width = v_sync_width & 0xF;
|
||||
timing.v_sync_width_high = (v_sync_width >> 4) & 0x3;
|
||||
|
||||
timing.h_size = 0;
|
||||
timing.v_size = 0;
|
||||
timing.h_size_high = 0;
|
||||
timing.v_size_high = 0;
|
||||
timing.h_border = 0;
|
||||
timing.v_border = 0;
|
||||
timing.interlaced = 0;
|
||||
timing.stereo = 0;
|
||||
timing.sync = 3;
|
||||
timing.misc = 0;
|
||||
timing.stereo_il = 0;
|
||||
|
||||
static const uint8 knownMode[] = { 0x64, 0x19, 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x18,
|
||||
0x88, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18};
|
||||
// This is the EDID description for a standard 1024x768 timing. We will find and replace
|
||||
// all occurences of it in the BIOS with our custom mode.
|
||||
|
||||
// Get a pointer to the BIOS
|
||||
const uintptr_t kBiosBase = 0xc0000;
|
||||
const size_t kBiosSize = 0x10000;
|
||||
uint8_t* bios = (uint8_t*)sBIOSModule->virtual_address(state, kBiosBase);
|
||||
uint8_t* biosEnd = bios + kBiosSize;
|
||||
|
||||
int replacementCount = 0;
|
||||
while (bios < biosEnd) {
|
||||
bios = (uint8_t*)memmem(bios, biosEnd - bios, knownMode, sizeof(knownMode));
|
||||
if (bios == NULL)
|
||||
break;
|
||||
memcpy(bios, &timing, sizeof(timing));
|
||||
bios++; // Skip one byte so the next memmem finds another occurence
|
||||
replacementCount++;
|
||||
}
|
||||
|
||||
dprintf(DEVICE_NAME ": patched custom mode in %d locations\n", replacementCount);
|
||||
|
||||
// Did we manage to find a mode descriptor to replace?
|
||||
if (replacementCount == 0)
|
||||
return B_NOT_SUPPORTED;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
vesa_set_custom_display_mode(vesa_info& info, display_mode& mode)
|
||||
{
|
||||
if (info.shared_info->bios_type == kUnknownBiosType)
|
||||
return B_NOT_SUPPORTED;
|
||||
|
||||
// Prepare BIOS environment
|
||||
bios_state* state;
|
||||
status_t status = vbe_call_prepare(&state);
|
||||
if (status != B_OK)
|
||||
return status;
|
||||
|
||||
int32 modeIndex; // Index of the mode that will be patched
|
||||
|
||||
// Patch bios to inject custom video mode
|
||||
switch (info.shared_info->bios_type) {
|
||||
case kIntelBiosType:
|
||||
status = vbe_patch_intel_bios(state, mode);
|
||||
// The patch replaces the 1024x768 modes with our custom resolution. We can then use
|
||||
// mode 0x118 which is the standard VBE2 mode for 1024x768 at 32 bits per pixel, and
|
||||
// know it will use our new timings.
|
||||
// TODO use modes 105 (256 colors), 116 (15 bit) and 117 (16 bit) depending on the
|
||||
// requested colorspace. They should work too.
|
||||
// TODO ideally locate the 1024x768 modes from the mode list in info.modes[], instead
|
||||
// of hardcoding its number, in case the BIOS does not use VBE2 standard numbering.
|
||||
modeIndex = 0x118;
|
||||
break;
|
||||
#if 0
|
||||
case kNVidiaBiosType:
|
||||
status = vbe_patch_nvidia_bios(state, mode);
|
||||
break;
|
||||
case kAtomBiosType1:
|
||||
status = vbe_patch_atom1_bios(state, mode);
|
||||
break;
|
||||
case kAtomBiosType2:
|
||||
status = vbe_patch_atom2_bios(state, mode);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
status = B_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status != B_OK)
|
||||
goto out;
|
||||
|
||||
// Get mode information
|
||||
struct vbe_mode_info modeInfo;
|
||||
status = vbe_get_mode_info(state, modeIndex, &modeInfo);
|
||||
if (status != B_OK) {
|
||||
dprintf(DEVICE_NAME ": vesa_set_custom_display_mode(): cannot get mode info\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
dprintf(DEVICE_NAME ": custom mode resolution %dx%d\n", modeInfo.width, modeInfo.height);
|
||||
|
||||
// Set mode
|
||||
status = vbe_set_mode(state, modeIndex);
|
||||
if (status != B_OK) {
|
||||
dprintf(DEVICE_NAME ": vesa_set_custom_display_mode(): cannot set mode\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info.modes[modeIndex].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)
|
||||
{
|
||||
|
@ -39,6 +39,7 @@ struct vesa_info {
|
||||
extern status_t vesa_init(vesa_info& info);
|
||||
extern void vesa_uninit(vesa_info& info);
|
||||
extern status_t vesa_set_display_mode(vesa_info& info, uint32 mode);
|
||||
extern status_t vesa_set_custom_display_mode(vesa_info& info, display_mode& mode);
|
||||
extern status_t vesa_get_dpms_mode(vesa_info& info, uint32& mode);
|
||||
extern status_t vesa_set_dpms_mode(vesa_info& info, uint32 mode);
|
||||
extern status_t vesa_set_indexed_colors(vesa_info& info, uint8 first,
|
||||
|
Loading…
Reference in New Issue
Block a user