Problem with VESA 3.0 in Protect Mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
pherosiden
Posts: 3
Joined: Wed Jun 20, 2018 2:28 am

Problem with VESA 3.0 in Protect Mode

Post by pherosiden »

Hello World!

I'm try to call VESA 3.0 function from BIOS in protected mode but crash with general protection fault. I followed VESA 3.0 specf but no details information how to do this.
I use OpenWatcom C++ 2.0 and MS-DOS 32 bits environment.
and here is my code...

Code: Select all

// VESA 3.0 protected mode info block
typedef struct
{
	unsigned char	Signature[4];			// PM Info Block signature ('PMID')
	unsigned short	EntryPoint;			// offset of PM entry point within BIOS
	unsigned short	PMInitialize;			// offset of PM initialization entry point
	unsigned short	BIOSDataSel;			// selector to BIOS data area emulation block
	unsigned short	A0000Sel;				// selector to 0xa0000
	unsigned short	B0000Sel;				// selector to 0xb0000
	unsigned short	B8000Sel;				// selector to 0xb8000
	unsigned short	CodeSegSel;			// selector to access code segment as data
	unsigned char	InProtectMode;			// true if in protected mode
	unsigned char	Checksum;			// sum of all bytes in this struct must match 0
} VBE_PM_INFO_BLOCK;

// VESA 3.0 far call memory struct (48 bits address)
typedef struct
{
	unsigned int	offset;				// 32 bits offset
	unsigned short	segment;				// 16 bits segment
} VBE_FAR_CALL;

// switch to 16 bit protect mode stack
void StackSwitch(void *mesp, void *mss)
{
	_asm {
		mov    eax, dr3
		mov    edx, mesp
		pushf
		pop    ecx
		add    eax, stackOffset
		cli
		push   ss
		cmp    [esp], 0x10
		je     kernelStack
		pop    eax
		jmp    switchStack
	kernelStack:
		pop    [eax + 4]
		mov    esp, [eax]
	switchStack:
		lss		esp, mss
		push	ecx
		popf
		jmp		fword ptr [edx]
	}
}

