16 bit real mode to protected mode and back, a neat trick!

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
childOfTechno
Member
Member
Posts: 28
Joined: Fri Feb 24, 2012 5:10 pm

16 bit real mode to protected mode and back, a neat trick!

Post by childOfTechno »

Firstly, let me say how much I love osdev.org, you just can't find this sort of information anywhere else.

I've been playing with a tiny (64k) 32 bit kernel implemented with FASM, and I thought I'd share a neat trick I've developed. Firstly, the code, data and stack are stored in an identity mapped segment in low memory. This is how I enter and exit protected mode:

Code: Select all

;---------------------------------------------------------------------------------------------------
; macro: to32
; "switch to 32 bit (protected) mode"
; notes:
;   processor must be in 16 bit (real) mode
;---------------------------------------------------------------------------------------------------
macro to32 {
    call core16_to32
    use32
}
;---------------------------------------------------------------------------------------------------
; macro: to16
; "switch to 16 bit (real) mode"
; notes:
;   processor must be in 32 bit (protected) mode
;---------------------------------------------------------------------------------------------------
macro to16 {
    call core32_to16
    use16
}  
;---------------------------------------------------------------------------------------------------
; core16_to32()
; "transition to 32 bit (protected) mode"
; returns:
;   cs = core code segment (protected)
;   ds, es, ss, fs, gs = core data segment (protected)
; assumes:
;   cpu is in 16 bit (real) mode
;   ds -> core data segment
;   interrupts are disabled
;---------------------------------------------------------------------------------------------------
use16
core16_to32:
    ; extend return address to 32 bits
    sub esp, 2
    ; save
    push eax
    ; mov high word of return address to low word
    xor eax, eax
    mov ax, [esp + 6]
    mov [esp + 4], eax
    ; load the page directory
    mov eax, [core.cr3]
    mov cr3, eax
    ; load the global descriptor table
    lgdt [core.gdtr]
    ; load the interrupt descriptor table
    lidt [core.idtr]
    ; enter protected mode (enable paging and protection)
    mov eax, cr0
    or eax, 0x80000001
    mov cr0, eax
    ; transfer to 32 bit segment
    jmp core.CS32:.step0
    use32
    .step0:
    ; load segment registers
    mov ax, core.DS32
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    ; restore and return
    pop eax
    ret
;---------------------------------------------------------------------------------------------------
; core32_to16()
; "transition to 16 bit (real) mode"
; returns:
;   cs, ds, es, fs, gs, ss = core segment (real)
; assumes:
;   cpu is in 32 bit (protected) mode
;   ds -> core data segment
;   interrupts are disabled
;---------------------------------------------------------------------------------------------------
use32
core32_to16:
    ; save eax
    push eax
    ; switch to 16 bit execution
    jmp core.CS16:.step0
    use16
    .step0:
    ; load 16 bit segment limits
    mov ax, core.DS16
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    ; clear protection and paging bits
    mov eax, cr0
    and eax, 0xffffffff xor 0x80000001
    mov cr0, eax
    ; reload cs
    push word [core.image_real_seg]
    push word .step1
    retf
    .step1:
    ; load real mode segment values
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    ; restore the real mode IDT
    lidt [core.real_idtr]
    ; restore eax
    pop eax
    ; return, remove high word of return address
    ret 2
Because the kernel resides in a single 64k segment, the high word of EIP and ESP is always zero, so this works. To transition to real mode and change the video mode I'd do something like:

Code: Select all

    ; in 32 bit mode here ...
    push ds, es, fs, gs
    pushf
    ; function = 0x00 "switch mode"
    mov ah, 0x00
    mov al, VIDEO_MODE
    ; call BIOS int 0x10
    cli
    to16
    int 0x10
    to32
    ; restore interrupt flag and segments
    popf
    pop ds, es, fs, gs   
(note: I extend the syntax of PUSH/POP to support multiple args using a macro)

I think it's rather neat to make the mode switch behave like an instruction :)
-~ Beware the turing tarpit, in which everything is possible but nothing of interest is easy ~-
Rudster816
Member
Member
Posts: 141
Joined: Thu Jun 17, 2010 2:36 am

Re: 16 bit real mode to protected mode and back, a neat tric

Post by Rudster816 »

You shouldn't assume that interrupts are going to be disabled when switching modes. Interrupts absolutely must be disabled when switching, so you should really add CLI instructions to the core16_to32 and core32_to16 functions. The odds of you receiving an interrupt in such a short span is unlikely, so you catching your code triple faulting if you forget to add a CLI instruction will be rare and extremely hard to find down the road.
childOfTechno
Member
Member
Posts: 28
Joined: Fri Feb 24, 2012 5:10 pm

Re: 16 bit real mode to protected mode and back, a neat tric

Post by childOfTechno »

You shouldn't assume that interrupts are going to be disabled when switching modes. Interrupts absolutely must be disabled when switching, so you should really add CLI instructions to the core16_to32 and core32_to16 functions. The odds of you receiving an interrupt in such a short span is unlikely, so you catching your code triple faulting if you forget to add a CLI instruction will be rare and extremely hard to find down the road.
Yeah, that's a pretty good point actually. I've got the "assumes" comment field there to remind me to make sure interrupts are disabled, but since mode switching is slow anyway, the occasional superfluous CLI isn't going to hurt :)

EDIT: although by design interrupts are always disabled while executing kernel code. The 64k nano kernel is basically just a pile of IRQ and exception handling code ... interrupts are enabled for tasks and servers

thanks :)
-~ Beware the turing tarpit, in which everything is possible but nothing of interest is easy ~-
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: 16 bit real mode to protected mode and back, a neat tric

Post by gerryg400 »

What is the 'trick' ? It looks, at first glance, more or less like my code.
If a trainstation is where trains stop, what is a workstation ?
childOfTechno
Member
Member
Posts: 28
Joined: Fri Feb 24, 2012 5:10 pm

Re: 16 bit real mode to protected mode and back, a neat tric

Post by childOfTechno »

What is the 'trick' ? It looks, at first glance, more or less like my code.
Look closely at the video mode switch code, all the instructions are executed in 32 bit protected mode, except for the "int 0x10" which is executed in 16 bit real mode.

The trick is the to32 and to16 macros, transitions to/from real mode take place inline and look somewhat like an instruction. Maybe you do things the same way as me? :)

I just think it's cute (although not a widely applicable approach.)
-~ Beware the turing tarpit, in which everything is possible but nothing of interest is easy ~-
Post Reply