mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-15 15:32:07 +00:00
This patch changes the top-level directory layout as a preparatory step for improving the tools for managing 3rd-party source codes. The rationale is described in the issue referenced below. Issue #1082
552 lines
11 KiB
C++
552 lines
11 KiB
C++
/*
|
|
* \brief x86 emulation binding and support
|
|
* \author Sebastian Sumpf
|
|
* \author Christian Helmuth
|
|
* \date 2007-09-11
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2007-2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#include <base/printf.h>
|
|
#include <base/env.h>
|
|
#include <base/sleep.h>
|
|
#include <util/avl_tree.h>
|
|
#include <util/misc_math.h>
|
|
#include <os/attached_ram_dataspace.h>
|
|
|
|
#include <io_port_session/connection.h>
|
|
#include <io_mem_session/connection.h>
|
|
#include <pci_session/connection.h>
|
|
|
|
#include "ifx86emu.h"
|
|
#include "framebuffer.h"
|
|
#include "vesa.h"
|
|
#include "hw_emul.h"
|
|
|
|
|
|
namespace X86emu {
|
|
#include"x86emu/x86emu.h"
|
|
}
|
|
|
|
using namespace Vesa;
|
|
using namespace Genode;
|
|
using X86emu::x86_mem;
|
|
using X86emu::PAGESIZE;
|
|
using X86emu::CODESIZE;
|
|
|
|
struct X86emu::X86emu_mem X86emu::x86_mem;
|
|
|
|
static const bool verbose = false;
|
|
static const bool verbose_mem = false;
|
|
static const bool verbose_port = false;
|
|
|
|
|
|
/***************
|
|
** Utilities **
|
|
***************/
|
|
|
|
class Region : public Avl_node<Region>
|
|
{
|
|
private:
|
|
|
|
addr_t _base;
|
|
size_t _size;
|
|
|
|
public:
|
|
|
|
Region(addr_t base, size_t size) : _base(base), _size(size) { }
|
|
|
|
/************************
|
|
** AVL node interface **
|
|
************************/
|
|
|
|
bool higher(Region *r) { return r->_base >= _base; }
|
|
|
|
/**
|
|
* Find region the given region fits into
|
|
*/
|
|
Region * match(addr_t base, size_t size)
|
|
{
|
|
Region *r = this;
|
|
|
|
do {
|
|
if (base >= r->_base
|
|
&& base + size <= r->_base + r->_size)
|
|
break;
|
|
|
|
if (base < r->_base)
|
|
r = r->child(LEFT);
|
|
else
|
|
r = r->child(RIGHT);
|
|
} while (r);
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Find region the given region meets
|
|
*
|
|
* \return Region overlapping or just meeting (can be merged into
|
|
* super region)
|
|
*/
|
|
Region * meet(addr_t base, size_t size)
|
|
{
|
|
Region *r = this;
|
|
|
|
do {
|
|
if ((r->_base <= base && r->_base + r->_size >= base)
|
|
|| (base <= r->_base && base + size >= r->_base))
|
|
break;
|
|
|
|
if (base < r->_base)
|
|
r = r->child(LEFT);
|
|
else
|
|
r = r->child(RIGHT);
|
|
} while (r);
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Log region
|
|
*/
|
|
void print_regions()
|
|
{
|
|
if (child(LEFT))
|
|
child(LEFT)->print_regions();
|
|
|
|
printf(" [%08lx,%08lx)\n", _base, _base + _size);
|
|
|
|
if (child(RIGHT))
|
|
child(RIGHT)->print_regions();
|
|
}
|
|
|
|
addr_t base() const { return _base; }
|
|
size_t size() const { return _size; }
|
|
};
|
|
|
|
|
|
template <typename TYPE>
|
|
class Region_database : public Avl_tree<Region>
|
|
{
|
|
public:
|
|
|
|
TYPE * match(addr_t base, size_t size)
|
|
{
|
|
if (!first()) return 0;
|
|
|
|
return static_cast<TYPE *>(first()->match(base, size));
|
|
}
|
|
|
|
TYPE * meet(addr_t base, size_t size)
|
|
{
|
|
if (!first()) return 0;
|
|
|
|
return static_cast<TYPE *>(first()->meet(base, size));
|
|
}
|
|
|
|
TYPE * get_region(addr_t base, size_t size)
|
|
{
|
|
TYPE *region;
|
|
|
|
/* look for match and return if found */
|
|
if ((region = match(base, size)))
|
|
return region;
|
|
|
|
/*
|
|
* We try to create a new port region, but first we look if any overlapping
|
|
* resp. meeting regions already exist. These are freed and merged into a
|
|
* new super region including the new port region.
|
|
*/
|
|
|
|
addr_t beg = base, end = base + size;
|
|
|
|
while ((region = meet(beg, end - beg))) {
|
|
/* merge region into super region */
|
|
beg = min(beg, static_cast<addr_t>(region->base()));
|
|
end = max(end, static_cast<addr_t>(region->base() + region->size()));
|
|
|
|
/* destroy old region */
|
|
remove(region);
|
|
destroy(env()->heap(), region);
|
|
}
|
|
|
|
try {
|
|
region = new (env()->heap()) TYPE(beg, end - beg);
|
|
insert(region);
|
|
return region;
|
|
} catch (...) {
|
|
PERR("Access to I/O region [%08lx,%08lx) denied", beg, end);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void print_regions()
|
|
{
|
|
if (!first()) return;
|
|
|
|
first()->print_regions();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* I/O port region including corresponding IO_PORT connection
|
|
*/
|
|
class Port_region : public Region, public Io_port_connection
|
|
{
|
|
public:
|
|
|
|
Port_region(unsigned short port_base, size_t port_size)
|
|
: Region(port_base, port_size), Io_port_connection(port_base, port_size)
|
|
{
|
|
if (verbose)
|
|
PLOG("add port [%04lx,%04lx)", base(), base() + size());
|
|
}
|
|
|
|
~Port_region()
|
|
{
|
|
if (verbose)
|
|
PLOG("del port [%04lx,%04lx)", base(), base() + size());
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* I/O memory region including corresponding IO_MEM connection
|
|
*/
|
|
class Mem_region : public Region
|
|
{
|
|
private:
|
|
|
|
class Io_mem_dataspace : public Io_mem_connection
|
|
{
|
|
private:
|
|
|
|
void *_local_addr;
|
|
|
|
public:
|
|
|
|
Io_mem_dataspace(addr_t base, size_t size)
|
|
: Io_mem_connection(base, size)
|
|
{
|
|
_local_addr = env()->rm_session()->attach(dataspace());
|
|
}
|
|
|
|
~Io_mem_dataspace()
|
|
{
|
|
env()->rm_session()->detach(_local_addr);
|
|
}
|
|
|
|
Io_mem_dataspace_capability cap() { return dataspace(); }
|
|
|
|
template <typename T>
|
|
T * local_addr() { return static_cast<T *>(_local_addr); }
|
|
};
|
|
|
|
Io_mem_dataspace _ds;
|
|
|
|
public:
|
|
|
|
Mem_region(addr_t mem_base, size_t mem_size)
|
|
: Region(mem_base, mem_size), _ds(mem_base, mem_size)
|
|
{
|
|
if (verbose)
|
|
PLOG("add mem [%08lx,%08lx) @ %p", base(), base() + size(), _ds.local_addr<void>());
|
|
}
|
|
|
|
~Mem_region()
|
|
{
|
|
if (verbose)
|
|
PLOG("del mem [%08lx,%08lx)", base(), base() + size());
|
|
}
|
|
|
|
template <typename T>
|
|
T * virt_addr(addr_t addr)
|
|
{
|
|
return reinterpret_cast<T *>(_ds.local_addr<char>() + (addr - base()));
|
|
}
|
|
};
|
|
|
|
|
|
static Region_database<Port_region> port_region_db;
|
|
static Region_database<Mem_region> mem_region_db;
|
|
|
|
|
|
/**
|
|
* Setup static memory for x86emu
|
|
*/
|
|
static int map_code_area(void)
|
|
{
|
|
int err;
|
|
Ram_dataspace_capability ds_cap;
|
|
void *dummy;
|
|
|
|
/* map page0 */
|
|
if ((err = Framebuffer_drv::map_io_mem(0x0, PAGESIZE, false, &dummy))) {
|
|
PERR("Could not map page zero");
|
|
return err;
|
|
}
|
|
x86_mem.bios_addr(dummy);
|
|
|
|
/* alloc code pages in RAM */
|
|
try {
|
|
static Attached_ram_dataspace ram_ds(env()->ram_session(), CODESIZE);
|
|
dummy = ram_ds.local_addr<void>();
|
|
x86_mem.data_addr(dummy);
|
|
} catch (...) {
|
|
PERR("Could not allocate dataspace for code");
|
|
return -1;
|
|
}
|
|
|
|
/* build opcode command */
|
|
uint32_t code = 0;
|
|
code = 0xcd; /* int opcode */
|
|
code |= 0x10 << 8; /* 10h */
|
|
code |= 0xf4 << 16; /* ret opcode */
|
|
memcpy(dummy, &code, sizeof(code));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**********************************
|
|
** x86emu memory-access support **
|
|
**********************************/
|
|
|
|
template <typename T>
|
|
static T X86API read(X86emu::u32 addr)
|
|
{
|
|
/*
|
|
* Access the last byte of the T value, before actually reading the value.
|
|
*
|
|
* If the value of the address is crossing the current region boundary,
|
|
* the region behind the boundary will be allocated. Both regions will be
|
|
* merged and can be attached to a different virtual address then when
|
|
* only accessing the first bytes of the value.
|
|
*/
|
|
T * ret = X86emu::virt_addr<T>(addr + sizeof(T) - 1);
|
|
ret = X86emu::virt_addr<T>(addr);
|
|
|
|
if (verbose_mem) {
|
|
unsigned v = *ret;
|
|
PLOG(" io_mem: read [%p,%p) val 0x%ux",
|
|
reinterpret_cast<void*>(addr),
|
|
reinterpret_cast<void*>(addr + sizeof(T)), v);
|
|
}
|
|
|
|
return *ret;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
static void X86API write(X86emu::u32 addr, T val)
|
|
{
|
|
/* see description of 'read' function */
|
|
X86emu::virt_addr<T>(addr + sizeof(T) - 1);
|
|
*X86emu::virt_addr<T>(addr) = val;
|
|
|
|
if (verbose_mem) {
|
|
unsigned v = val;
|
|
PLOG(" io_mem: write [%p,%p) val 0x%ux",
|
|
reinterpret_cast<void*>(addr),
|
|
reinterpret_cast<void*>(addr + sizeof(T)), v);
|
|
}
|
|
}
|
|
|
|
|
|
X86emu::X86EMU_memFuncs mem_funcs = {
|
|
&read, &read, &read,
|
|
&write, &write, &write
|
|
};
|
|
|
|
|
|
/********************************
|
|
** x86emu port-access support **
|
|
********************************/
|
|
|
|
template <typename T>
|
|
static T X86API inx(X86emu::X86EMU_pioAddr addr)
|
|
{
|
|
T ret;
|
|
unsigned short port = static_cast<unsigned short>(addr);
|
|
|
|
if (hw_emul_handle_port_read(port, &ret))
|
|
return ret;
|
|
|
|
Port_region *region = port_region_db.get_region(port, sizeof(T));
|
|
|
|
if (!region) return 0;
|
|
|
|
switch (sizeof(T)) {
|
|
case 1:
|
|
ret = (T)region->inb(port);
|
|
break;
|
|
case 2:
|
|
ret = (T)region->inw(port);
|
|
break;
|
|
default:
|
|
ret = (T)region->inl(port);
|
|
}
|
|
|
|
if (verbose_port) {
|
|
unsigned v = ret;
|
|
PLOG("io_port: read [%04ux,%04zx) val 0x%ux", (unsigned short)addr,
|
|
addr + sizeof(T), v);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
static void X86API outx(X86emu::X86EMU_pioAddr addr, T val)
|
|
{
|
|
unsigned short port = static_cast<unsigned short>(addr);
|
|
|
|
if (hw_emul_handle_port_write(port, val))
|
|
return;
|
|
|
|
Port_region *region = port_region_db.get_region(port, sizeof(T));
|
|
|
|
if (!region) return;
|
|
|
|
if (verbose_port) {
|
|
unsigned v = val;
|
|
PLOG("io_port: write [%04ux,%04zx) val 0x%ux", (unsigned short)addr,
|
|
addr + sizeof(T), v);
|
|
}
|
|
|
|
switch (sizeof(T)) {
|
|
case 1:
|
|
region->outb(port, val);
|
|
break;
|
|
case 2:
|
|
region->outw(port, val);
|
|
break;
|
|
default:
|
|
region->outl(port, val);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Port access hooks
|
|
*/
|
|
X86emu::X86EMU_pioFuncs port_funcs = {
|
|
&inx, &inx, &inx,
|
|
&outx, &outx, &outx
|
|
};
|
|
|
|
|
|
/************************
|
|
** API implementation **
|
|
************************/
|
|
|
|
/* instantiate externally used template funciton */
|
|
template char* X86emu::virt_addr<char, uint32_t>(uint32_t addr);
|
|
template uint16_t* X86emu::virt_addr<uint16_t, uint32_t>(uint32_t addr);
|
|
|
|
template <typename TYPE, typename ADDR_TYPE>
|
|
TYPE * X86emu::virt_addr(ADDR_TYPE addr)
|
|
{
|
|
addr_t local_addr = static_cast<addr_t>(addr);
|
|
Mem_region *region;
|
|
|
|
/* retrieve local mapping for given address */
|
|
|
|
/* page 0 */
|
|
if (local_addr >= 0 && local_addr < PAGESIZE)
|
|
local_addr += x86_mem.bios_addr();
|
|
|
|
/* fake code segment */
|
|
else if (local_addr >= PAGESIZE && local_addr < (PAGESIZE + CODESIZE))
|
|
local_addr += (x86_mem.data_addr() - PAGESIZE);
|
|
|
|
/* any other I/O memory allocated on demand */
|
|
else if ((region = mem_region_db.get_region(addr & ~(PAGESIZE-1), PAGESIZE)))
|
|
return region->virt_addr<TYPE>(local_addr);
|
|
|
|
else {
|
|
PWRN("Invalid address 0x%08lx", local_addr);
|
|
local_addr = 0;
|
|
}
|
|
|
|
return reinterpret_cast<TYPE *>(local_addr);
|
|
}
|
|
|
|
|
|
uint16_t X86emu::x86emu_cmd(uint16_t eax, uint16_t ebx, uint16_t ecx,
|
|
uint16_t edi, uint16_t *out_ebx)
|
|
{
|
|
using namespace X86emu;
|
|
M.x86.R_EAX = eax; /* int10 function number */
|
|
M.x86.R_EBX = ebx;
|
|
M.x86.R_ECX = ecx;
|
|
M.x86.R_EDI = edi;
|
|
M.x86.R_IP = 0; /* address of "int10; ret" */
|
|
M.x86.R_SP = PAGESIZE; /* SS:SP pointer to stack */
|
|
M.x86.R_CS =
|
|
M.x86.R_DS =
|
|
M.x86.R_ES =
|
|
M.x86.R_SS = PAGESIZE >> 4;
|
|
|
|
X86EMU_exec();
|
|
|
|
if (out_ebx)
|
|
*out_ebx = M.x86.R_EBX;
|
|
|
|
return M.x86.R_AX;
|
|
}
|
|
|
|
|
|
int X86emu::init(void)
|
|
{
|
|
/*
|
|
* Wait until Acpi/Pci driver initialization is done to avoid potentially
|
|
* concurrently accesses by this driver and the Acpi/Pci driver to the
|
|
* graphic device (PCI config space).
|
|
*/
|
|
Pci::Connection conn;
|
|
|
|
if (map_code_area())
|
|
return -1;
|
|
|
|
if (verbose) {
|
|
PDBG("--- x86 bios area is [0x%lx - 0x%lx) ---",
|
|
x86_mem.bios_addr(), x86_mem.bios_addr() + PAGESIZE);
|
|
PDBG("--- x86 data area is [0x%lx - 0x%lx) ---",
|
|
x86_mem.data_addr(), x86_mem.data_addr() + CODESIZE);
|
|
}
|
|
|
|
X86emu::M.x86.debug = 0;
|
|
X86emu::X86EMU_setupPioFuncs(&port_funcs);
|
|
X86emu::X86EMU_setupMemFuncs(&mem_funcs);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void X86emu::print_regions()
|
|
{
|
|
printf("I/O port regions:\n");
|
|
port_region_db.print_regions();
|
|
|
|
printf("I/O memory regions:\n");
|
|
mem_region_db.print_regions();
|
|
}
|
|
|
|
void X86emu::printk(const char *format, ...)
|
|
{
|
|
va_list list;
|
|
va_start(list, format);
|
|
|
|
vprintf(format, list);
|
|
|
|
va_end(list);
|
|
}
|