int main(int argc, char const *argv[])
{
	VBE_FAR_CALL		fcall;
	VBE_DRIVER_INFO		drvInfo;
	VBE_PM_INFO_BLOCK	*pmInfo;

	unsigned int val = 0;
	unsigned int i = 0;
	unsigned char biosCheckSum = 0;

	unsigned char *biosCode;
	unsigned char *biosData;
	unsigned char *biosStack;
	unsigned char *biosPtr;

	unsigned short biosDataSel;
	unsigned short biosCodeSel;
	unsigned short a0000Sel;
	unsigned short b0000Sel;
	unsigned short b8000Sel;

	unsigned short biosInitSel;
	unsigned short biosStackSel;
	
	unsigned short vbeInfoSel;

	// copy BIOS code from physical address 0xC0000 to RAM
	biosCode = (unsigned char*)malloc(VBE_CODE_SIZE);
	if (!biosCode) return 1;
	memcpy(biosCode, (unsigned char*)0xC0000, VBE_CODE_SIZE);

	// find VESA 3.0 protected mode info block signature
	biosPtr = biosCode;
	while ((biosPtr <= biosCode + VBE_CODE_SIZE - sizeof(VBE_PM_INFO_BLOCK)) && memcmp(((VBE_PM_INFO_BLOCK*)biosPtr)->Signature, "PMID", 4)) biosPtr++;

	// check for correct signature
	pmInfo = (VBE_PM_INFO_BLOCK*)biosPtr;
	if (memcmp(pmInfo->Signature, "PMID", 4))
	{
		printf("VESA PMID not found!\n");
		return 1;
	}

	// calculate BIOS checksum
	for (i = 0; i != sizeof(VBE_PM_INFO_BLOCK); i++) biosCheckSum += *biosPtr++;
	if (biosCheckSum)
	{
		printf("VESA BIOS checksum error!\n");
		return 1;
	}

	// setup structure (provide selectors, map video mem, ...)
	biosData = (unsigned char *)malloc(VBE_DATA_SIZE);
	if (!biosData) return 1;
	memset(biosData, 0, VBE_DATA_SIZE);

	// setup BIOS data selector
	biosDataSel = AllocSelector();
	if (biosDataSel == 0 || biosDataSel == 0xFFFF) return 1;
	if (!SetSelectorRights(biosDataSel, 0x8092)) return 1;
	if (!SetSelectorBase(biosDataSel, (unsigned int)biosData)) return 1;
	if (!SetSelectorLimit(biosDataSel, VBE_DATA_SIZE - 1)) return 1;
	pmInfo->BIOSDataSel = biosDataSel;

	// map video memory
	a0000Sel = AllocSelector();
	if (a0000Sel == 0 || a0000Sel == 0xFFFF) return 1;
	if (!SetSelectorRights(a0000Sel, 0x8092)) return 1;
	if (!SetSelectorBase(a0000Sel, (unsigned int)0xA0000)) return 1;
	if (!SetSelectorLimit(a0000Sel, 0xFFFF)) return 1;
	pmInfo->A0000Sel = a0000Sel;

	b0000Sel = AllocSelector();
	if (b0000Sel == 0 || b0000Sel == 0xFFFF) return 1;
	if (!SetSelectorRights(b0000Sel, 0x8092)) return 1;
	if (!SetSelectorBase(b0000Sel, (unsigned int)0xB0000)) return 1;
	if (!SetSelectorLimit(b0000Sel, 0xFFFF)) return 1;
	pmInfo->B0000Sel = b0000Sel;

	b8000Sel = AllocSelector();
	if (b8000Sel == 0 || b8000Sel == 0xFFFF) return 1;
	if (!SetSelectorRights(b8000Sel, 0x8092)) return 1;
	if (!SetSelectorBase(b8000Sel, (unsigned int)0xB8000)) return 1;
	if (!SetSelectorLimit(b8000Sel, 0x7FFF)) return 1;
	pmInfo->B8000Sel = b8000Sel;

	// setup BIOS code selector
	biosCodeSel = AllocSelector();
	if (biosCodeSel == 0 || biosCodeSel == 0xFFFF) return 1;
	if (!SetSelectorRights(biosCodeSel, 0x8092)) return 1;
	if (!SetSelectorBase(biosCodeSel, (unsigned int)biosCode)) return 1;
	if (!SetSelectorLimit(biosCodeSel, VBE_CODE_SIZE - 1)) return 1;
	pmInfo->CodeSegSel = biosCodeSel;

	// put BIOS code to run in protect mode
	pmInfo->InProtectMode = 1;

	// alloc code segment selector for initialize function
	biosInitSel = AllocSelector();
	if (biosInitSel == 0 || biosInitSel == 0xFFFF) return 1;
	if (!SetSelectorRights(biosInitSel, 0x8092)) return 1;
	if (!SetSelectorBase(biosInitSel, (unsigned int)biosCode)) return 1;
	if (!SetSelectorLimit(biosInitSel, VBE_CODE_SIZE - 1)) return 1;

	// alloc stack selector
	biosStack = (unsigned char *)malloc(VBE_STACK_SIZE);
	if (!biosStack) return 1;
	biosStackSel = AllocSelector();
	if (biosStackSel == 0 || biosStackSel == 0xFFFF) return 1;
	if (!SetSelectorRights(biosStackSel, 0x8092)) return 1;
	if (!SetSelectorBase(biosStackSel, (unsigned int)biosStack)) return 1;
	if (!SetSelectorLimit(biosStackSel, VBE_STACK_SIZE - 1)) return 1;

	// call initialize protect mode function first
	fcall.offset = pmInfo->PMInitialize;
	fcall.segment = biosInitSel;
	_asm {
		push   biosStackSel
		push   VBE_STACK_SIZE
		call   StackSwitch
		lea    esi, fcall
		call   fword ptr [esi]
        call   StackSwitch
	}
//// CRASHED HERE: GENERAL PROTECTION FAULT!!!!!!
	// call initialize VBE controller
	vbeInfoSel = AllocSelector();
	if (vbeInfoSel == 0 || vbeInfoSel == 0xFFFF) return 1;
	if (!SetSelectorRights(vbeInfoSel, 0x8092)) return 1;
	if (!SetSelectorBase(vbeInfoSel, (unsigned int)&drvInfo)) return 1;
	if (!SetSelectorLimit(vbeInfoSel, sizeof(VBE_DRIVER_INFO) - 1)) return 1;

	fcall.offset = pmInfo->EntryPoint;
	fcall.segment = pmInfo->CodeSegSel;
	_asm {
		push   biosStackSel
		push   VBE_STACK_SIZE
		call   StackSwitch
		mov    ax, vbeInfoSel
		mov    es, ax
		mov    eax, 0x4F00
		xor    edi, edi
		lea    esi, fcall
		call   fword ptr [esi]
		call   StackSwitch
                mov    val, eax
	}

	if (val == 0x004F && !memcmp(drvInfo.VBESignature, "VESA", 4) && drvInfo.VBEVersion >= 0x0200) printf("OK!\n");
	else printf("VESA 3.0 INIT FAILED!\n");

	return 0;
}
Anyone help me,
Thanks in advanced!

