Page 1 of 1

Dropping to Real Mode

Posted: Thu May 17, 2007 2:05 am
by pcmattman
OK, I'm trying to drop back to real mode from my kernel (so I can do BIOS calls). Unfortunately, it doesn't work... LD gives me the following error:

Code: Select all

C:\***\realmode.o(.text+0x16): relocation truncated to fit: R_386_16 t
ext
C:\***\realmode.o(.text+0x32): relocation truncated to fit: R_386_16 t
ext
C:\***\realmode.o(.text+0x3d): relocation truncated to fit: R_386_16 _
gp
C:\***\realmode.o(.text+0x48): relocation truncated to fit: R_386_16 t
ext
The assembly code which causes this is assembled with NASM in ELF format:

Code: Select all

; 32-bit
bits 32

; globals
global _enter_rm

; external stuff
extern _gp

; enters real mode, does stuff, comes back to pmode
_enter_rm:

	; don't allow any interrupts!
	cli

	; push everything
	pushad
	
	; jump to the 16-bit code segment
	jmp 0x18:do16

; 16-bit now!
bits 16	

	do16:
	
	; load the new segments
	mov ax,0x20
	mov ds,ax
	mov ss,ax
	
	; get the new segments initialized
	nop
	
	; push the CS:IP for the real mode task
	push 0x18
	lea bx,[realmode]
	push bx
	
	; enter real mode
	mov eax,cr0
	and al,0xFE
	mov cr0,eax
	
	; far jump
	retf

; Real mode task
realmode:

	; load real-mode segments
	mov ax,cx
	mov ds,ax
	mov ss,ax
	nop
	mov es,ax
	mov fs,ax
	mov gs,ax
	
	; load the real-mode idt
	lidt [ridtr]
	
	; set ds and es
	push  cs
	pop   ds
	push  ds
	pop   es
	
	; allow interrupts now
	sti
	
	; do what needs to be done in real mode

	; no more interrupts
	cli
	
	; load the pmode GDT again
	lgdt [_gp]
	
	; set the PE bit in the CR0 field
	mov	eax,cr0
	or	al,1
	mov	cr0,eax
	
	; jump to the flush part
	jmp 0x08:flush2

; back to 32-bit now
bits 32

	; load new segment descriptors
	flush2:
	
	; 0x10 is the data segment
	mov ax,0x10
	mov ds,ax
	mov ss,ax
	nop
	mov es,ax
	mov fs,ax
	mov gs,ax
	
	; pop everything
	popad

	; return
	ret

; real-mode IDTR - this points to the IVT
ridtr:	dw 0xFFFF					; limit=0xFFFF
		dd 0						; base=0
I'm stumped. Any ideas?

Posted: Thu May 17, 2007 2:50 am
by mathematician
push 0x18
lea bx,[realmode]
push bx
I may be wrong, but the segmant address you are pushing looks a bit like a reference to the gdt, rather than a real mode segment address. If 0x18 is the intended segment address, it takes you straight into the middle of the real mode interrupt vector table, which runs from 0x0000:0000 to 0x100:0000

Posted: Thu May 17, 2007 2:54 am
by mathematician
Unless you are going to keep interrupts permanently disable whilst you are in real mode it might be worth setting the PICs back to what they were, and, just to be on the safe side, disable the A20 line.

Posted: Thu May 17, 2007 3:06 am
by pcmattman
OK, I've found some code on the internet that looks promising:

Code: Select all

; handles the drop into real mode
; so far i'm not worrying about getting back...

; 32-bit to start with
bits 32

; globals
global _enter_rm

; enters real mode, with the real mode segments
_enter_rm:

	; enter real mode
	cli
	
	; PMode data selector
	mov eax,0x20
	mov ds,eax
	mov es,eax
	mov fs,eax
	mov gs,eax
	mov ss,eax

	; clear the protection bit
	mov eax,cr0
	and al,0xFE
	mov	cr0,eax

	; load ds
	xor ax,ax
	mov ds,ax
	
	; load the idt register
	lidt [ridtr]
	
	; far jump to load new segments
	jmp 0x18:L1
bits 16
	L1:
	
	; load SS:ESP
	xor ax,ax
	mov ss,ax
	mov esp,0x100000
	
	; we're now in real mode, load segments
	xor ax,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	
	; enable interrupts again
	sti
	
	; forever loop
	l:
	jmp $

; real-mode IDTR - this points to the IVT
ridtr:	dw 0xFFFF					; limit=0xFFFF
		dd 0						; base=0
; handles the drop into real mode
; so far i'm not worrying about getting back...

; 32-bit to start with
bits 32

; globals
global _enter_rm

; enters real mode, with the real mode segments
_enter_rm:

	; enter real mode
	cli
	
	; PMode data selector
	mov eax,0x20
	mov ds,eax
	mov es,eax
	mov fs,eax
	mov gs,eax
	mov ss,eax

	; clear the protection bit
	mov eax,cr0
	and al,0xFE
	mov	cr0,eax

	; load ds
	xor ax,ax
	mov ds,ax
	
	; load the idt register
	lidt [ridtr]
	
	; far jump to load new segments
	jmp 0x18:L1
bits 16
	L1:
	
	; load SS:ESP
	xor ax,ax
	mov ss,ax
	mov esp,0x100000
	
	; we're now in real mode, load segments
	xor ax,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	
	; enable interrupts again
	sti
	
	; forever loop
	l:
	jmp $

; real-mode IDTR - this points to the IVT
ridtr:	dw 0xFFFF					; limit=0xFFFF
		dd 0						; base=0
Every segment except CS becomes their 16-bit counterparts (including their index in the GDT). CS is the only segment that doesn't. Can you give any suggestions as to what value should be used for the code segment in RM?

Posted: Thu May 17, 2007 5:07 am
by mathematician
pcmattman wrote: Every segment except CS becomes their 16-bit counterparts (including their index in the GDT). CS is the only segment that doesn't. Can you give any suggestions as to what value should be used for the code segment in RM?
That all depends upon whereabouts in physical memory the code you want to jump to is located. To take an arbitrary location, say that it's 0x74A5C. Then any combination of cs and ip will do provided it satisfies the equation:

cs * 0x10 + ip = 0x74A5C

As long as that equation is satisfied, the exact values chosen are purely a matter of convenience.

The above code looks as if it is assuming that the other seg registers are all pointing at the base of memory. If that is right, as it well might be, then you might just as well use the same value for cs, and load ip accordingly.