/* * Copyright 2012, Alex Smith, alex@alex-smith.me.uk. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #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 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 };