I wrote a program that from real mode sets up and loads a GDT, and then calls a protection mode switcher function that is responsible for doing everything to put it into 32bit protected mode, including the far call. Then from in 32bit protected mode it calls a function to switch video modes. Of course this requires the instruction INT 10h, but you can't do that in protected mode without an IDT and setting up your own interrupt functions to replace those of the BIOS. Suffice to say I didn't do such an extensive thing as that. Instead I wrote an ordinary function that in addition to calling INT 10h, actually first calls a function I made to temporarily switch back to 16bit protected mode. After the INT call, my video mode switching function calls a protected mode resuming function, and after that it returns to the main protected mode function. After doing that, it then returns from the protected mode function back to the 16bit real mode function that was initially responsible for far calling the main 32bit protected mode function. From there it returns to the program's main code and finally returns to DOS, leaving the graphics mode set to mode 13h (320x200 256 color mode).
Yes that is a LOT of work to accomplish a simple task, but it demonstrates a way to call 16bit real mode interrupts, without ever having to load an IDT and have interrupt gates. It also demonstrates how to get into and then out of 32bit protected mode in such a manner that you can get back to DOS without crashing the system by messing up the stack (or any other number of things that could go wrong). And that doesn't even touch on switching to ring3 user mode (which I haven't quite figured out yet how to do). All of the protected mode stuff I did here was in ring0 kernel mode.
Here's the code.
ORG 0x100
USE16
Main:
push bp
mov bp,sp
xor eax,eax
mov ax,cs
shl eax,4
mov edx,eax
shr edx,16
mov [GDT_Entry_1.Base0],ax
mov [GDT_Entry_1.Base1],dl
mov [GDT_Entry_2.Base0],ax
mov [GDT_Entry_2.Base1],dl
add eax,StartOfGDT
mov [PointerToGDT+2],eax
lgdt [PointerToGDT]
push ProtMode32EntryPoint
call SwitchToProtMode
leave
ret
SwitchToProtMode:
push bp
mov bp,sp
mov [OldCS],cs
mov [OldDS],ds
mov [OldES],es
mov [OldFS],fs
mov [OldGS],gs
mov [OldSS],ss
mov dx,[bp+4]
push 8
push dx
cli
mov eax,cr0
or al,1
mov cr0,eax
push bx
push si
push di
call far [bp-4]
mov ds,[OldDS]
mov es,[OldES]
mov fs,[OldFS]
mov gs,[OldGS]
mov ss,[OldSS]
nop
pop di
pop si
pop bx
sti
leave
ret 2
PointerToGDT:
dw EndOfGDT-StartOfGDT-1
dd 0
StartOfGDT:
dq 0
GDT_Entry_1: ;32bit code
.Limit0 dw 0xFFFF
.Base0 dw 0x0000
.Base1 db 0x00
.Access db 0x9A
.FlagsAndLimit1 db 0xCF
.Base2 db 0x00
GDT_Entry_2: ;32bit data
.Limit0 dw 0xFFFF
.Base0 dw 0x0000
.Base1 db 0x00
.Access db 0x92
.FlagsAndLimit1 db 0xCF
.Base2 db 0x00
EndOfGDT:
ProtMode32EntryPoint:
USE32
mov ax,8*2
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
nop
push ebp
mov ebp,esp
call SetVideoMode
leave
mov eax,cr0
and al,0xFE
mov cr0,eax
retfw
SetVideoMode:
push ebp
mov ebp,esp
call TempSwitchToRealMode
USE16
mov ax,0x0013
int 0x10
call ReturnToProtMode
USE32
leave
ret
TempSwitchToRealMode:
mov eax,cr0
and al,0xFE
mov cr0,eax
mov ax,[OldCS]
push ax
push TempSwitchToRealModePart2
mov eax,esp
jmp far [eax]
TempSwitchToRealModePart2:
USE16
mov ds,[OldDS]
mov es,[OldES]
mov fs,[OldFS]
mov gs,[OldGS]
mov ss,[OldSS]
nop
add sp,6
ret
ReturnToProtMode:
mov eax,cr0
or al,1
mov cr0,eax
push 8
push ReturnToProtModePart2
mov bx,sp
jmp far [bx]
ReturnToProtModePart2:
USE32
mov ax,8*2
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
add sp,4
ret
OldCS dw 0
OldDS dw 0
OldES dw 0
OldFS dw 0
OldGS dw 0
OldSS dw 0
Once you copy this code and save it in a text file (asm or txt file extension doesn't matter to NASM), you should be able to be compile it with the command line:
nasm -f bin -o filename.com filename.asm
It runs fine in DosBox. I haven't tested it on real hardware yet.