⛏️ index : haiku.git

/*
 * Copyright 2012, Alex Smith, alex@alex-smith.me.uk.
 * Distributed under the terms of the MIT License.
 */


#include <drivers/bios.h>

#include <KernelExport.h>

#include <AutoDeleter.h>

#include <arch/x86/arch_cpu.h>
#include <vm/vm.h>
#include <vm/VMAddressSpace.h>

#include "x86emu.h"


struct BIOSState {
	BIOSState()
		:
		mapped_address(0),
		area(-1),
		allocated_size(0)
	{
	}

	~BIOSState()
	{
		if (area >= 0)
			delete_area(area);
	}

	addr_t		mapped_address;
	area_id		area;
	size_t		allocated_size;
};


// BIOS memory layout definitions.

// Bottom of RAM contains the interrupt jump vectors
// They need to be copied to our memory area
static const uint32 kBDABase = 0;
static const uint32 kBDASize = 0x1000;

// Remaining part of RAM (left uninitialized initially)
static const uint32 kRAMBase = 0x1000;
static const uint32 kRAMSize = 0x8f000;

// Upper part of address space: a bit of RAM, the video RAM, then the ROMs
// Copied to the memory area as well, so the BIOS can be patched if needed.
static const uint32 kEBDABase = 0x90000;
static const uint32 kEBDASize = 0x70000;

// Total size of the above
static const uint32 kTotalSize = 0x100000;

// The stack is dynamically allocated somewhere inside the RAM
static const uint32 kStackSize = 0x1000;


static sem_id sBIOSLock;
static BIOSState* sCurrentBIOSState;


//	#pragma mark - X86EMU hooks.


static x86emuu8
x86emu_pio_inb(X86EMU_pioAddr port)
{
	return in8(port);
}


static x86emuu16
x86emu_pio_inw(X86EMU_pioAddr port)
{
	return in16(port);
}


static x86emuu32
x86emu_pio_inl(X86EMU_pioAddr port)
{
	return in32(port);
}


static void
x86emu_pio_outb(X86EMU_pioAddr port, x86emuu8 data)
{
	out8(data, port);
}


static void
x86emu_pio_outw(X86EMU_pioAddr port, x86emuu16 data)
{
	out16(data, port);
}


static void
x86emu_pio_outl(X86EMU_pioAddr port, x86emuu32 data)
{
	out32(data, port);
}


static X86EMU_pioFuncs x86emu_pio_funcs = {
	x86emu_pio_inb,
	x86emu_pio_inw,
	x86emu_pio_inl,
	x86emu_pio_outb,
	x86emu_pio_outw,
	x86emu_pio_outl,
};


static x86emuu8
x86emu_mem_rdb(x86emuu32 addr)
{
	return *(x86emuu8*)((addr_t)addr + sCurrentBIOSState->mapped_address);
}


static x86emuu16
x86emu_mem_rdw(x86emuu32 addr)
{
	return *(x86emuu16*)((addr_t)addr + sCurrentBIOSState->mapped_address);
}


static x86emuu32
x86emu_mem_rdl(x86emuu32 addr)
{
	return *(x86emuu32*)((addr_t)addr + sCurrentBIOSState->mapped_address);
}


