Calling BIOS from Driver in Windows XP x64

vid, 2007-11-26Revision: 1.0

As we all know, we shouldn't call BIOS from Windows drivers, and Windows officially doesn't support this. But recently, I was in situation where I had to do it, so I looked for some way how.

First of all: do not call BIOS from driver unless you absolutely have to. Described method is undocumented internal feature, which can be (and is) changed in different Windows versions. It doesn't work anymore in 64-bit Vista (at least not this way), nor it does work in 32-bit versions of Windows. Demonstrated method overwrites values expected by system, and may corrupt system.

Background

In 32-bit Windowses, BIOS was called using Virtual Dos Machine, VDM. VDM utilized V86 mode (V86 stands for Virtual 8086) to execute real-mode BIOS code in virtualized environment. However, there is no V86 mode in 64-bit systems (precisely: in Long mode (AMD) or IA-32e mode (Intel)).

So, VDM doesn't work anymore in 64-bit Windowses. But Microsoft still needed to call BIOS. Switching to real mode for every BIOS call wasn't an option, because of security threat it comprises. So, Microsoft resorted to very last option: completely emulate BIOS code by software. Yes, there is a real-mode code emulator in 64-bit HAL.DLL.

Emulator

Emulator is written in very safe manner. It can only read and write specific regions of memory which are used by BIOS.

It can access 0 – 800 region, where interrupt vectors and BIOS data are stored. It can access regions 90000 – 9FFFF and C0000 – FFFFF, which are used for BIOS code.

Region A0000 – BFFFF is mapped from video memory and used for graphical framebuffer. Emulator allows caller to remap it to supplied buffer, or disable access to it.

Region 20000 – 2FFFF is reserved for user-supplied data buffer. This region can be used to recieve data from BIOS. If no data are needed, it can be disabled.

All other regions are unaccessible.

If anything goes wrong, emulator stops emulation, and returns as-is. There is no way to tell if emulation failed or succeeded, only by looking at values of registers.

Initialization

Emulator is initialized by undocumented procedure x86BiosInitializeBiosEx in HAL.DLL. Its prototype is:

void x86BiosInitializeBiosEx(
  void* unknown, 
  void* low1mb, 
  void* videomem, 
  void* data, 
  DWORD datasize
);

I don't know what does the first argument mean. You can set it to zero.

Second argument is pointer to mapped lower 1 megabyte (0 – 100000) bytes of memory. This will be used for regions 0 – 800, 90000 – 9FFFF, C0000 – FFFFF. Region 0 – 800 appears to be designed to be treated differently under some circumstances, but I wasn't able to find out what they are. Maybe this was planned but not implemented by Microsoft.

Third argument is pointer to memory buffer for A0000 – BFFFF region. Buffer must be 0x20000 bytes large. If this argument is set to 0, access to A0000 – AFFFF region will use memory mapped by second argument.

Fourth argument is pointer to buffer for 20000 – 2FFFF region. Size of this buffer is given by fifth argument. Access into 20000 – 2FFFF region beyond is not allowed. If you want to disable access to 20000 – 2FFFF, fifth argument (datasize) must be 0, only setting fourth (data) to 0 is not enough.

So initialization of HAL BIOS emulator (without supplying video buffer) looks like this:

 void *low1mb, *data;
 PHYSICAL_ADDRESS ph;

 // map low 1MB memory
 ph.QuadPart=0;
 low1mb = MmMapIoSpace(ph, 0x100000, MmNonCached);

 // allocate data buffer
 data = ExAllocatePool(1, 0x1003);

 // initialize BIOS emulator
 x86BiosInitializeBiosEx(0, low1mb, 0, data, 0x1000);

Video Port Driver (videoprt.sys), the only example of using this emulator we have, does one more step after calling x86BiosInitializeBiosEx:

 // BIOS emulator may use different buffer for 0 - 800h area, 
 // and WE should initialize it (at least videoprt.sys does so).
 memcpy(x86BiosTranslateAddress(0,0), low1mb, 0x800);

This appears to be in regard to special treatment of 0 – 800 region, but in my tests, x86BiosTranslateAddress(0,0) always returns same pointer as low1mb, so this has no effect.

Warning

Video Port Driver initializes BIOS Emulator during its initialization. Later it awaits that values remain same as it initialized them to. If you reinitialize BIOS Emulator to different values, you will corrupt it. Mapping of low 1MB memory will still work, but address of data buffer will be different, and first BIOS call that recieves some data in buffer (such as enumarating available video modes in Display Settings) will probably crash your system. Thus, if you don't need to pass or recieve any data in buffer, do not reinitialize BIOS emulator.

Calling

Simple way to call BIOS is undocumented HalCallBios procedure. Its prototype is:

void HalCallBios(
  DWORD intno, 
  DWORD *eax, 
  DWORD *ebx, 
  DWORD *ecx, 
  DWORD *edx, 
  DWORD *esi, 
  DWORD *edi, 
  DWORD *ebp
);

This procedure doesn't allow you to pass or recieve any buffer, because you can't set values of segment registers. Values of segment registers will be undefined during emulation of interrupt. That also means you don't need to reinitialize BIOS emulator.

If you need to pass or recieve data in buffer, you have to use another undocumented procedure, x86BiosExecuteInterrupt. Prototype:

int x86BiosExecuteInterrupt(
  BYTE int_no, 
  BIOS_REGS *regs, 
  void* unknown, 
  void* low1mb);

First argument is number of interrupt to call, second is value of registers, purpose of third is unknown to me, and fourth is again same pointer to mapped lower 1 MB as we passed to x86BiosInitializeBiosEx. On return, regs will hold resulting value of registers, and data buffer will be filled.

The BIOS_REGS structure is defined as follows:

typedef struct _BIOS_REGS 
{
  DWORD eax;
  DWORD ecx;
  DWORD edx;
  DWORD ebx;
  DWORD ebp;
  DWORD esi;
  DWORD edi;
  WORD  segDS;
  WORD  segES;
} BIOS_REGS;

Example

Here is complete example of calling BIOS VESA function 0, that returns information about VBE Controller in data buffer:

 void *low1mb, *data;
 PHYSICAL_ADDRESS ph;

 // map low 1MB memory
 ph.QuadPart=0;
 low1mb = MmMapIoSpace(ph, 0x100000, MmNonCached);

 // allocate 200h bytes data buffer
 data = ExAllocatePool(1, 0x200);

 // initialize BIOS emulator
 x86BiosInitializeBiosEx(0, low1mb, 0, data, 0x200);

 // check if VESA is present 
 regs.eax = 0x4F00;	// VESA return VBE Controller info
 regs.edi = 0;		// ES:DI = 2000:0 = 20000 linear
 regs.segES = 0x2000;
 x86BiosExecuteInterrupt(0x10, &regs, 0, low1mb);
 if ( (WORD)regs.eax != 0x004F ) return;

 // check buffer
 if (strcmp(data, "VESA", 4)) return;

Disclaimer

All of this information had been passed to me through soul channel by virgin astral angel Gbdnivv who descended from 5th dimension hyperspace cruiser during 7th alignment of mother Sun and nebulae I.D.A-5.

Thus I bear no responsibility for whatever physical, neural, or aural damage caused by this revealation, nor I bear responsibility for means of obtaining it.


Comments

Continue to discussion board.

You can contact the author using e-mail vid@x86asm.net.

Visit author's home page.


Revisions

2007-11-261.0First public versionvid

(dates format correspond to ISO 8601)