PheroSiden
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Problem with VESA 3.0 in Protect Mode

Post by Brendan »

Hi,
pherosiden wrote:I use OpenWatcom C++ 2.0 and MS-DOS 32 bits environment.
What is "MS-DOS 32 bits environment"?
pherosiden wrote:and here is my code...
Never touch the stack pointer in C code. It belongs to the compiler.

Code: Select all

// switch to 16 bit protect mode stack
void StackSwitch(void *mesp, void *mss)
{

// Compiler inserts "random" function prologue code here that does anything the compiler feels like with the stack

	_asm {
		mov    eax, dr3
		mov    edx, mesp
		pushf
		pop    ecx
		add    eax, stackOffset
		cli
		push   ss
		cmp    [esp], 0x10
		je     kernelStack
		pop    eax
		jmp    switchStack
	kernelStack:
		pop    [eax + 4]
		mov    esp, [eax]
	switchStack:
		lss		esp, mss
		push	ecx
		popf
		jmp		fword ptr [edx]
	}

// Compiler inserts "random" function epilogue code here to match whatever the compiler felt like doing in the prologue

}
Mostly, this sort of thing should be some kind of "load segments, switch stack, call VBE, switch stack back, restore segments" routine in pure (external and not inline) assembly.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
pherosiden
Posts: 3
Joined: Wed Jun 20, 2018 2:28 am

Re: Problem with VESA 3.0 in Protect Mode

Post by pherosiden »

Hi Brendan,

i'm just follow your guide, put some code in external ASM file and then link to main file, but no results, program crashed with general protection fault.
and here is my code (vesa_stack.asm)

Code: Select all

.model compact, c
.386
.data
	VBE_STACK_SIZE	equ	2000h	; VBE caller stack size
	KERNEL_DATA_SEG	equ	10h		; kernel data segment

	extern call_addr	: dword	; VBE far call address
	extern stack_sel	: dword	; VBE caller stack selector
	stack_offset	dd	0		; current stack offset

.code
	public VesaCall

; 16 bit protected mode stack switching
; struct VBE_CALL_STACK {
;	unsigned int *esp;
;	unsigned int *ss
;}
StackSwitch proc
	mov		eax, dr3	; get current thread
	mov		edx, [esp]	; get top of stack pointer
	pushf
	pop		ecx
	add		eax, stack_offset
	cli
	push	ss
	cmp		dword ptr [esp], KERNEL_DATA_SEG
	je		kernel_stack
	pop		eax
	jmp		switch_stack
kernel_stack:
	pop		[eax + 4]
	mov		[eax], esp
switch_stack:
	lss		esp, [esp + 4]
	push	ecx
	popf
	jmp		fword ptr [edx]
StackSwitch endp

