/*
* Copyright 2004-2010, Axel DΓΆrfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
/*! This file contains code to call BIOS functions out of a protected
mode environment. It doesn't use the virtual86 mode - it switches
to real mode, make the BIOS call, and switch back to protected
mode again. It's meant to be used in a single-threaded boot loader,
not in a multi-tasking operating system.
It relies on the real mode segment descriptors found in shell.S.
*/
#define FUNCTION(x) .globl x
#define REAL_MODE_STACK 0x9000
// the location of the stack in real mode
#define SAVED_ESP 0x10000
#define SAVED_CR3 0x10004
#define SAVED_EAX 0x10008
#define SAVED_ES 0x1000c
#define SAVED_FLAGS 0x10010
#define SAVED_EBP 0x10014
// we're overwriting the start of our boot loader to hold some
// temporary values - the first 1024 bytes of it are used at
// startup only, and we avoid some linking issues this way
.text
.code32
/*! This function brings you back to protected mode after you've
switched to it using switch_to_real_mode().
Should restore the whole environment to what it looked like
before. Clobbers %eax.
*/
FUNCTION(switch_to_protected_mode)
cli // turn off interrupts
.code16
movl %cr0, %eax // set the PE bit (0) to switch to protected mode
orb $0x1, %al
movl %eax, %cr0
.code32
.byte 0x66 // jump to the protected mode segment
ljmp $0x8, $_protected_code_segment
_protected_code_segment:
movw $0x10, %ax // setup data and stack selectors
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
// turn on paging again
movl SAVED_CR3, %eax // restore the saved page directory
orl %eax, %eax // is there a paging directory at all?
jz _no_paging
movl %eax, %cr3
movl %cr0, %eax // set the PG bit (31) to enable paging
orl $0x80000000, %eax
movl %eax, %cr0
_no_paging:
// save the return address so that we can pick it up again later
movl (%esp), %eax
movl %eax, REAL_MODE_STACK
// setup protected stack frame again
movl SAVED_ESP, %eax
movl %eax, %esp
movl SAVED_EBP, %ebp
// copy the return address to the current stack
movl REAL_MODE_STACK, %eax
movl %eax, (%esp)
ret
//--------------------------------------------------------------
/*! Switches from protected mode back to real mode.
It will disable paging and set the real mode segment selectors to 0x1000,
except for the stack selector, which will be 0x0 (the stack is at 0x9000
which is where the BFS boot loader puts it as well).
Clobbers %eax.
*/
FUNCTION(switch_to_real_mode)
// save the %esp register
movl %esp, %eax
movl %eax, SAVED_ESP
movl %ebp, SAVED_EBP
// put the return address on the real mode stack
movl (%esp), %eax
movl %eax, REAL_MODE_STACK
// disable paging
movl %cr3, %eax // save the page directory address
movl %eax, SAVED_CR3
movl %cr0, %eax
andl $0x7fffffff, %eax // clear PG bit (31)
movl %eax, %cr0
xor %eax, %eax // clear page directory to flush TLBs
movl %eax, %cr3
// setup real mode stack
movl $REAL_MODE_STACK, %eax
movl %eax, %esp
movl %eax, %ebp
// setup selectors to point to our 16 bit segments
movw $0x20, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw $0x28, %ax
movw %ax, %ss
ljmp $0x18, $(_almost_real_code_segment - 0x10000)
_almost_real_code_segment:
movl %cr0, %eax // switch to real mode
andb $0xfe, %al // clear PE bit (0)
movl %eax, %cr0
.byte 0x66
ljmp $0x1000, $(_real_code_segment - 0x10000)
_real_code_segment:
.code16
movw $0x1000, %ax // setup data & stack segments
movw %ax, %ds // data in segment 0x1000,
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
xor %ax, %ax // stack in segment 0x0
movw %ax, %ss
sti // turn on interrupts again
ret
.code32
//--------------------------------------------------------------
/*! void call_bios_internal(uint8 num, struct bios_regs *regs)
Does a BIOS call by triggering a software interrupt in real
mode.
*/
FUNCTION(call_bios_internal)
pushal
pushfl
// make sure the correct IDT is in place
lidt idt_descriptor
// get the interrupt vector and patch the instruction at the target address
movl 40(%esp), %eax
mov %al, int_number
// Fills registers from the passed in structure
// Since switch_to_real_mode() clobbers %eax, we have to handle
// it specially here (by temporarily storing it to an arbitrary
// memory location, SAVED_EAX)
movl 44(%esp), %ebp
movl (%ebp), %eax
movl %eax, SAVED_EAX
movl 4(%ebp), %ebx
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
movl 16(%ebp), %esi
movl 20(%ebp), %edi
movw 24(%ebp), %ax
movw %ax, SAVED_ES
call switch_to_real_mode
.code16
// restore %eax and %es from saved location
movl (SAVED_EAX - 0x10000), %eax
movw (SAVED_ES - 0x10000), %es
// call the interrupt (will be dynamically changed above)
.byte 0xcd
int_number:
.byte 0
// we're interested in the flags state as well
pushf
// save %eax from the call
movl %eax, (SAVED_EAX - 0x10000)
// save flags from the call
pop %ax
movw %ax, (SAVED_FLAGS - 0x10000)
// back to protected mode
call switch_to_protected_mode
.code32
// store the register state into the structure that has been passed in
movl 44(%esp), %eax
movl %ebx, 4(%eax)
movl %ecx, 8(%eax)
movl %edx, 12(%eax)
movl %esi, 16(%eax)
movl %edi, 20(%eax)
movl SAVED_EAX, %ecx // special handling for %eax and flags
movl %ecx, (%eax)
movw SAVED_FLAGS, %cx
movw %cx, 26(%eax)
popfl
popal
ret
//--------------------------------------------------------------
/*! uint32 boot_key_in_keyboard_buffer()
Search keyboard buffer for the keycodes for space in the first run, and,
if not found - for the escape key at the last two positions of this buffer
*/
FUNCTION(boot_key_in_keyboard_buffer)
pushal
pushfl
// make sure the correct IDT is in place
lidt idt_descriptor
call switch_to_real_mode
.code16
cld
push %ds
xorl %eax, %eax
mov %ax, %ds
mov $0x41E, %si // BIOS kbd buffer
search_cycle1:
lodsw
cmp $0x3920, %ax // test space key
jz to_ret
cmp $0x440, %si
jnz search_cycle1
addw 0x41C, %si
movw -0x42(%si), %ax
cmp $0x011B, %ax // test ESC key
jz to_ret
movw -0x44(%si), %ax
cmp $0x011B, %ax // test ESC key
to_ret:
pop %ds
// save %eax
movl %eax, (SAVED_EAX - 0x10000)
call switch_to_protected_mode
.code32
popfl
popal
// restore %eax
movl SAVED_EAX, %eax
ret
//--------------------------------------------------------------
.globl idt_descriptor
idt_descriptor:
.short 0x7ff // IDT at 0x0, default real mode location
.long 0x0