/* * \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 #include #include #include #include #include #include #include #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 { 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 class Region_database : public Avl_tree { public: TYPE * match(addr_t base, size_t size) { if (!first()) return 0; return static_cast(first()->match(base, size)); } TYPE * meet(addr_t base, size_t size) { if (!first()) return 0; return static_cast(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(region->base())); end = max(end, static_cast(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 T * local_addr() { return static_cast(_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()); } ~Mem_region() { if (verbose) PLOG("del mem [%08lx,%08lx)", base(), base() + size()); } template T * virt_addr(addr_t addr) { return reinterpret_cast(_ds.local_addr() + (addr - base())); } }; static Region_database port_region_db; static Region_database 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(); 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 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(addr + sizeof(T) - 1); ret = X86emu::virt_addr(addr); if (verbose_mem) { unsigned v = *ret; PLOG(" io_mem: read [%p,%p) val 0x%ux", reinterpret_cast(addr), reinterpret_cast(addr + sizeof(T)), v); } return *ret; } template static void X86API write(X86emu::u32 addr, T val) { /* see description of 'read' function */ X86emu::virt_addr(addr + sizeof(T) - 1); *X86emu::virt_addr(addr) = val; if (verbose_mem) { unsigned v = val; PLOG(" io_mem: write [%p,%p) val 0x%ux", reinterpret_cast(addr), reinterpret_cast(addr + sizeof(T)), v); } } X86emu::X86EMU_memFuncs mem_funcs = { &read, &read, &read, &write, &write, &write }; /******************************** ** x86emu port-access support ** ********************************/ template static T X86API inx(X86emu::X86EMU_pioAddr addr) { T ret; unsigned short port = static_cast(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 static void X86API outx(X86emu::X86EMU_pioAddr addr, T val) { unsigned short port = static_cast(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(uint32_t addr); template uint16_t* X86emu::virt_addr(uint32_t addr); template TYPE * X86emu::virt_addr(ADDR_TYPE addr) { addr_t local_addr = static_cast(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(local_addr); else { PWRN("Invalid address 0x%08lx", local_addr); local_addr = 0; } return reinterpret_cast(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) { 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); }