; call VESA BIOS function
; VesaCall(REGS16 *regs)
VesaCall proc
		pushf
		cli
		push	es
		pusha
		mov		esi, esp
		push	stack_sel
		push	VBE_STACK_SIZE
		call	StackSwitch
		push	KERNEL_DATA_SEG
		push	esi
		push	edi
		push	ds
		mov		dx, [edi + 12]
		mov		es, dx
		mov		ax, [edi]
		mov		bx, [edi + 2]
		mov		cx, [edi + 4]
		mov		dx, [edi + 6]
		mov     si, [edi + 8]
		mov     di, [edi + 10]
		mov     esi, call_addr
		call    fword ptr [esi]
		pop     ds
		pop		ebp
		mov		ds:[ebp], ax
		mov		eax, ebp
		mov		[eax + 2], bx
		mov		[eax + 4], cx
		mov		[eax + 6], dx
		mov		[eax + 8], si
		mov		[eax + 10], di
		mov     bx, es
		mov     [eax + 12], bx
		call    StackSwitch
		popa
		pop		es
		popf
VesaCall endp

end
and here is C code (vesa_bios.c)
#pragma pack(push, 1)
typedef struct {
unsigned short ax;
unsigned short bx;
unsigned short cx;
unsigned short dx;
unsigned short si;
unsigned short di;
unsigned short es;
} REGS16;

typedef struct {
unsigned int *esp;
unsigned int *ss;
} VBE_CALL_STACK;
#pragma pack(pop)

void far *call_addr = NULL;
unsigned int stack_sel = 0;

extern void VesaCall(REGS16 *regs);

int main(int argc, char const *argv[])
{
REGS16 regs;
...
// call initialize protect mode function first
fcall.offset = pmInfo->PMInitialize;
fcall.segment = biosInitSel;
call_addr = (void far*)&fcall;
stack_sel = biosStackSel;
memset(&regs, 0, sizeof(REGS16));
VesaCall(&regs);
return 1;
}
i spend more time to this, can you correct my code? or any suggestion.

Cheers,
PheroSiden
User avatar
Schol-R-LEA
Member
Member
Posts: 1925
Joined: Fri Oct 27, 2006 9:42 am
Location: Athens, GA, USA

Re: Problem with VESA 3.0 in Protect Mode

Post by Schol-R-LEA »

pherosiden wrote:i'm just follow your guide,
When you say 'guide', are you referring to his comment now, or one of the various Tutorials that are around? AFAIK, Brendan hasn't written any, and is very adamant that they are not a Good Thing for OS-Dev - the one known as "Bran's Kernel Development Guide" was written by someone else, and has several known flaws as well as being woefully outdated.

If it is a tutorial, could you please tell us which one are you using? A link to the specific page(s) would be appreciated, so we can see exactly what you are reading.

I should add that we generally advise against a) writing your own boot loader, and b) using any of the VESA 3.0 functions (which were never widely supported, even when VESA still mattered; these days, even VESA 2.0 functions may not be present on current hardware, IIUC). We won't stop you, and will help you write that it if you meant to do so, but as a rule we recommend using an existing bootloader such as GRUB (or just the built-in UEFI loader, if it is a system built after 2010 or so) and getting the initial video setting hooks from that.
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.
User avatar
zaval
Member
Member
Posts: 659
Joined: Fri Feb 17, 2017 4:01 pm
Location: Ukraine, Bachmut
Contact:

Re: Problem with VESA 3.0 in Protect Mode

Post by zaval »