static void
x86emu_mem_wrb(x86emuu32 addr, x86emuu8 val)
{
	*(x86emuu8*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
}


static void
x86emu_mem_wrw(x86emuu32 addr, x86emuu16 val)
{
	*(x86emuu16*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
}


static void
x86emu_mem_wrl(x86emuu32 addr, x86emuu32 val)
{
	*(x86emuu32*)((addr_t)addr + sCurrentBIOSState->mapped_address) = val;
}


static X86EMU_memFuncs x86emu_mem_funcs = {
	x86emu_mem_rdb,
	x86emu_mem_rdw,
	x86emu_mem_rdl,
	x86emu_mem_wrb,
	x86emu_mem_wrw,
	x86emu_mem_wrl,
};


//	#pragma mark -


static void*
bios_allocate_mem(bios_state* state, size_t size)
{
	// Simple allocator for memory to pass to the BIOS. No need for a complex
	// allocator here, there is only a few allocations per BIOS usage.

	size = ROUNDUP(size, 4);

	if (state->allocated_size + size > kRAMSize)
		return NULL;

	void* address
		= (void*)(state->mapped_address + kRAMBase + state->allocated_size);
	state->allocated_size += size;

	return address;
}


static uint32
bios_physical_address(bios_state* state, void* virtualAddress)
{
	return (uint32)((addr_t)virtualAddress - state->mapped_address);
}


static void*
bios_virtual_address(bios_state* state, uint32 physicalAddress)
{
	return (void*)((addr_t)physicalAddress + state->mapped_address);
}


static status_t
bios_prepare(bios_state** _state)
{
	status_t status;

	BIOSState* state = new(std::nothrow) BIOSState;
	if (state == NULL)
		return B_NO_MEMORY;
	ObjectDeleter<BIOSState> stateDeleter(state);

	// Reserve a chunk of address space to map at.
	status = vm_reserve_address_range(VMAddressSpace::KernelID(),
		(void**)&state->mapped_address, B_ANY_KERNEL_ADDRESS,
		kTotalSize, 0);
	if (status != B_OK)
		return status;

	// Map RAM for for the BIOS.
	state->area = create_area("bios", (void**)&state->mapped_address,
		B_EXACT_ADDRESS, kTotalSize, B_NO_LOCK, B_KERNEL_READ_AREA
			| B_KERNEL_WRITE_AREA);
	if (state->area < B_OK) {
		vm_unreserve_address_range(VMAddressSpace::KernelID(),
			(void*)state->mapped_address, kTotalSize);
		return state->area;
	}

	// Copy the interrupt vectors and the BIOS data area.
	status = vm_memcpy_from_physical((void*)state->mapped_address, kBDABase,
		kBDASize, false);
	if (status != B_OK) {
		vm_unreserve_address_range(VMAddressSpace::KernelID(),
			(void*)state->mapped_address, kTotalSize);
		return status;
	}

	// Map the extended BIOS data area and VGA memory.
	void* address = (void*)(state->mapped_address + kEBDABase);
	status = vm_memcpy_from_physical(address, kEBDABase, kEBDASize, false);

	if (status != B_OK) {
		vm_unreserve_address_range(VMAddressSpace::KernelID(),
			(void*)state->mapped_address, kTotalSize);
		return status;
	}

	// Attempt to acquire exclusive access to the BIOS.
	acquire_sem(sBIOSLock);

	stateDeleter.Detach();
	*_state = state;
	return B_OK;
}


static status_t
bios_interrupt(bios_state* state, uint8 vector, bios_regs* regs)
{
	sCurrentBIOSState = state;

	// Allocate a stack.
	void* stack = bios_allocate_mem(state, kStackSize);
	if (stack == NULL)
		return B_NO_MEMORY;
	uint32 stackTop = bios_physical_address(state, stack) + kStackSize;

	// X86EMU finishes when it encounters a HLT instruction, allocate a
	// byte to store one in and set the return address of the interrupt to
	// point to it.
	void* halt = bios_allocate_mem(state, 1);
	if (halt == NULL)
		return B_NO_MEMORY;
	*(uint8*)halt = 0xF4;

	// Copy in the registers.
	memset(&M, 0, sizeof(M));
	M.x86.R_EAX = regs->eax;
	M.x86.R_EBX = regs->ebx;
	M.x86.R_ECX = regs->ecx;
	M.x86.R_EDX = regs->edx;
	M.x86.R_EDI = regs->edi;
	M.x86.R_ESI = regs->esi;
	M.x86.R_EBP = regs->ebp;
	M.x86.R_EFLG = regs->eflags | X86_EFLAGS_INTERRUPT | X86_EFLAGS_RESERVED1;
	M.x86.R_EIP = bios_physical_address(state, halt);
	M.x86.R_CS = 0x0;
	M.x86.R_DS = regs->ds;
	M.x86.R_ES = regs->es;
	M.x86.R_FS = regs->fs;
	M.x86.R_GS = regs->gs;
	M.x86.R_SS = stackTop >> 4;
	M.x86.R_ESP = stackTop - (M.x86.R_SS << 4);

	/* Run the interrupt. */
	X86EMU_setupPioFuncs(&x86emu_pio_funcs);
	X86EMU_setupMemFuncs(&x86emu_mem_funcs);
	X86EMU_prepareForInt(vector);
	X86EMU_exec();

	// Copy back modified data.
	regs->eax = M.x86.R_EAX;
	regs->ebx = M.x86.R_EBX;
	regs->ecx = M.x86.R_ECX;
	regs->edx = M.x86.R_EDX;
	regs->edi = M.x86.R_EDI;
	regs->esi = M.x86.R_ESI;
	regs->ebp = M.x86.R_EBP;
	regs->eflags = M.x86.R_EFLG;
	regs->ds = M.x86.R_DS;
	regs->es = M.x86.R_ES;
	regs->fs = M.x86.R_FS;
	regs->gs = M.x86.R_GS;

	return B_OK;
}


static void
bios_finish(bios_state* state)
{
	release_sem(sBIOSLock);

	delete state;
}


static status_t
std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
			sBIOSLock = create_sem(1, "bios lock");
			if (sBIOSLock < B_OK)
				return sBIOSLock;

			return B_OK;
		case B_MODULE_UNINIT:
			delete_sem(sBIOSLock);
			return B_OK;
		default:
			return B_ERROR;
	}
}


static bios_module_info sBIOSModule = {
	{
		B_BIOS_MODULE_NAME,
		0,
		std_ops
	},

	bios_prepare,
	bios_interrupt,
	bios_finish,
	bios_allocate_mem,
	bios_physical_address,
	bios_virtual_address
};


module_info *modules[] = {
	(module_info*)&sBIOSModule,
	NULL
};