248 lines
7.2 KiB
C
248 lines
7.2 KiB
C
/* -*- Mode: C; c-basic-offset: 3 -*-
|
|
*
|
|
* bios.c - Make real-mode BIOS calls from protected mode.
|
|
* For simplicity and small size, this implementation
|
|
* switches back to real-mode rather than using virtual 8086
|
|
* mode. A v86 mode implementation may be more robust.
|
|
*
|
|
* This file is part of Metalkit, a simple collection of modules for
|
|
* writing software that runs on the bare metal. Get the latest code
|
|
* at http://svn.navi.cx/misc/trunk/metalkit/
|
|
*
|
|
* Copyright (c) 2008-2009 Micah Dowty
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "bios.h"
|
|
#include "boot.h"
|
|
#include "intr.h"
|
|
|
|
|
|
/*
|
|
* BIOSCallInternal --
|
|
*
|
|
* Internal implementation of BIOS_Call. This is a C function which
|
|
* wraps the assembly-language internal implementation. The only
|
|
* reason to use C here, really, is so we can easily calculate
|
|
* offsets into our shared C structures.
|
|
*
|
|
* This function must not make any function calls, since we need to
|
|
* be able to trust the value of %esp. This is also why it must not
|
|
* be inlined into BIOS_Call itself.
|
|
*/
|
|
|
|
static __attribute__((noinline)) void
|
|
BIOSCallInternal(void)
|
|
{
|
|
/*
|
|
* Save registers and stack in a safe place.
|
|
*/
|
|
asm volatile ("pusha");
|
|
asm volatile ("mov %%esp, %0" :"=m" (BIOS_SHARED->esp));
|
|
|
|
/*
|
|
* Jump the the relocated 16-bit trampoline (source code below).
|
|
*/
|
|
asm volatile ("ljmp %0, %1"
|
|
:: "i" (BOOT_CODE16_SEG), "i" (BIOS_SHARED->trampoline));
|
|
|
|
/*
|
|
* This is where we return from the relocated trampoline.
|
|
* We're back in protected mode, but the data segments
|
|
* are still 16-bit. Restore them.
|
|
*/
|
|
|
|
asm volatile ("BIOSReturn32: \n"
|
|
"mov %0, %%ax \n"
|
|
"mov %%ax, %%ss \n"
|
|
"mov %%ax, %%ds \n"
|
|
"mov %%ax, %%es \n"
|
|
"mov %%ax, %%fs \n"
|
|
"mov %%ax, %%gs \n"
|
|
:: "i" (BOOT_DATA_SEG));
|
|
|
|
/*
|
|
* Restore our stack and saved registers.
|
|
* Now we can safely execute C code again.
|
|
*/
|
|
|
|
asm volatile("mov %0, %%esp" ::"m" (BIOS_SHARED->esp));
|
|
asm volatile ("popa");
|
|
|
|
/*
|
|
* Return here. The rest of this code is never run directly,
|
|
* but we need to prevent GCC from optimizing it out.
|
|
*/
|
|
asm volatile("jmp BIOSTrampolineEnd\n");
|
|
|
|
/*
|
|
* This is a 16-bit assembly-language trampoline, relocated at
|
|
* runtime to low memory, which actually makes the BIOS call. It
|
|
* handles saving/restoring registers, and it switches in and out
|
|
* of real mode.
|
|
*
|
|
* This code is never run directly.
|
|
*/
|
|
|
|
asm volatile("BIOSTrampoline: .code16");
|
|
|
|
/*
|
|
* Switch to our 16-bit data segment.
|
|
*/
|
|
asm volatile("movw %0, %%ax \n"
|
|
"movw %%ax, %%ds \n"
|
|
"movw %%ax, %%es \n"
|
|
"movw %%ax, %%ss \n"
|
|
:: "i" (BOOT_DATA16_SEG));
|
|
|
|
/*
|
|
* Disable protected mode.
|
|
*/
|
|
asm volatile("movl %cr0, %eax \n"
|
|
"andl $(~1), %eax \n"
|
|
"movl %eax, %cr0 \n");
|
|
|
|
/*
|
|
* Do another long jump to reset the real-mode %cs
|
|
* register to a valid paragraph number. Right now
|
|
* it's still a protected-mode-style selector index.
|
|
*
|
|
* XXX: I'm not sure how to do this address calculation cleanly.
|
|
* Currently I'm hardcoding the address of the relocated trampoline.
|
|
*/
|
|
asm volatile("ljmp $0, $(BIOSTrampolineCS16 - BIOSTrampoline + 0x7C00)\n"
|
|
"BIOSTrampolineCS16: \n");
|
|
|
|
/*
|
|
* Set up the real-mode stack and %cs register.
|
|
*/
|
|
asm volatile("xorw %%ax, %%ax \n"
|
|
"mov %%ax, %%ss \n"
|
|
"mov %0, %%esp \n"
|
|
:: "i" (&BIOS_SHARED->stackTop[-sizeof(Regs)]));
|
|
|
|
/*
|
|
* Pop Regs off the stack.
|
|
*/
|
|
asm volatile("pop %ds \n"
|
|
"pop %es \n"
|
|
"pop %eax \n" // Ignore EFLAGS value.
|
|
"popal \n");
|
|
|
|
/*
|
|
* This interrupt instruction is a placeholder that gets
|
|
* patched at runtime (after relocation) to point to the
|
|
* right interrupt vector.
|
|
*/
|
|
asm volatile("BIOSTrampolineVector: \n"
|
|
"int $0xFF");
|
|
|
|
/*
|
|
* Push Regs back onto the stack.
|
|
*/
|
|
asm volatile("pushal \n"
|
|
"pushfl \n"
|
|
"push %es \n"
|
|
"push %ds \n");
|
|
|
|
/*
|
|
* Enable protected mode.
|
|
*/
|
|
asm volatile("movl %cr0, %eax \n"
|
|
"orl $1, %eax \n"
|
|
"movl %eax, %cr0 \n");
|
|
|
|
/*
|
|
* Return via a long 16-to-32 bit jump.
|
|
*/
|
|
asm volatile("data32 ljmp %0, $BIOSReturn32 \n"
|
|
:: "i" (BOOT_CODE_SEG));
|
|
|
|
asm volatile("BIOSTrampolineEnd: .code32 \n");
|
|
}
|
|
|
|
extern struct {
|
|
uint16 limit;
|
|
uint32 base;
|
|
} PACKED IDTDesc;
|
|
|
|
/*
|
|
* BIOS_Call --
|
|
*
|
|
* Make BIOS calls after boot, by temporarily switching
|
|
* back into real mode.
|
|
*
|
|
* This function relocates the trampoline and stack into
|
|
* real-mode-addressable low memory, then makes a 32-to-16-bit jump
|
|
* into the trampoline.
|
|
*/
|
|
|
|
fastcall void
|
|
BIOS_Call(uint8 vector, Regs *regs)
|
|
{
|
|
extern uint8 BIOSTrampoline[];
|
|
extern uint8 BIOSTrampolineVector[];
|
|
extern uint8 BIOSTrampolineEnd[];
|
|
const uint32 trampSize = (uint8*)BIOSTrampolineEnd - (uint8*)BIOSTrampoline;
|
|
const uint32 vectorOffset = (uint8*)BIOSTrampolineVector - (uint8*)BIOSTrampoline + 1;
|
|
|
|
Bool iFlag = Intr_Save();
|
|
Intr_Disable();
|
|
|
|
/*
|
|
* Relocate the trampoline code itself.
|
|
*/
|
|
memcpy(BIOS_SHARED->trampoline, BIOSTrampoline, trampSize);
|
|
|
|
/*
|
|
* Save the 32-bit IDT descriptor, and set up a legacy 256-entry
|
|
* 16-bit IDT descriptor.
|
|
*/
|
|
asm volatile("sidt %0" : "=m" (BIOS_SHARED->idtr32));
|
|
BIOS_SHARED->idtr16.base = 0;
|
|
BIOS_SHARED->idtr16.limit = 0x3ff;
|
|
asm volatile("lidt %0" :: "m" (BIOS_SHARED->idtr16));
|
|
|
|
/*
|
|
* Binary-patch the trampoline code with the right interrupt vector.
|
|
*/
|
|
BIOS_SHARED->trampoline[vectorOffset] = vector;
|
|
|
|
/*
|
|
* Copy Regs onto the top of the 16-bit stack.
|
|
*/
|
|
memcpy(&BIOS_SHARED->stackTop[-sizeof *regs], regs, sizeof *regs);
|
|
|
|
BIOSCallInternal();
|
|
|
|
/* Copy Regs back */
|
|
memcpy(regs, &BIOS_SHARED->stackTop[-sizeof *regs], sizeof *regs);
|
|
|
|
/*
|
|
* Back to 32-bit IDT.
|
|
*/
|
|
asm volatile("lidt %0" :: "m" (BIOS_SHARED->idtr32));
|
|
|
|
Intr_Restore(iFlag);
|
|
}
|