Schol-R-LEA wrote: I should add that we generally advise against a) writing your own boot loader, and b) using any of the VESA 3.0 functions (which were never widely supported, even when VESA still mattered; these days, even VESA 2.0 functions may not be present on current hardware, IIUC). We won't stop you, and will help you write that it if you meant to do so, but as a rule we recommend using an existing bootloader such as GRUB (or just the built-in UEFI loader, if it is a system built after 2010 or so) and getting the initial video setting hooks from that.
I am not inclined to advise anything at all, just because I am a newbass. but I wanted to say my opinion on this. I believe it's a very intrusive recommendation and I very doubt it is a good fit for everybody.
For example, I consider an OS loader a part of my OS, that understands what OS needs and, not less important, - what it doesn't nor cares about. no grubs or anything could serve such a role. they spit on me absolutely irrelevant concepts and conventions I couldn't care less about. I need a delegate into FW that working as FW payload/client/application - taking advantages of its services, will prepare environment for my OS, not for every OS on this planet and in the neighbourhood. It needs to know all inerfacing protocols and conventions required for the preparation and those I need to define, not some dudes.
Finally, there is no "built-in" UEFI loader at all. UEFI expects you to write an OS loader for your OS and it runs it as its application and gives to it its services - exactly what I described above. "Built-in" in UEFI is the Boot Manager, it's a thing that deals with exctly interacting with user and letting them to choose/set what OS loaders to run in which order. Loaders not OSes. The fact linux screwed it up embedding into itself an efi application, turning itself into a OS/efi loader mutant doesn't change the paradigm. The same was in ARC i believe and the same is on OF. And by btw, with a good FW, writing an OS loader is not complex. Most complexities arise on the CPU architecture side and OS requirements side, not on the FW side, the latter turns into something similar to writing a banal C application heavily relying on C library.
Unifying loaders is as bad as unifying driver models across OSes - reminds me that amazing cover parody you posted here. Image

Image
ANT - NT-like OS for x64 and arm64.
efify - UEFI for a couple of boards (mips and arm). suspended due to lost of all the target park boards (russians destroyed our town).
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Problem with VESA 3.0 in Protect Mode

Post by Brendan »

Hi,
pherosiden wrote:i spend more time to this, can you correct my code? or any suggestion.
Some notes..

1) You can expect that all of VBE's functions (especially the ones to get EDID, set a video mode, update the palette and change the display start) will take a relatively large amount of time (potentially tens of milliseconds), and for a real OS (that has to care about dropping packets from the network cards, missing IRQs from timers, etc) this is far too long to have IRQs disabled. For this reason you shouldn't disable IRQs while running VBE functions.

2) When running VBE's code; the descriptor used for SS must have the "big" flag clear so that instructions that implicitly use the stack pointer (including "push", "pop", "call", ...) will use SP and not ESP.

3) When an interrupt (including those that can't be disabled - exceptions, NMI, machine check) interrupts CPL=0 code the stack is not normally switched. This means that when VBE's code is interrupted your interrupt handler will be started with a 16-bit stack (due to the "big flag clear" mentioned above). Note: The alternative would be to run VBE's code at CPL=3 and emulate any protected instructions (HLT, CLI, STI, ...), but if you were doing that it'd make more sense to use virtual8086 mode and not bother with VBE's protected mode interface at all.

4) To make some of the above easier to deal with; I'd recommend having a 32-bit stack in the first 64 KiB of the address space; so that the highest bits of ESP are zero and so that ESP is equal to SP. This allows you to switch from a 32-bit stack ("big" flag set) to a 16-bit stack ("big" flag clear) without changing ESP at all (just by loading SS alone), and without changing where the stack is in the address space.

5) All of the above combined would allow you to delete the entire "StackSwitch" procedure, and just have a "mov ss" (to switch to a 16-bit stack) immediately before calling the VBE function and another "mov ss" (to switch back to a 32-bit stack) immediately after calling the VBE function.

6) You should be able to test most of the code without actually calling any VBE function (just remove the "call fword [call_addr]" and make sure your code switches segment registers, etc correctly). You should also be able to use a breakpoint (e.g. the "xchg bx,bx" magic breakpoint in Bochs) and manually check that everything is right at the point where you would've called a VBE function; and should be able to deliberately cause an exception (e.g. "int3") to make sure your interrupt/exception handlers work correctly.

7) If your interrupt handlers work correctly; for some VBE functions (e.g. "Return VBE Controller Information") you should be able to display all of the information that might be useful to figure out what went wrong (contents of all the registers, value in EIP, exception number, exception's error code, ...). For other VBE functions ("Set VBE Mode") video output may be in an undefined state; but hopefully if some VBE functions work they all will.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply