470 lines
16 KiB
C
470 lines
16 KiB
C
/******************************************************************************
|
|
* Copyright (c) 2004, 2008 IBM Corporation
|
|
* Copyright (c) 2009 Pattrick Hueper <phueper@hueper.net>
|
|
* All rights reserved.
|
|
* This program and the accompanying materials
|
|
* are made available under the terms of the BSD License
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.opensource.org/licenses/bsd-license.php
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial implementation
|
|
*****************************************************************************/
|
|
|
|
#include <types.h>
|
|
#ifndef CONFIG_COREBOOT_V2
|
|
#include <cpu.h>
|
|
#endif
|
|
#include "debug.h"
|
|
#include "device.h"
|
|
#include "x86emu/x86emu.h"
|
|
#include "biosemu.h"
|
|
#ifdef CONFIG_COREBOOT_V2
|
|
#include "compat/time.h"
|
|
#else
|
|
#include <time.h>
|
|
#endif
|
|
|
|
// define a check for access to certain (virtual) memory regions (interrupt handlers, BIOS Data Area, ...)
|
|
#ifdef CONFIG_DEBUG
|
|
static u8 in_check = 0; // to avoid recursion...
|
|
u16 ebda_segment;
|
|
u32 ebda_size;
|
|
|
|
//TODO: these macros have grown so large, that they should be changed to an inline function,
|
|
//just for the sake of readability...
|
|
|
|
//declare prototypes of the functions to follow, for use in DEBUG_CHECK_VMEM_ACCESS
|
|
u8 my_rdb(u32);
|
|
u16 my_rdw(u32);
|
|
u32 my_rdl(u32);
|
|
|
|
#define DEBUG_CHECK_VMEM_READ(_addr, _rval) \
|
|
if ((debug_flags & DEBUG_CHECK_VMEM_ACCESS) && (in_check == 0)) { \
|
|
in_check = 1; \
|
|
/* determine ebda_segment and size \
|
|
* since we are using my_rdx calls, make sure, this is after setting in_check! */ \
|
|
/* offset 03 in BDA is EBDA segment */ \
|
|
ebda_segment = my_rdw(0x40e); \
|
|
/* first value in ebda is size in KB */ \
|
|
ebda_size = my_rdb(ebda_segment << 4) * 1024; \
|
|
/* check Interrupt Vector Access (0000:0000h - 0000:0400h) */ \
|
|
if (_addr < 0x400) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from Interrupt Vector %x --> %x\n", \
|
|
__func__, _addr / 4, _rval); \
|
|
} \
|
|
/* access to BIOS Data Area (0000:0400h - 0000:0500h)*/ \
|
|
else if ((_addr >= 0x400) && (addr < 0x500)) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from BIOS Data Area: addr: %x --> %x\n", \
|
|
__func__, _addr, _rval); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* access to first 64k of memory... */ \
|
|
else if (_addr < 0x10000) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from segment 0000h: addr: %x --> %x\n", \
|
|
__func__, _addr, _rval); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* read from PMM_CONV_SEGMENT */ \
|
|
else if ((_addr <= ((PMM_CONV_SEGMENT << 4) | 0xffff)) && (_addr >= (PMM_CONV_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from PMM Segment %04xh: addr: %x --> %x\n", \
|
|
__func__, PMM_CONV_SEGMENT, _addr, _rval); \
|
|
/* HALT_SYS(); */ \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* read from PNP_DATA_SEGMENT */ \
|
|
else if ((_addr <= ((PNP_DATA_SEGMENT << 4) | 0xffff)) && (_addr >= (PNP_DATA_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from PnP Data Segment %04xh: addr: %x --> %x\n", \
|
|
__func__, PNP_DATA_SEGMENT, _addr, _rval); \
|
|
/* HALT_SYS(); */ \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* read from EBDA Segment */ \
|
|
else if ((_addr <= ((ebda_segment << 4) | (ebda_size - 1))) && (_addr >= (ebda_segment << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from Extended BIOS Data Area %04xh, size: %04x: addr: %x --> %x\n", \
|
|
__func__, ebda_segment, ebda_size, _addr, _rval); \
|
|
} \
|
|
/* read from BIOS_DATA_SEGMENT */ \
|
|
else if ((_addr <= ((BIOS_DATA_SEGMENT << 4) | 0xffff)) && (_addr >= (BIOS_DATA_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: read from BIOS Data Segment %04xh: addr: %x --> %x\n", \
|
|
__func__, BIOS_DATA_SEGMENT, _addr, _rval); \
|
|
/* for PMM debugging */ \
|
|
/*if (_addr == BIOS_DATA_SEGMENT << 4) { \
|
|
X86EMU_trace_on(); \
|
|
M.x86.debug &= ~DEBUG_DECODE_NOPRINT_F; \
|
|
}*/ \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
in_check = 0; \
|
|
}
|
|
#define DEBUG_CHECK_VMEM_WRITE(_addr, _val) \
|
|
if ((debug_flags & DEBUG_CHECK_VMEM_ACCESS) && (in_check == 0)) { \
|
|
in_check = 1; \
|
|
/* determine ebda_segment and size \
|
|
* since we are using my_rdx calls, make sure, this is after setting in_check! */ \
|
|
/* offset 03 in BDA is EBDA segment */ \
|
|
ebda_segment = my_rdw(0x40e); \
|
|
/* first value in ebda is size in KB */ \
|
|
ebda_size = my_rdb(ebda_segment << 4) * 1024; \
|
|
/* check Interrupt Vector Access (0000:0000h - 0000:0400h) */ \
|
|
if (_addr < 0x400) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to Interrupt Vector %x <-- %x\n", \
|
|
__func__, _addr / 4, _val); \
|
|
} \
|
|
/* access to BIOS Data Area (0000:0400h - 0000:0500h)*/ \
|
|
else if ((_addr >= 0x400) && (addr < 0x500)) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to BIOS Data Area: addr: %x <-- %x\n", \
|
|
__func__, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* access to first 64k of memory...*/ \
|
|
else if (_addr < 0x10000) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to segment 0000h: addr: %x <-- %x\n", \
|
|
__func__, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* write to PMM_CONV_SEGMENT... */ \
|
|
else if ((_addr <= ((PMM_CONV_SEGMENT << 4) | 0xffff)) && (_addr >= (PMM_CONV_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to PMM Segment %04xh: addr: %x <-- %x\n", \
|
|
__func__, PMM_CONV_SEGMENT, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* write to PNP_DATA_SEGMENT... */ \
|
|
else if ((_addr <= ((PNP_DATA_SEGMENT << 4) | 0xffff)) && (_addr >= (PNP_DATA_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to PnP Data Segment %04xh: addr: %x <-- %x\n", \
|
|
__func__, PNP_DATA_SEGMENT, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* write to EBDA Segment... */ \
|
|
else if ((_addr <= ((ebda_segment << 4) | (ebda_size - 1))) && (_addr >= (ebda_segment << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to Extended BIOS Data Area %04xh, size: %04x: addr: %x <-- %x\n", \
|
|
__func__, ebda_segment, ebda_size, _addr, _val); \
|
|
} \
|
|
/* write to BIOS_DATA_SEGMENT... */ \
|
|
else if ((_addr <= ((BIOS_DATA_SEGMENT << 4) | 0xffff)) && (_addr >= (BIOS_DATA_SEGMENT << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to BIOS Data Segment %04xh: addr: %x <-- %x\n", \
|
|
__func__, BIOS_DATA_SEGMENT, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
/* write to current CS segment... */ \
|
|
else if ((_addr < ((M.x86.R_CS << 4) | 0xffff)) && (_addr > (M.x86.R_CS << 4))) { \
|
|
DEBUG_PRINTF_CS_IP("%s: write to CS segment %04xh: addr: %x <-- %x\n", \
|
|
__func__, M.x86.R_CS, _addr, _val); \
|
|
/* dump registers */ \
|
|
/* x86emu_dump_xregs(); */ \
|
|
} \
|
|
in_check = 0; \
|
|
}
|
|
#else
|
|
#define DEBUG_CHECK_VMEM_READ(_addr, _rval)
|
|
#define DEBUG_CHECK_VMEM_WRITE(_addr, _val)
|
|
#endif
|
|
|
|
//defined in net-snk/kernel/timer.c
|
|
extern u64 get_time(void);
|
|
|
|
void update_time(u32);
|
|
|
|
// read byte from memory
|
|
u8
|
|
my_rdb(u32 addr)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
u8 rval;
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%08x): access to VGA Memory\n",
|
|
__func__, addr);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
set_ci();
|
|
rval = *((u8 *) translated_addr);
|
|
clr_ci();
|
|
DEBUG_PRINTF_MEM("%s(%08x) VGA --> %02x\n", __func__, addr,
|
|
rval);
|
|
return rval;
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* read from virtual memory */
|
|
rval = *((u8 *) (M.mem_base + addr));
|
|
DEBUG_CHECK_VMEM_READ(addr, rval);
|
|
return rval;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//read word from memory
|
|
u16
|
|
my_rdw(u32 addr)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
u16 rval;
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%08x): access to VGA Memory\n",
|
|
__func__, addr);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
// check for legacy memory, because of the remapping to BARs, the reads must
|
|
// be byte reads...
|
|
if ((addr >= 0xa0000) && (addr < 0xc0000)) {
|
|
//read bytes a using my_rdb, because of the remapping to BARs
|
|
//words may not be contiguous in memory, so we need to translate
|
|
//every address...
|
|
rval = ((u8) my_rdb(addr)) |
|
|
(((u8) my_rdb(addr + 1)) << 8);
|
|
} else {
|
|
if ((translated_addr & (u64) 0x1) == 0) {
|
|
// 16 bit aligned access...
|
|
set_ci();
|
|
rval = in16le((void *) translated_addr);
|
|
clr_ci();
|
|
} else {
|
|
// unaligned access, read single bytes
|
|
set_ci();
|
|
rval = (*((u8 *) translated_addr)) |
|
|
(*((u8 *) translated_addr + 1) << 8);
|
|
clr_ci();
|
|
}
|
|
}
|
|
DEBUG_PRINTF_MEM("%s(%08x) VGA --> %04x\n", __func__, addr,
|
|
rval);
|
|
return rval;
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* read from virtual memory */
|
|
rval = in16le((void *) (M.mem_base + addr));
|
|
DEBUG_CHECK_VMEM_READ(addr, rval);
|
|
return rval;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//read long from memory
|
|
u32
|
|
my_rdl(u32 addr)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
u32 rval;
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%x): access to VGA Memory\n",
|
|
__func__, addr);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
// check for legacy memory, because of the remapping to BARs, the reads must
|
|
// be byte reads...
|
|
if ((addr >= 0xa0000) && (addr < 0xc0000)) {
|
|
//read bytes a using my_rdb, because of the remapping to BARs
|
|
//dwords may not be contiguous in memory, so we need to translate
|
|
//every address...
|
|
rval = ((u8) my_rdb(addr)) |
|
|
(((u8) my_rdb(addr + 1)) << 8) |
|
|
(((u8) my_rdb(addr + 2)) << 16) |
|
|
(((u8) my_rdb(addr + 3)) << 24);
|
|
} else {
|
|
if ((translated_addr & (u64) 0x3) == 0) {
|
|
// 32 bit aligned access...
|
|
set_ci();
|
|
rval = in32le((void *) translated_addr);
|
|
clr_ci();
|
|
} else {
|
|
// unaligned access, read single bytes
|
|
set_ci();
|
|
rval = (*((u8 *) translated_addr)) |
|
|
(*((u8 *) translated_addr + 1) << 8) |
|
|
(*((u8 *) translated_addr + 2) << 16) |
|
|
(*((u8 *) translated_addr + 3) << 24);
|
|
clr_ci();
|
|
}
|
|
}
|
|
DEBUG_PRINTF_MEM("%s(%08x) VGA --> %08x\n", __func__, addr,
|
|
rval);
|
|
//HALT_SYS();
|
|
return rval;
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* read from virtual memory */
|
|
rval = in32le((void *) (M.mem_base + addr));
|
|
switch (addr) {
|
|
case 0x46c:
|
|
//BDA Time Data, update it, before reading
|
|
update_time(rval);
|
|
rval = in32le((void *) (M.mem_base + addr));
|
|
break;
|
|
}
|
|
DEBUG_CHECK_VMEM_READ(addr, rval);
|
|
return rval;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//write byte to memory
|
|
void
|
|
my_wrb(u32 addr, u8 val)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%x, %x): access to VGA Memory\n",
|
|
__func__, addr, val);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
set_ci();
|
|
*((u8 *) translated_addr) = val;
|
|
clr_ci();
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* write to virtual memory */
|
|
DEBUG_CHECK_VMEM_WRITE(addr, val);
|
|
*((u8 *) (M.mem_base + addr)) = val;
|
|
}
|
|
}
|
|
|
|
void
|
|
my_wrw(u32 addr, u16 val)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%x, %x): access to VGA Memory\n",
|
|
__func__, addr, val);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
// check for legacy memory, because of the remapping to BARs, the reads must
|
|
// be byte reads...
|
|
if ((addr >= 0xa0000) && (addr < 0xc0000)) {
|
|
//read bytes a using my_rdb, because of the remapping to BARs
|
|
//words may not be contiguous in memory, so we need to translate
|
|
//every address...
|
|
my_wrb(addr, (u8) (val & 0x00FF));
|
|
my_wrb(addr + 1, (u8) ((val & 0xFF00) >> 8));
|
|
} else {
|
|
if ((translated_addr & (u64) 0x1) == 0) {
|
|
// 16 bit aligned access...
|
|
set_ci();
|
|
out16le((void *) translated_addr, val);
|
|
clr_ci();
|
|
} else {
|
|
// unaligned access, write single bytes
|
|
set_ci();
|
|
*((u8 *) translated_addr) =
|
|
(u8) (val & 0x00FF);
|
|
*((u8 *) translated_addr + 1) =
|
|
(u8) ((val & 0xFF00) >> 8);
|
|
clr_ci();
|
|
}
|
|
}
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* write to virtual memory */
|
|
DEBUG_CHECK_VMEM_WRITE(addr, val);
|
|
out16le((void *) (M.mem_base + addr), val);
|
|
}
|
|
}
|
|
void
|
|
my_wrl(u32 addr, u32 val)
|
|
{
|
|
unsigned long translated_addr = addr;
|
|
u8 translated = biosemu_dev_translate_address(&translated_addr);
|
|
if (translated != 0) {
|
|
//translation successfull, access VGA Memory (BAR or Legacy...)
|
|
DEBUG_PRINTF_MEM("%s(%x, %x): access to VGA Memory\n",
|
|
__func__, addr, val);
|
|
//DEBUG_PRINTF_MEM("%s(%08x): translated_addr: %llx\n", __func__, addr, translated_addr);
|
|
// check for legacy memory, because of the remapping to BARs, the reads must
|
|
// be byte reads...
|
|
if ((addr >= 0xa0000) && (addr < 0xc0000)) {
|
|
//read bytes a using my_rdb, because of the remapping to BARs
|
|
//words may not be contiguous in memory, so we need to translate
|
|
//every address...
|
|
my_wrb(addr, (u8) (val & 0x000000FF));
|
|
my_wrb(addr + 1, (u8) ((val & 0x0000FF00) >> 8));
|
|
my_wrb(addr + 2, (u8) ((val & 0x00FF0000) >> 16));
|
|
my_wrb(addr + 3, (u8) ((val & 0xFF000000) >> 24));
|
|
} else {
|
|
if ((translated_addr & (u64) 0x3) == 0) {
|
|
// 32 bit aligned access...
|
|
set_ci();
|
|
out32le((void *) translated_addr, val);
|
|
clr_ci();
|
|
} else {
|
|
// unaligned access, write single bytes
|
|
set_ci();
|
|
*((u8 *) translated_addr) =
|
|
(u8) (val & 0x000000FF);
|
|
*((u8 *) translated_addr + 1) =
|
|
(u8) ((val & 0x0000FF00) >> 8);
|
|
*((u8 *) translated_addr + 2) =
|
|
(u8) ((val & 0x00FF0000) >> 16);
|
|
*((u8 *) translated_addr + 3) =
|
|
(u8) ((val & 0xFF000000) >> 24);
|
|
clr_ci();
|
|
}
|
|
}
|
|
} else if (addr > M.mem_size) {
|
|
DEBUG_PRINTF("%s(%08x): Memory Access out of range!\n",
|
|
__func__, addr);
|
|
//disassemble_forward(M.x86.saved_cs, M.x86.saved_ip, 1);
|
|
HALT_SYS();
|
|
} else {
|
|
/* write to virtual memory */
|
|
DEBUG_CHECK_VMEM_WRITE(addr, val);
|
|
out32le((void *) (M.mem_base + addr), val);
|
|
}
|
|
}
|
|
|
|
//update time in BIOS Data Area
|
|
//DWord at offset 0x6c is the timer ticks since midnight, timer is running at 18Hz
|
|
//byte at 0x70 is timer overflow (set if midnight passed since last call to interrupt 1a function 00
|
|
//cur_val is the current value, of offset 6c...
|
|
void
|
|
update_time(u32 cur_val)
|
|
{
|
|
//for convenience, we let the start of timebase be at midnight, we currently dont support
|
|
//real daytime anyway...
|
|
u64 ticks_per_day = tb_freq * 60 * 24;
|
|
// at 18Hz a period is ~55ms, converted to ticks (tb_freq is ticks/second)
|
|
u32 period_ticks = (55 * tb_freq) / 1000;
|
|
u64 curr_time = get_time();
|
|
u64 ticks_since_midnight = curr_time % ticks_per_day;
|
|
u32 periods_since_midnight = ticks_since_midnight / period_ticks;
|
|
// if periods since midnight is smaller than last value, set overflow
|
|
// at BDA Offset 0x70
|
|
if (periods_since_midnight < cur_val) {
|
|
my_wrb(0x470, 1);
|
|
}
|
|
// store periods since midnight at BDA offset 0x6c
|
|
my_wrl(0x46c, periods_since_midnight);
|
|
}
|