All right...
I cleaned up, took what I've learnt, and put together a new test program to enter protected mode, go back to real mode, and return to DOS. A lot of it was rewritten from scratch.
Code: Select all
;
; Sample program to enter and exit protected mode.
;
ORG 100h
%define BSS_OLD_SEG 0
%define BSS_GDTR 2
%define BSS_IDTR 0x0A
%define BSS_SAVE_GDTR 0x12
%define BSS_SAVE_IDTR 0x1A
main:
smsw ax
test ax, 1
jnz .err
call setvideoseg
mov di, (10*80)*2
cld
mov ax, 0x0F00 | '0'
stosw
call waitesc16
mov bp, bss_start
mov ax, cs
mov [bp+BSS_OLD_SEG], ax
xor dx, dx
mov dl, ah
mov cl, 4
shr dl, cl
shl ax, cl
;
; Fixups:
;
add [ze_gdt.sel08+2], ax
adc [ze_gdt.sel08+4], dl
add [ze_gdt.sel10+2], ax
adc [ze_gdt.sel10+4], dl
add [ze_gdt.sel20+2], ax
adc [ze_gdt.sel20+4], dl
;
; Save existing GDTR and IDTR.
;
sgdt [bp+BSS_SAVE_GDTR]
sidt [bp+BSS_SAVE_IDTR]
;
; Make new GDTR:
;
push ax
push dx
add ax, ze_gdt
adc dl, 0
mov word [bp+BSS_GDTR], ze_gdt.end-ze_gdt-1
mov [bp+BSS_GDTR+2], ax
mov [bp+BSS_GDTR+4], dx
;
; Make new INTR -- this one is currently a dummy.
;
pop dx
pop ax
add ax, ze_idt
adc dl, 0
mov word [bp+BSS_IDTR], ze_idt.end-ze_idt-1
mov [bp+BSS_IDTR+2], ax
mov [bp+BSS_IDTR+4], dx
;
; Load GTDR and switch to protected mode.
;
cli
lgdt [bp+BSS_GDTR]
lidt [bp+BSS_IDTR]
mov eax,cr0
or al, 1
mov cr0,eax
.inpm: ;
; We are in protected mode. The segments remain in their old/
; existing real mode configuration. Therefore we can still use
; the previously loaded segment for the video memory.
;
smsw ax
test al, 1
mov ah, 0x0F
jz .rm
mov al, 'P'
jmp .cmm
.rm: mov al, 'R'
.cmm: stosw
call waitesc16
;
; Generate a FAR jump to set one of our GDT code segment selectors,
; namely the 16-bit one.
;
push word 0x20 ; 16-bit code seg.
push word .begin_pm16
retf
ALIGN 2
.begin_pm16:
;
; Now running with protected mode CS.
;
mov ax, 0x0F00 | 'A'
stosw
call waitesc16
;
; Load PM data and stack selector.
;
mov ax, 0x10 ; 16-bit data/stack seg.
mov ds, ax
mov ss, ax
;
; "Exercise" the stack.
;
pusha
popa
mov ax, 0x0F00 | 'B'
stosw
call waitesc16
.doexitpm:
;
; Exit protected mode.
;
mov eax,cr0
and al, ~1
mov cr0,eax
;
; Restore old GDTR and IDTR
;
lgdt [bp+BSS_SAVE_GDTR]
lidt [bp+BSS_SAVE_IDTR]
.outofpm:
; We are in real mode, but still running with protected mode
; selectors in the segment registers, except for ES which still
; remains unchanged ever since we loaded it with the real mode
; video memory address (0xB800) before the switch to PM.
;
; Now, generate a FAR jump to restore CS to its real mode setting.
; Next, restore the data/stack segment registers.
;
mov ax, [bp+BSS_OLD_SEG]
push ax
push word .load_cs
retf
ALIGN 2
.load_cs:
mov ds, ax
mov ss, ax
;
; All segments now restored to their real mode configuration.
;
mov ax, 0x0F00 | 'C'
stosw
call waitesc16
;
; Re-enable interrupts and return to DOS with status 0.
;
sti
xor al, al
jmp .term
.err: mov al, 1
.term: mov ah, 4Ch
int 21h
ALIGN 2
setvideoseg:
push ax
smsw ax
test al, 1
jnz .1
mov ax, 0xB800
jmp .2
.1: mov ax, 0x0018 ; video segment selector
.2: mov es, ax
pop ax
ret
waitesc16:
pushf
push ax
.1: in al, 60h
cmp al, 1
jne .1
.2: in al, 60h
cmp al, 1
je .2
pop ax
popf
ret
SEGMENT _DATA
ALIGN 8
ze_gdt:
dd 0, 0 ; entry 0: unused
.sel08: ; code segment selector (32-bit)
dw 0xFFFF ; +0 limit bits 0-15
dw 0 ; +2 base addr. 0-15 -- must fixup
db 0 ; +4 base addr. 16-23 -- "
db 0x9B ; +5 PDdSType, *P=present, D=DPL, S=0=system descr. type
db 0x40 ; +6 GBLALimt, G=gran., B=32bit, L=64bit-, A=avail.
db 0 ; +7 base addr. 24-31
.sel10: ; data and stack segment selector (16-bit)
dw 0xFFFF ; +0 limit bits 0-15
dw 0 ; +2 base addr. 0-15 -- must fixup
db 0 ; +4 base addr. 16-23 -- "
db 0x93 ; +5 PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
db 0x00 ; +6 GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
db 0x00 ; +7 base addr. 24-31
.sel18: ; text mode VGA memory
dw 0xFFF ; +0 limit 4095
dw 0x8000 ; +2 low 16 bits of base addr.
db 0x0B ; +4 bits 16-23 of base addr.
db 0x93 ; +5 PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
db 0x00 ; +6 GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
db 0x00 ; +7 base addr. 24-31
.sel20: ; code segment selector (16-bit)
dw 0xFFFF ; +0 limit bits 0-15
dw 0 ; +2 base addr. 0-15 -- must fixup
db 0 ; +4 base addr. 16-23 -- "
db 0x9B ; +5 PDdSType, *P=present, D=DPL, S=0=system descr. type
db 0x00 ; +6 GBLALimt, G=gran., B=0=16bit*, L=64bit-, A=avail.
db 0 ; +7 base addr. 24-31
.sel28: ; flat code segment selector (32-bit, 4 GB)
dw 0xFFFF ; +0 limit bits 0-15
dw 0 ; +2 base addr. 0-15
db 0 ; +4 base addr. 16-23
db 0x9B ; +5 PDdSType, *P=present, D=DPL, S=0=system descr. type
db 0xCF ; +6 GBLALimt, G=gran*, B=1=32bit*, L=64bit-, A=avail.
db 0 ; +7 base addr. 24-31
.sel30: ; flat data and stack segment selector (32-bit, 4 GB)
dw 0xFFFF ; +0 limit bits 0-15
dw 0 ; +2 base addr. 0-15
db 0 ; +4 base addr. 16-23
db 0x93 ; +5 PDdSType, *P=present, D=DPL, S=0=sys.desc, d.seg+w
db 0xCF ; +6 GBLALimt, G=gran., B=16bit*, L=64bit-, A=avail.
db 0x00 ; +7 base addr. 24-31
.end:
ze_idt:
dd 0,0
.end:
SEGMENT .bss
ALIGN 16
bss_start:
; vim: syn=nasm:
The shortcomings of this program, are a) that it doesn't test 32-bit mode, and b) that the IDT is just a dummy, so it can't do STI before exiting protected mode and restoring the real mode IDTR.
-Albert.