;see http://ringzero.free.fr/os/protected%20mode/Pm/PM1.ASM %define VIDEORAM 0xb8000 [bits 16] %ifndef okadfull ;standalone code for testing %define RETSTACK VIDEORAM + 80 * 25 * 2 org 0 ;probably 0x7c00 but it doesn't matter start: cli call setup %ifdef test_16bit_stack push 'A ' push 'B ' hlt %endif call setpmode ;note that while in protected mode, we continue using 16-bit code, but ;expecting different behavior; e.g. in pmode the following is seen ;as mov eax, 0x48402042 ;(so do NOT put a [bits 32] directive here!) mov ax, 'A ' ;should show A on a green background inc ax ;these two bytes will be loaded into upper half of eax in pmode dec ax push ax ;show at bottom of screen mov ax, 'B ' inc ax dec ax push ax ;now test going back into real mode [bits 32] call setrmode [bits 16] push 'A ' push 'B ' push 'C ' push 'D ' hlt %endif ;okadfull ;the [bits 16] directive at top applies here either way pmode: mov eax, cr0 or al, 1 mov cr0, eax xor eax, eax ;clear high bits of eax ret enterpm: pop ax push code32p push ax retf enterrm: pop ax push fs ;real-mode code segment push ax retf %ifndef okadfull a20enable: mov al, 0x0d1 out 0x64, al ;to keyboard .wait: in al, 0x64 and al, 2 jnz .wait mov al, 0x4b out 0x60, al ;to keyboard, enable A20 ret setup: call a20enable ;enable address line 20 even if we don't use pmode pop ax ;get return address mov bx, cs ;we want to make sure code segment is 0 rol bx, 4 ;change segment offset into byte offset add bx, ax ;add return address mov ax, VIDEORAM >> 4 mov ss, ax ;stack sector in screen memory mov ax, 80 * 25 * 2 ;end of screen memory mov sp, ax xor ax, ax ;new code segment of 0 mov fs, ax ;save zero for later (same zero reg as newboot.nasm) push ax ;as code segment push bx ;as return address shr bx, 4 ;shift to segment offset and bl, ~0x1f ;start of 512-byte block mov ds, bx ;set data segment relative to start of this code being 0 retf %endif ;okadfull setpmode: ;go into protected mode from real mode pushad ;save all registers as doublewords %ifndef okadfull mov ax, ds shl ax, 4 ;lgdt loads from a linear address, IGNORING segment registers! add word [gdt + 2], ax ;patch descriptor with correct offset mov bx, gdt lgdt [bx] ;will it use segment register with register-indirect? yep %endif ;okadfull call pmode call enterpm [bits 32] mov eax, data32p ;protected mode data segment mov es, ax mov ds, ax %if RETSTACK > 0x10000 ;manipulate stack to unsegmented (SS:SP to 0:ESP) xor ebx, ebx mov bx, ss shl ebx, 4 add bx, sp mov esp, ebx %endif mov ss, ax ;this makes stack segment 32 bits popad o16 ret rmode: ;actually just puts us into 16-bit protected mode pop eax ;return address push dword code16r ;for 16-bit PM and real mode push eax retf setrmode: ;assumed that protected-mode stack is based at 0 ;and that bits 16 through 19 will not change during time in realmode pushad ;save 32-bit values of registers mov ecx, esp ;do all possible 32-bit ops before going to 16 bits mov edx, cr0 call rmode [bits 16] mov ax, data16r mov ds, ax mov es, ax mov ss, ax ;here the stack becomes 16 bits based at 0, and SP used not ESP ;*** consider stack invalid from here until we reach real mode *** xor cx, cx ;clear low 16 bits shr ecx, 4 ;move high 4 bits into cl dec dl ;leave protected mode, only works if we KNOW bit 0 is set mov cr0, edx %if RETSTACK > 0x10000 ;switch stack for predictable results(?) mov bx, 0x1000 ;relatively safe stack pointer we hope? xchg bx, sp %endif call enterrm %if RETSTACK > 0x10000 mov sp, bx %endif xor ax, ax mov ds, ax mov es, ax mov ss, cx ;note we don't need to set SP to 8xxx if ESP is b8xxx, since ;the b000 is now in SS, and the b of b8xxx is ignored in real mode popad o32 ret %ifndef okadfull ;data area begins here align 8, db 0 gdt: ;using null descriptor as GDT descriptor dw gdt_end - gdt - 1 ;GDT limit dd gdt ;pointer to start of table dw 0 code32p equ $ - gdt dw 0xffff, 0, 0x9a00, 0xcf ;32-bit protected-mode code data32p equ $ - gdt dw 0xffff, 0, 0x9200, 0xcf ;32-bit protected-mode data code16r equ $ - gdt dw 0xffff, 0, 0x9a00, 0x00 ;16-bit real-mode code data16r equ $ - gdt dw 0xffff, 0, 0x9200, 0x00 ;16-bit real-mode data gdt_end: times 510 - ($ - $$) db 0x0 ;pad with nulls to 55AA at end of boot sector db 0x55, 0xaa %endif ;okadfull