Author Topic: how to swtich into protected mode/32bit and then back to real-mode/16-bit?  (Read 14385 times)

Offline DiTBho

  • Jr. Member
  • *
  • Posts: 5
hi
I am writing a monitor able to load and start a Linux kernel.

The application is started by the BIOS at 0x7c0 in real-mode and it switches into protected mode.
The whole monitor is 32bit and works in protected mode.

Code: [Select]
[bits 16]
...
switch_to_32bit:
    cli                      ; 1. disable interrupts
    lgdt [ gdt_descriptor ]  ; 2. load the GDT descriptor
    mov eax, cr0
    or eax, 0x1              ; 3. set 32-bit mode bit in cr0
    mov cr0, eax
    jmp code_seg:init_32bit  ; 4. far jump by using a different segment

[bits 32]
init_32bit:                  ; now using 32-bit instructions
    mov ax, data_seg         ; 5. update the segment registers
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    jmp app32_start

Code: [Select]

; -----------------------------------------------------------------------------
; boot/gdt
;
; labels, are needed to compute sizes and jumps
;
; GDT for code segment. base = 0x00000000, length = 0xfffff
; -----------------------------------------------------------------------------
gdt_start:
    ; the GDT starts with a null 8-byte
    dd 0x0 ; 4 byte
    dd 0x0 ; 4 byte

gdt_code:
    dw 0xffff    ; segment length, bits 00-15
    dw 0x0       ; segment base  , bits 00-15
    db 0x0       ; segment base  , bits 16-23
    db 10011010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x0       ; segment base  , bits 24-31

; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0

gdt_end:

; GDT descriptor
gdt_descriptor:
    dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
    dd gdt_start               ; address (32 bit)

; define some constants for later use
code_seg equ gdt_code - gdt_start
data_seg equ gdt_data - gdt_start

This part works.

At some point the monitor loads from disk the kernel as bz-image, and places it in ram.

bz-image is composed by two parts:
  • the real-mode kernel setup, loaded to 0x10000
  • the kernel body, loaded to 0x100000

So, I need to switch back to real-mode in order to run the kernel-setup.

edit:
split into two posts
« Last Edit: July 12, 2023, 10:04:09 AM by DiTBho »

Offline DiTBho

  • Jr. Member
  • *
  • Posts: 5
Code: [Select]
switch_to_rm:
        mov   eax,cr0           ; get CPU control register
                                ; 286 will resume execution @ 0xF000:FFF0
                                ; 386 will fall through.
        lidt  [reset_IDTR]
                                ; restore real-mode compatible value
                                ;   * * * mandatory * * *
                                ;   real-mode interrupts are relocatable
                                ;   on the 386 via the IDT register!
        and     eax,0xfffffffe  ; clear protected mode bit
        mov     cr0,eax         ; now, the cpu is out of protected-mode
;----------------------------------------------------------------------------
        xor     eax,eax         ; a convenient zero
        mov     cr3,eax         ; Flush the TLB
;----------------------------------------------------------------------------
        mov eax, 0x0000         ; zero segment for app16
       ;mov eax, 0x1000         ; zero segment for kernel
        mov ds,  ax
        mov gs,  ax
        mov es,  ax
        mov cs,  ax
        nop                     ; flush prefetch queue
        jmp app16_addr          ; jump into real-mode application   !!!!!!DOES NOT WORK!!!!!!!
        nop                     ;
;----------------------------------------------------------------------------
reset_IDTR:
        dw      0
        dw      0

This part doesn't work properly as I always get a "triple fault", which resets the CPU but doesn't force the InstructionPointer to point to app16_addr, instead it points to the BIOS, which loads the MBR again at 0x7c00.
« Last Edit: July 12, 2023, 10:05:01 AM by DiTBho »

Offline DiTBho

  • Jr. Member
  • *
  • Posts: 5
by searching in this forum, I indirectly found this topic that has precious information

Offline Michael Petch

  • Jr. Member
  • *
  • Posts: 4
Since your code seems to assume you aren't using paging, I have some sample code in this Stackoverflow answer that contains a do_vbe function that switches from 32-bit protected mode to real mode. This code assumes you didn't remap the PICs (which would also potentially have to be handled). The do_vbe function drops to real mode, does some video mode work with the BIOS and then returns back to 32-bit protected mode and then back to the calling function. The code of interest to you that you could draw inspiration from will be from the line starting from the CLI instruction down to the CLD instruction in do_vbe
« Last Edit: July 12, 2023, 09:33:37 PM by Michael Petch »

Offline DiTBho

  • Jr. Member
  • *
  • Posts: 5
Since your code seems to assume you aren't using paging, I have some sample code in this Stackoverflow answer that contains a do_vbe function that switches from 32-bit protected mode to real mode.

yup, no paging.
Thanks for your link, it's very interesting  :D

Offline fredericopissarra

  • Full Member
  • **
  • Posts: 373
  • Country: br
Intel SDM, volume 3, topic 9.9: "Mode Switching".
Maybe you want to read Intel SDM, volume 3, chapter 20: "8086 emulation" (works in i386 mode).
« Last Edit: July 13, 2023, 11:10:13 AM by fredericopissarra »

Offline DiTBho

  • Jr. Member
  • *
  • Posts: 5
Intel SDM, volume 3, topic 9.9: "Mode Switching".
Maybe you want to read Intel SDM, volume 3, chapter 20: "8086 emulation" (works in i386 mode).

I will for sure read it sooner or later.

At the moment I'd like to complete my mon/loader, so I am looking for examples that are known to work.
I have other priorities, as I have also to hack the Linux kernel and adapt it for my Geode router (amd x86 compatible).