2570 lines
70 KiB
C
2570 lines
70 KiB
C
|
|
/*
|
||
|
|
* PC emulator
|
||
|
|
*
|
||
|
|
* Copyright (c) 2011-2017 Fabrice Bellard
|
||
|
|
*
|
||
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
|
* in the Software without restriction, including without limitation the rights
|
||
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
|
* furnished to do so, subject to the following conditions:
|
||
|
|
*
|
||
|
|
* The above copyright notice and this permission notice shall be included in
|
||
|
|
* all copies or substantial portions of the Software.
|
||
|
|
*
|
||
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
|
* THE SOFTWARE.
|
||
|
|
*/
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdarg.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <assert.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include <sys/time.h>
|
||
|
|
|
||
|
|
#include "cutils.h"
|
||
|
|
#include "iomem.h"
|
||
|
|
#include "virtio.h"
|
||
|
|
#include "x86_cpu.h"
|
||
|
|
#include "machine.h"
|
||
|
|
#include "pci.h"
|
||
|
|
#include "ide.h"
|
||
|
|
#include "ps2.h"
|
||
|
|
|
||
|
|
#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__))
|
||
|
|
#define USE_KVM
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
#include <linux/kvm.h>
|
||
|
|
#include <sys/mman.h>
|
||
|
|
#include <sys/ioctl.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <sys/time.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
//#define DEBUG_BIOS
|
||
|
|
//#define DUMP_IOPORT
|
||
|
|
|
||
|
|
/***********************************************************/
|
||
|
|
/* cmos emulation */
|
||
|
|
|
||
|
|
//#define DEBUG_CMOS
|
||
|
|
|
||
|
|
#define RTC_SECONDS 0
|
||
|
|
#define RTC_SECONDS_ALARM 1
|
||
|
|
#define RTC_MINUTES 2
|
||
|
|
#define RTC_MINUTES_ALARM 3
|
||
|
|
#define RTC_HOURS 4
|
||
|
|
#define RTC_HOURS_ALARM 5
|
||
|
|
#define RTC_ALARM_DONT_CARE 0xC0
|
||
|
|
|
||
|
|
#define RTC_DAY_OF_WEEK 6
|
||
|
|
#define RTC_DAY_OF_MONTH 7
|
||
|
|
#define RTC_MONTH 8
|
||
|
|
#define RTC_YEAR 9
|
||
|
|
|
||
|
|
#define RTC_REG_A 10
|
||
|
|
#define RTC_REG_B 11
|
||
|
|
#define RTC_REG_C 12
|
||
|
|
#define RTC_REG_D 13
|
||
|
|
|
||
|
|
#define REG_A_UIP 0x80
|
||
|
|
|
||
|
|
#define REG_B_SET 0x80
|
||
|
|
#define REG_B_PIE 0x40
|
||
|
|
#define REG_B_AIE 0x20
|
||
|
|
#define REG_B_UIE 0x10
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
uint8_t cmos_index;
|
||
|
|
uint8_t cmos_data[128];
|
||
|
|
IRQSignal *irq;
|
||
|
|
BOOL use_local_time;
|
||
|
|
/* used for the periodic irq */
|
||
|
|
uint32_t irq_timeout;
|
||
|
|
uint32_t irq_period;
|
||
|
|
} CMOSState;
|
||
|
|
|
||
|
|
static void cmos_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t data, int size_log2);
|
||
|
|
static uint32_t cmos_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
|
||
|
|
static int to_bcd(CMOSState *s, unsigned int a)
|
||
|
|
{
|
||
|
|
if (s->cmos_data[RTC_REG_B] & 0x04) {
|
||
|
|
return a;
|
||
|
|
} else {
|
||
|
|
return ((a / 10) << 4) | (a % 10);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cmos_update_time(CMOSState *s, BOOL set_century)
|
||
|
|
{
|
||
|
|
struct timeval tv;
|
||
|
|
struct tm tm;
|
||
|
|
time_t ti;
|
||
|
|
int val;
|
||
|
|
|
||
|
|
gettimeofday(&tv, NULL);
|
||
|
|
ti = tv.tv_sec;
|
||
|
|
if (s->use_local_time) {
|
||
|
|
localtime_r(&ti, &tm);
|
||
|
|
} else {
|
||
|
|
gmtime_r(&ti, &tm);
|
||
|
|
}
|
||
|
|
|
||
|
|
s->cmos_data[RTC_SECONDS] = to_bcd(s, tm.tm_sec);
|
||
|
|
s->cmos_data[RTC_MINUTES] = to_bcd(s, tm.tm_min);
|
||
|
|
if (s->cmos_data[RTC_REG_B] & 0x02) {
|
||
|
|
s->cmos_data[RTC_HOURS] = to_bcd(s, tm.tm_hour);
|
||
|
|
} else {
|
||
|
|
s->cmos_data[RTC_HOURS] = to_bcd(s, tm.tm_hour % 12);
|
||
|
|
if (tm.tm_hour >= 12)
|
||
|
|
s->cmos_data[RTC_HOURS] |= 0x80;
|
||
|
|
}
|
||
|
|
s->cmos_data[RTC_DAY_OF_WEEK] = to_bcd(s, tm.tm_wday);
|
||
|
|
s->cmos_data[RTC_DAY_OF_MONTH] = to_bcd(s, tm.tm_mday);
|
||
|
|
s->cmos_data[RTC_MONTH] = to_bcd(s, tm.tm_mon + 1);
|
||
|
|
s->cmos_data[RTC_YEAR] = to_bcd(s, tm.tm_year % 100);
|
||
|
|
|
||
|
|
if (set_century) {
|
||
|
|
/* not set by the hardware, but easier to do it here */
|
||
|
|
val = to_bcd(s, (tm.tm_year / 100) + 19);
|
||
|
|
s->cmos_data[0x32] = val;
|
||
|
|
s->cmos_data[0x37] = val;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* update in progress flag: 8/32768 seconds after change */
|
||
|
|
if (tv.tv_usec < 244) {
|
||
|
|
s->cmos_data[RTC_REG_A] |= REG_A_UIP;
|
||
|
|
} else {
|
||
|
|
s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
CMOSState *cmos_init(PhysMemoryMap *port_map, int addr,
|
||
|
|
IRQSignal *irq, BOOL use_local_time)
|
||
|
|
{
|
||
|
|
CMOSState *s;
|
||
|
|
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
s->use_local_time = use_local_time;
|
||
|
|
|
||
|
|
s->cmos_index = 0;
|
||
|
|
|
||
|
|
s->cmos_data[RTC_REG_A] = 0x26;
|
||
|
|
s->cmos_data[RTC_REG_B] = 0x02;
|
||
|
|
s->cmos_data[RTC_REG_C] = 0x00;
|
||
|
|
s->cmos_data[RTC_REG_D] = 0x80;
|
||
|
|
|
||
|
|
cmos_update_time(s, TRUE);
|
||
|
|
|
||
|
|
s->irq = irq;
|
||
|
|
|
||
|
|
cpu_register_device(port_map, addr, 2, s, cmos_read, cmos_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define CMOS_FREQ 32768
|
||
|
|
|
||
|
|
static uint32_t cmos_get_timer(CMOSState *s)
|
||
|
|
{
|
||
|
|
struct timespec ts;
|
||
|
|
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (uint32_t)ts.tv_sec * CMOS_FREQ +
|
||
|
|
((uint64_t)ts.tv_nsec * CMOS_FREQ / 1000000000);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cmos_update_timer(CMOSState *s)
|
||
|
|
{
|
||
|
|
int period_code;
|
||
|
|
|
||
|
|
period_code = s->cmos_data[RTC_REG_A] & 0x0f;
|
||
|
|
if ((s->cmos_data[RTC_REG_B] & REG_B_PIE) &&
|
||
|
|
period_code != 0) {
|
||
|
|
if (period_code <= 2)
|
||
|
|
period_code += 7;
|
||
|
|
s->irq_period = 1 << (period_code - 1);
|
||
|
|
s->irq_timeout = (cmos_get_timer(s) + s->irq_period) &
|
||
|
|
~(s->irq_period - 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* XXX: could return a delay, but we don't need high precision
|
||
|
|
(Windows 2000 uses it for delay calibration) */
|
||
|
|
static void cmos_update_irq(CMOSState *s)
|
||
|
|
{
|
||
|
|
uint32_t d;
|
||
|
|
if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
|
||
|
|
d = cmos_get_timer(s) - s->irq_timeout;
|
||
|
|
if ((int32_t)d >= 0) {
|
||
|
|
/* this is not what the real RTC does. Here we sent the IRQ
|
||
|
|
immediately */
|
||
|
|
s->cmos_data[RTC_REG_C] |= 0xc0;
|
||
|
|
set_irq(s->irq, 1);
|
||
|
|
/* update for the next irq */
|
||
|
|
s->irq_timeout += s->irq_period;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cmos_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t data, int size_log2)
|
||
|
|
{
|
||
|
|
CMOSState *s = opaque;
|
||
|
|
|
||
|
|
if (offset == 0) {
|
||
|
|
s->cmos_index = data & 0x7f;
|
||
|
|
} else {
|
||
|
|
#ifdef DEBUG_CMOS
|
||
|
|
printf("cmos_write: reg=0x%02x val=0x%02x\n", s->cmos_index, data);
|
||
|
|
#endif
|
||
|
|
switch(s->cmos_index) {
|
||
|
|
case RTC_REG_A:
|
||
|
|
s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
|
||
|
|
(s->cmos_data[RTC_REG_A] & REG_A_UIP);
|
||
|
|
cmos_update_timer(s);
|
||
|
|
break;
|
||
|
|
case RTC_REG_B:
|
||
|
|
s->cmos_data[s->cmos_index] = data;
|
||
|
|
cmos_update_timer(s);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
s->cmos_data[s->cmos_index] = data;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t cmos_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
CMOSState *s = opaque;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
if (offset == 0) {
|
||
|
|
return 0xff;
|
||
|
|
} else {
|
||
|
|
switch(s->cmos_index) {
|
||
|
|
case RTC_SECONDS:
|
||
|
|
case RTC_MINUTES:
|
||
|
|
case RTC_HOURS:
|
||
|
|
case RTC_DAY_OF_WEEK:
|
||
|
|
case RTC_DAY_OF_MONTH:
|
||
|
|
case RTC_MONTH:
|
||
|
|
case RTC_YEAR:
|
||
|
|
case RTC_REG_A:
|
||
|
|
cmos_update_time(s, FALSE);
|
||
|
|
ret = s->cmos_data[s->cmos_index];
|
||
|
|
break;
|
||
|
|
case RTC_REG_C:
|
||
|
|
ret = s->cmos_data[s->cmos_index];
|
||
|
|
s->cmos_data[RTC_REG_C] = 0x00;
|
||
|
|
set_irq(s->irq, 0);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
ret = s->cmos_data[s->cmos_index];
|
||
|
|
}
|
||
|
|
#ifdef DEBUG_CMOS
|
||
|
|
printf("cmos_read: reg=0x%02x val=0x%02x\n", s->cmos_index, ret);
|
||
|
|
#endif
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/***********************************************************/
|
||
|
|
/* 8259 pic emulation */
|
||
|
|
|
||
|
|
//#define DEBUG_PIC
|
||
|
|
|
||
|
|
typedef void PICUpdateIRQFunc(void *opaque);
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
uint8_t last_irr; /* edge detection */
|
||
|
|
uint8_t irr; /* interrupt request register */
|
||
|
|
uint8_t imr; /* interrupt mask register */
|
||
|
|
uint8_t isr; /* interrupt service register */
|
||
|
|
uint8_t priority_add; /* used to compute irq priority */
|
||
|
|
uint8_t irq_base;
|
||
|
|
uint8_t read_reg_select;
|
||
|
|
uint8_t special_mask;
|
||
|
|
uint8_t init_state;
|
||
|
|
uint8_t auto_eoi;
|
||
|
|
uint8_t rotate_on_autoeoi;
|
||
|
|
uint8_t init4; /* true if 4 byte init */
|
||
|
|
uint8_t elcr; /* PIIX edge/trigger selection*/
|
||
|
|
uint8_t elcr_mask;
|
||
|
|
PICUpdateIRQFunc *update_irq;
|
||
|
|
void *opaque;
|
||
|
|
} PICState;
|
||
|
|
|
||
|
|
static void pic_reset(PICState *s);
|
||
|
|
static void pic_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2);
|
||
|
|
static uint32_t pic_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
static void pic_elcr_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2);
|
||
|
|
static uint32_t pic_elcr_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
|
||
|
|
PICState *pic_init(PhysMemoryMap *port_map, int port, int elcr_port,
|
||
|
|
int elcr_mask,
|
||
|
|
PICUpdateIRQFunc *update_irq, void *opaque)
|
||
|
|
{
|
||
|
|
PICState *s;
|
||
|
|
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
s->elcr_mask = elcr_mask;
|
||
|
|
s->update_irq = update_irq;
|
||
|
|
s->opaque = opaque;
|
||
|
|
cpu_register_device(port_map, port, 2, s,
|
||
|
|
pic_read, pic_write, DEVIO_SIZE8);
|
||
|
|
cpu_register_device(port_map, elcr_port, 1, s,
|
||
|
|
pic_elcr_read, pic_elcr_write, DEVIO_SIZE8);
|
||
|
|
pic_reset(s);
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pic_reset(PICState *s)
|
||
|
|
{
|
||
|
|
/* all 8 bit registers */
|
||
|
|
s->last_irr = 0; /* edge detection */
|
||
|
|
s->irr = 0; /* interrupt request register */
|
||
|
|
s->imr = 0; /* interrupt mask register */
|
||
|
|
s->isr = 0; /* interrupt service register */
|
||
|
|
s->priority_add = 0; /* used to compute irq priority */
|
||
|
|
s->irq_base = 0;
|
||
|
|
s->read_reg_select = 0;
|
||
|
|
s->special_mask = 0;
|
||
|
|
s->init_state = 0;
|
||
|
|
s->auto_eoi = 0;
|
||
|
|
s->rotate_on_autoeoi = 0;
|
||
|
|
s->init4 = 0; /* true if 4 byte init */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* set irq level. If an edge is detected, then the IRR is set to 1 */
|
||
|
|
static void pic_set_irq1(PICState *s, int irq, int level)
|
||
|
|
{
|
||
|
|
int mask;
|
||
|
|
mask = 1 << irq;
|
||
|
|
if (s->elcr & mask) {
|
||
|
|
/* level triggered */
|
||
|
|
if (level) {
|
||
|
|
s->irr |= mask;
|
||
|
|
s->last_irr |= mask;
|
||
|
|
} else {
|
||
|
|
s->irr &= ~mask;
|
||
|
|
s->last_irr &= ~mask;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
/* edge triggered */
|
||
|
|
if (level) {
|
||
|
|
if ((s->last_irr & mask) == 0)
|
||
|
|
s->irr |= mask;
|
||
|
|
s->last_irr |= mask;
|
||
|
|
} else {
|
||
|
|
s->last_irr &= ~mask;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int pic_get_priority(PICState *s, int mask)
|
||
|
|
{
|
||
|
|
int priority;
|
||
|
|
if (mask == 0)
|
||
|
|
return -1;
|
||
|
|
priority = 7;
|
||
|
|
while ((mask & (1 << ((priority + s->priority_add) & 7))) == 0)
|
||
|
|
priority--;
|
||
|
|
return priority;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* return the pic wanted interrupt. return -1 if none */
|
||
|
|
static int pic_get_irq(PICState *s)
|
||
|
|
{
|
||
|
|
int mask, cur_priority, priority;
|
||
|
|
|
||
|
|
mask = s->irr & ~s->imr;
|
||
|
|
priority = pic_get_priority(s, mask);
|
||
|
|
if (priority < 0)
|
||
|
|
return -1;
|
||
|
|
/* compute current priority */
|
||
|
|
cur_priority = pic_get_priority(s, s->isr);
|
||
|
|
if (priority > cur_priority) {
|
||
|
|
/* higher priority found: an irq should be generated */
|
||
|
|
return priority;
|
||
|
|
} else {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* acknowledge interrupt 'irq' */
|
||
|
|
static void pic_intack(PICState *s, int irq)
|
||
|
|
{
|
||
|
|
if (s->auto_eoi) {
|
||
|
|
if (s->rotate_on_autoeoi)
|
||
|
|
s->priority_add = (irq + 1) & 7;
|
||
|
|
} else {
|
||
|
|
s->isr |= (1 << irq);
|
||
|
|
}
|
||
|
|
/* We don't clear a level sensitive interrupt here */
|
||
|
|
if (!(s->elcr & (1 << irq)))
|
||
|
|
s->irr &= ~(1 << irq);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pic_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
PICState *s = opaque;
|
||
|
|
int priority, addr;
|
||
|
|
|
||
|
|
addr = offset & 1;
|
||
|
|
#ifdef DEBUG_PIC
|
||
|
|
console.log("pic_write: addr=" + toHex2(addr) + " val=" + toHex2(val));
|
||
|
|
#endif
|
||
|
|
if (addr == 0) {
|
||
|
|
if (val & 0x10) {
|
||
|
|
/* init */
|
||
|
|
pic_reset(s);
|
||
|
|
s->init_state = 1;
|
||
|
|
s->init4 = val & 1;
|
||
|
|
if (val & 0x02)
|
||
|
|
abort(); /* "single mode not supported" */
|
||
|
|
if (val & 0x08)
|
||
|
|
abort(); /* "level sensitive irq not supported" */
|
||
|
|
} else if (val & 0x08) {
|
||
|
|
if (val & 0x02)
|
||
|
|
s->read_reg_select = val & 1;
|
||
|
|
if (val & 0x40)
|
||
|
|
s->special_mask = (val >> 5) & 1;
|
||
|
|
} else {
|
||
|
|
switch(val) {
|
||
|
|
case 0x00:
|
||
|
|
case 0x80:
|
||
|
|
s->rotate_on_autoeoi = val >> 7;
|
||
|
|
break;
|
||
|
|
case 0x20: /* end of interrupt */
|
||
|
|
case 0xa0:
|
||
|
|
priority = pic_get_priority(s, s->isr);
|
||
|
|
if (priority >= 0) {
|
||
|
|
s->isr &= ~(1 << ((priority + s->priority_add) & 7));
|
||
|
|
}
|
||
|
|
if (val == 0xa0)
|
||
|
|
s->priority_add = (s->priority_add + 1) & 7;
|
||
|
|
break;
|
||
|
|
case 0x60:
|
||
|
|
case 0x61:
|
||
|
|
case 0x62:
|
||
|
|
case 0x63:
|
||
|
|
case 0x64:
|
||
|
|
case 0x65:
|
||
|
|
case 0x66:
|
||
|
|
case 0x67:
|
||
|
|
priority = val & 7;
|
||
|
|
s->isr &= ~(1 << priority);
|
||
|
|
break;
|
||
|
|
case 0xc0:
|
||
|
|
case 0xc1:
|
||
|
|
case 0xc2:
|
||
|
|
case 0xc3:
|
||
|
|
case 0xc4:
|
||
|
|
case 0xc5:
|
||
|
|
case 0xc6:
|
||
|
|
case 0xc7:
|
||
|
|
s->priority_add = (val + 1) & 7;
|
||
|
|
break;
|
||
|
|
case 0xe0:
|
||
|
|
case 0xe1:
|
||
|
|
case 0xe2:
|
||
|
|
case 0xe3:
|
||
|
|
case 0xe4:
|
||
|
|
case 0xe5:
|
||
|
|
case 0xe6:
|
||
|
|
case 0xe7:
|
||
|
|
priority = val & 7;
|
||
|
|
s->isr &= ~(1 << priority);
|
||
|
|
s->priority_add = (priority + 1) & 7;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
switch(s->init_state) {
|
||
|
|
case 0:
|
||
|
|
/* normal mode */
|
||
|
|
s->imr = val;
|
||
|
|
s->update_irq(s->opaque);
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
s->irq_base = val & 0xf8;
|
||
|
|
s->init_state = 2;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
if (s->init4) {
|
||
|
|
s->init_state = 3;
|
||
|
|
} else {
|
||
|
|
s->init_state = 0;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
s->auto_eoi = (val >> 1) & 1;
|
||
|
|
s->init_state = 0;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t pic_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
PICState *s = opaque;
|
||
|
|
int addr, ret;
|
||
|
|
|
||
|
|
addr = offset & 1;
|
||
|
|
if (addr == 0) {
|
||
|
|
if (s->read_reg_select)
|
||
|
|
ret = s->isr;
|
||
|
|
else
|
||
|
|
ret = s->irr;
|
||
|
|
} else {
|
||
|
|
ret = s->imr;
|
||
|
|
}
|
||
|
|
#ifdef DEBUG_PIC
|
||
|
|
console.log("pic_read: addr=" + toHex2(addr1) + " val=" + toHex2(ret));
|
||
|
|
#endif
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pic_elcr_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
PICState *s = opaque;
|
||
|
|
s->elcr = val & s->elcr_mask;
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t pic_elcr_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
PICState *s = opaque;
|
||
|
|
return s->elcr;
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
PICState *pics[2];
|
||
|
|
int irq_requested;
|
||
|
|
void (*cpu_set_irq)(void *opaque, int level);
|
||
|
|
void *opaque;
|
||
|
|
#if defined(DEBUG_PIC)
|
||
|
|
uint8_t irq_level[16];
|
||
|
|
#endif
|
||
|
|
IRQSignal *irqs;
|
||
|
|
} PIC2State;
|
||
|
|
|
||
|
|
static void pic2_update_irq(void *opaque);
|
||
|
|
static void pic2_set_irq(void *opaque, int irq, int level);
|
||
|
|
|
||
|
|
PIC2State *pic2_init(PhysMemoryMap *port_map, uint32_t addr0, uint32_t addr1,
|
||
|
|
uint32_t elcr_addr0, uint32_t elcr_addr1,
|
||
|
|
void (*cpu_set_irq)(void *opaque, int level),
|
||
|
|
void *opaque, IRQSignal *irqs)
|
||
|
|
{
|
||
|
|
PIC2State *s;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
|
||
|
|
for(i = 0; i < 16; i++) {
|
||
|
|
irq_init(&irqs[i], pic2_set_irq, s, i);
|
||
|
|
}
|
||
|
|
s->cpu_set_irq = cpu_set_irq;
|
||
|
|
s->opaque = opaque;
|
||
|
|
s->pics[0] = pic_init(port_map, addr0, elcr_addr0, 0xf8, pic2_update_irq, s);
|
||
|
|
s->pics[1] = pic_init(port_map, addr1, elcr_addr1, 0xde, pic2_update_irq, s);
|
||
|
|
s->irq_requested = 0;
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
void pic2_set_elcr(PIC2State *s, const uint8_t *elcr)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
for(i = 0; i < 2; i++) {
|
||
|
|
s->pics[i]->elcr = elcr[i] & s->pics[i]->elcr_mask;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* raise irq to CPU if necessary. must be called every time the active
|
||
|
|
irq may change */
|
||
|
|
static void pic2_update_irq(void *opaque)
|
||
|
|
{
|
||
|
|
PIC2State *s = opaque;
|
||
|
|
int irq2, irq;
|
||
|
|
|
||
|
|
/* first look at slave pic */
|
||
|
|
irq2 = pic_get_irq(s->pics[1]);
|
||
|
|
if (irq2 >= 0) {
|
||
|
|
/* if irq request by slave pic, signal master PIC */
|
||
|
|
pic_set_irq1(s->pics[0], 2, 1);
|
||
|
|
pic_set_irq1(s->pics[0], 2, 0);
|
||
|
|
}
|
||
|
|
/* look at requested irq */
|
||
|
|
irq = pic_get_irq(s->pics[0]);
|
||
|
|
#if 0
|
||
|
|
console.log("irr=" + toHex2(s->pics[0].irr) + " imr=" + toHex2(s->pics[0].imr) + " isr=" + toHex2(s->pics[0].isr) + " irq="+ irq);
|
||
|
|
#endif
|
||
|
|
if (irq >= 0) {
|
||
|
|
/* raise IRQ request on the CPU */
|
||
|
|
s->cpu_set_irq(s->opaque, 1);
|
||
|
|
} else {
|
||
|
|
/* lower irq */
|
||
|
|
s->cpu_set_irq(s->opaque, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pic2_set_irq(void *opaque, int irq, int level)
|
||
|
|
{
|
||
|
|
PIC2State *s = opaque;
|
||
|
|
#if defined(DEBUG_PIC)
|
||
|
|
if (irq != 0 && level != s->irq_level[irq]) {
|
||
|
|
console.log("pic_set_irq: irq=" + irq + " level=" + level);
|
||
|
|
s->irq_level[irq] = level;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
pic_set_irq1(s->pics[irq >> 3], irq & 7, level);
|
||
|
|
pic2_update_irq(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* called from the CPU to get the hardware interrupt number */
|
||
|
|
static int pic2_get_hard_intno(PIC2State *s)
|
||
|
|
{
|
||
|
|
int irq, irq2, intno;
|
||
|
|
|
||
|
|
irq = pic_get_irq(s->pics[0]);
|
||
|
|
if (irq >= 0) {
|
||
|
|
pic_intack(s->pics[0], irq);
|
||
|
|
if (irq == 2) {
|
||
|
|
irq2 = pic_get_irq(s->pics[1]);
|
||
|
|
if (irq2 >= 0) {
|
||
|
|
pic_intack(s->pics[1], irq2);
|
||
|
|
} else {
|
||
|
|
/* spurious IRQ on slave controller */
|
||
|
|
irq2 = 7;
|
||
|
|
}
|
||
|
|
intno = s->pics[1]->irq_base + irq2;
|
||
|
|
irq = irq2 + 8;
|
||
|
|
} else {
|
||
|
|
intno = s->pics[0]->irq_base + irq;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
/* spurious IRQ on host controller */
|
||
|
|
irq = 7;
|
||
|
|
intno = s->pics[0]->irq_base + irq;
|
||
|
|
}
|
||
|
|
pic2_update_irq(s);
|
||
|
|
|
||
|
|
#if defined(DEBUG_PIC)
|
||
|
|
if (irq != 0 && irq != 14)
|
||
|
|
printf("pic_interrupt: irq=%d\n", irq);
|
||
|
|
#endif
|
||
|
|
return intno;
|
||
|
|
}
|
||
|
|
|
||
|
|
/***********************************************************/
|
||
|
|
/* 8253 PIT emulation */
|
||
|
|
|
||
|
|
#define PIT_FREQ 1193182
|
||
|
|
|
||
|
|
#define RW_STATE_LSB 0
|
||
|
|
#define RW_STATE_MSB 1
|
||
|
|
#define RW_STATE_WORD0 2
|
||
|
|
#define RW_STATE_WORD1 3
|
||
|
|
#define RW_STATE_LATCHED_WORD0 4
|
||
|
|
#define RW_STATE_LATCHED_WORD1 5
|
||
|
|
|
||
|
|
//#define DEBUG_PIT
|
||
|
|
|
||
|
|
typedef int64_t PITGetTicksFunc(void *opaque);
|
||
|
|
|
||
|
|
typedef struct PITState PITState;
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
PITState *pit_state;
|
||
|
|
uint32_t count;
|
||
|
|
uint32_t latched_count;
|
||
|
|
uint8_t rw_state;
|
||
|
|
uint8_t mode;
|
||
|
|
uint8_t bcd;
|
||
|
|
uint8_t gate;
|
||
|
|
int64_t count_load_time;
|
||
|
|
int64_t last_irq_time;
|
||
|
|
} PITChannel;
|
||
|
|
|
||
|
|
struct PITState {
|
||
|
|
PITChannel pit_channels[3];
|
||
|
|
uint8_t speaker_data_on;
|
||
|
|
PITGetTicksFunc *get_ticks;
|
||
|
|
IRQSignal *irq;
|
||
|
|
void *opaque;
|
||
|
|
};
|
||
|
|
|
||
|
|
static void pit_load_count(PITChannel *pc, int val);
|
||
|
|
static void pit_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2);
|
||
|
|
static uint32_t pit_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
static void speaker_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2);
|
||
|
|
static uint32_t speaker_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
|
||
|
|
PITState *pit_init(PhysMemoryMap *port_map, int addr0, int addr1,
|
||
|
|
IRQSignal *irq,
|
||
|
|
PITGetTicksFunc *get_ticks, void *opaque)
|
||
|
|
{
|
||
|
|
PITState *s;
|
||
|
|
PITChannel *pc;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
|
||
|
|
s->irq = irq;
|
||
|
|
s->get_ticks = get_ticks;
|
||
|
|
s->opaque = opaque;
|
||
|
|
|
||
|
|
for(i = 0; i < 3; i++) {
|
||
|
|
pc = &s->pit_channels[i];
|
||
|
|
pc->pit_state = s;
|
||
|
|
pc->mode = 3;
|
||
|
|
pc->gate = (i != 2) >> 0;
|
||
|
|
pit_load_count(pc, 0);
|
||
|
|
}
|
||
|
|
s->speaker_data_on = 0;
|
||
|
|
|
||
|
|
cpu_register_device(port_map, addr0, 4, s, pit_read, pit_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
|
||
|
|
cpu_register_device(port_map, addr1, 1, s, speaker_read, speaker_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* unit = PIT frequency */
|
||
|
|
static int64_t pit_get_time(PITChannel *pc)
|
||
|
|
{
|
||
|
|
PITState *s = pc->pit_state;
|
||
|
|
return s->get_ticks(s->opaque);
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t pit_get_count(PITChannel *pc)
|
||
|
|
{
|
||
|
|
uint32_t counter;
|
||
|
|
uint64_t d;
|
||
|
|
|
||
|
|
d = pit_get_time(pc) - pc->count_load_time;
|
||
|
|
switch(pc->mode) {
|
||
|
|
case 0:
|
||
|
|
case 1:
|
||
|
|
case 4:
|
||
|
|
case 5:
|
||
|
|
counter = (pc->count - d) & 0xffff;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
counter = pc->count - (d % pc->count);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return counter;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* get pit output bit */
|
||
|
|
static int pit_get_out(PITChannel *pc)
|
||
|
|
{
|
||
|
|
int out;
|
||
|
|
int64_t d;
|
||
|
|
|
||
|
|
d = pit_get_time(pc) - pc->count_load_time;
|
||
|
|
switch(pc->mode) {
|
||
|
|
default:
|
||
|
|
case 0:
|
||
|
|
out = (d >= pc->count) >> 0;
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
out = (d < pc->count) >> 0;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
/* mode used by Linux */
|
||
|
|
if ((d % pc->count) == 0 && d != 0)
|
||
|
|
out = 1;
|
||
|
|
else
|
||
|
|
out = 0;
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
out = ((d % pc->count) < (pc->count >> 1)) >> 0;
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
case 5:
|
||
|
|
out = (d == pc->count) >> 0;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return out;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pit_load_count(PITChannel *s, int val)
|
||
|
|
{
|
||
|
|
if (val == 0)
|
||
|
|
val = 0x10000;
|
||
|
|
s->count_load_time = pit_get_time(s);
|
||
|
|
s->last_irq_time = 0;
|
||
|
|
s->count = val;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pit_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
PITState *pit = opaque;
|
||
|
|
int channel, access, addr;
|
||
|
|
PITChannel *s;
|
||
|
|
|
||
|
|
addr = offset & 3;
|
||
|
|
#ifdef DEBUG_PIT
|
||
|
|
printf("pit_write: off=%d val=0x%02x\n", addr, val);
|
||
|
|
#endif
|
||
|
|
if (addr == 3) {
|
||
|
|
channel = val >> 6;
|
||
|
|
if (channel == 3)
|
||
|
|
return;
|
||
|
|
s = &pit->pit_channels[channel];
|
||
|
|
access = (val >> 4) & 3;
|
||
|
|
switch(access) {
|
||
|
|
case 0:
|
||
|
|
s->latched_count = pit_get_count(s);
|
||
|
|
s->rw_state = RW_STATE_LATCHED_WORD0;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
s->mode = (val >> 1) & 7;
|
||
|
|
s->bcd = val & 1;
|
||
|
|
s->rw_state = access - 1 + RW_STATE_LSB;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
s = &pit->pit_channels[addr];
|
||
|
|
switch(s->rw_state) {
|
||
|
|
case RW_STATE_LSB:
|
||
|
|
pit_load_count(s, val);
|
||
|
|
break;
|
||
|
|
case RW_STATE_MSB:
|
||
|
|
pit_load_count(s, val << 8);
|
||
|
|
break;
|
||
|
|
case RW_STATE_WORD0:
|
||
|
|
case RW_STATE_WORD1:
|
||
|
|
if (s->rw_state & 1) {
|
||
|
|
pit_load_count(s, (s->latched_count & 0xff) | (val << 8));
|
||
|
|
} else {
|
||
|
|
s->latched_count = val;
|
||
|
|
}
|
||
|
|
s->rw_state ^= 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t pit_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
PITState *pit = opaque;
|
||
|
|
PITChannel *s;
|
||
|
|
int ret, count, addr;
|
||
|
|
|
||
|
|
addr = offset & 3;
|
||
|
|
if (addr == 3)
|
||
|
|
return 0xff;
|
||
|
|
|
||
|
|
s = &pit->pit_channels[addr];
|
||
|
|
switch(s->rw_state) {
|
||
|
|
case RW_STATE_LSB:
|
||
|
|
case RW_STATE_MSB:
|
||
|
|
case RW_STATE_WORD0:
|
||
|
|
case RW_STATE_WORD1:
|
||
|
|
count = pit_get_count(s);
|
||
|
|
if (s->rw_state & 1)
|
||
|
|
ret = (count >> 8) & 0xff;
|
||
|
|
else
|
||
|
|
ret = count & 0xff;
|
||
|
|
if (s->rw_state & 2)
|
||
|
|
s->rw_state ^= 1;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
case RW_STATE_LATCHED_WORD0:
|
||
|
|
case RW_STATE_LATCHED_WORD1:
|
||
|
|
if (s->rw_state & 1)
|
||
|
|
ret = s->latched_count >> 8;
|
||
|
|
else
|
||
|
|
ret = s->latched_count & 0xff;
|
||
|
|
s->rw_state ^= 1;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
#ifdef DEBUG_PIT
|
||
|
|
printf("pit_read: off=%d val=0x%02x\n", addr, ret);
|
||
|
|
#endif
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void speaker_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
PITState *pit = opaque;
|
||
|
|
pit->speaker_data_on = (val >> 1) & 1;
|
||
|
|
pit->pit_channels[2].gate = val & 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t speaker_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
PITState *pit = opaque;
|
||
|
|
PITChannel *s;
|
||
|
|
int out, val;
|
||
|
|
|
||
|
|
s = &pit->pit_channels[2];
|
||
|
|
out = pit_get_out(s);
|
||
|
|
val = (pit->speaker_data_on << 1) | s->gate | (out << 5);
|
||
|
|
#ifdef DEBUG_PIT
|
||
|
|
// console.log("speaker_read: addr=" + toHex2(addr) + " val=" + toHex2(val));
|
||
|
|
#endif
|
||
|
|
return val;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* set the IRQ if necessary and return the delay in ms until the next
|
||
|
|
IRQ. Note: The code does not handle all the PIT configurations. */
|
||
|
|
static int pit_update_irq(PITState *pit)
|
||
|
|
{
|
||
|
|
PITChannel *s;
|
||
|
|
int64_t d, delay;
|
||
|
|
|
||
|
|
s = &pit->pit_channels[0];
|
||
|
|
|
||
|
|
delay = PIT_FREQ; /* could be infinity delay */
|
||
|
|
|
||
|
|
d = pit_get_time(s) - s->count_load_time;
|
||
|
|
switch(s->mode) {
|
||
|
|
default:
|
||
|
|
case 0:
|
||
|
|
case 1:
|
||
|
|
case 4:
|
||
|
|
case 5:
|
||
|
|
if (s->last_irq_time == 0) {
|
||
|
|
delay = s->count - d;
|
||
|
|
if (delay <= 0) {
|
||
|
|
set_irq(pit->irq, 1);
|
||
|
|
set_irq(pit->irq, 0);
|
||
|
|
s->last_irq_time = d;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2: /* mode used by Linux */
|
||
|
|
case 3:
|
||
|
|
delay = s->last_irq_time + s->count - d;
|
||
|
|
if (delay <= 0) {
|
||
|
|
set_irq(pit->irq, 1);
|
||
|
|
set_irq(pit->irq, 0);
|
||
|
|
s->last_irq_time += s->count;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (delay <= 0)
|
||
|
|
return 0;
|
||
|
|
else
|
||
|
|
return delay / (PIT_FREQ / 1000);
|
||
|
|
}
|
||
|
|
|
||
|
|
/***********************************************************/
|
||
|
|
/* serial port emulation */
|
||
|
|
|
||
|
|
#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */
|
||
|
|
|
||
|
|
#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */
|
||
|
|
#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */
|
||
|
|
#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */
|
||
|
|
#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */
|
||
|
|
|
||
|
|
#define UART_IIR_NO_INT 0x01 /* No interrupts pending */
|
||
|
|
#define UART_IIR_ID 0x06 /* Mask for the interrupt ID */
|
||
|
|
|
||
|
|
#define UART_IIR_MSI 0x00 /* Modem status interrupt */
|
||
|
|
#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */
|
||
|
|
#define UART_IIR_RDI 0x04 /* Receiver data interrupt */
|
||
|
|
#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */
|
||
|
|
#define UART_IIR_FE 0xC0 /* Fifo enabled */
|
||
|
|
|
||
|
|
#define UART_LSR_TEMT 0x40 /* Transmitter empty */
|
||
|
|
#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */
|
||
|
|
#define UART_LSR_BI 0x10 /* Break interrupt indicator */
|
||
|
|
#define UART_LSR_FE 0x08 /* Frame error indicator */
|
||
|
|
#define UART_LSR_PE 0x04 /* Parity error indicator */
|
||
|
|
#define UART_LSR_OE 0x02 /* Overrun error indicator */
|
||
|
|
#define UART_LSR_DR 0x01 /* Receiver data ready */
|
||
|
|
|
||
|
|
#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */
|
||
|
|
#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */
|
||
|
|
#define UART_FCR_FE 0x01 /* FIFO Enable */
|
||
|
|
|
||
|
|
#define UART_FIFO_LENGTH 16 /* 16550A Fifo Length */
|
||
|
|
|
||
|
|
typedef struct {
|
||
|
|
uint8_t divider;
|
||
|
|
uint8_t rbr; /* receive register */
|
||
|
|
uint8_t ier;
|
||
|
|
uint8_t iir; /* read only */
|
||
|
|
uint8_t lcr;
|
||
|
|
uint8_t mcr;
|
||
|
|
uint8_t lsr; /* read only */
|
||
|
|
uint8_t msr;
|
||
|
|
uint8_t scr;
|
||
|
|
uint8_t fcr;
|
||
|
|
IRQSignal *irq;
|
||
|
|
void (*write_func)(void *opaque, const uint8_t *buf, int buf_len);
|
||
|
|
void *opaque;
|
||
|
|
} SerialState;
|
||
|
|
|
||
|
|
static void serial_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2);
|
||
|
|
static uint32_t serial_read(void *opaque, uint32_t offset, int size_log2);
|
||
|
|
|
||
|
|
SerialState *serial_init(PhysMemoryMap *port_map, int addr,
|
||
|
|
IRQSignal *irq,
|
||
|
|
void (*write_func)(void *opaque, const uint8_t *buf, int buf_len), void *opaque)
|
||
|
|
{
|
||
|
|
SerialState *s;
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
|
||
|
|
/* all 8 bit registers */
|
||
|
|
s->divider = 0;
|
||
|
|
s->rbr = 0; /* receive register */
|
||
|
|
s->ier = 0;
|
||
|
|
s->iir = UART_IIR_NO_INT; /* read only */
|
||
|
|
s->lcr = 0;
|
||
|
|
s->mcr = 0;
|
||
|
|
s->lsr = UART_LSR_TEMT | UART_LSR_THRE; /* read only */
|
||
|
|
s->msr = 0;
|
||
|
|
s->scr = 0;
|
||
|
|
s->fcr = 0;
|
||
|
|
|
||
|
|
s->irq = irq;
|
||
|
|
s->write_func = write_func;
|
||
|
|
s->opaque = opaque;
|
||
|
|
|
||
|
|
cpu_register_device(port_map, addr, 8, s, serial_read, serial_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void serial_update_irq(SerialState *s)
|
||
|
|
{
|
||
|
|
if ((s->lsr & UART_LSR_DR) && (s->ier & UART_IER_RDI)) {
|
||
|
|
s->iir = UART_IIR_RDI;
|
||
|
|
} else if ((s->lsr & UART_LSR_THRE) && (s->ier & UART_IER_THRI)) {
|
||
|
|
s->iir = UART_IIR_THRI;
|
||
|
|
} else {
|
||
|
|
s->iir = UART_IIR_NO_INT;
|
||
|
|
}
|
||
|
|
if (s->iir != UART_IIR_NO_INT) {
|
||
|
|
set_irq(s->irq, 1);
|
||
|
|
} else {
|
||
|
|
set_irq(s->irq, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#if 0
|
||
|
|
/* send remainining chars in fifo */
|
||
|
|
Serial.prototype.write_tx_fifo = function()
|
||
|
|
{
|
||
|
|
if (s->tx_fifo != "") {
|
||
|
|
s->write_func(s->tx_fifo);
|
||
|
|
s->tx_fifo = "";
|
||
|
|
|
||
|
|
s->lsr |= UART_LSR_THRE;
|
||
|
|
s->lsr |= UART_LSR_TEMT;
|
||
|
|
s->update_irq();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static void serial_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
SerialState *s = opaque;
|
||
|
|
int addr;
|
||
|
|
|
||
|
|
addr = offset & 7;
|
||
|
|
switch(addr) {
|
||
|
|
default:
|
||
|
|
case 0:
|
||
|
|
if (s->lcr & UART_LCR_DLAB) {
|
||
|
|
s->divider = (s->divider & 0xff00) | val;
|
||
|
|
} else {
|
||
|
|
#if 0
|
||
|
|
if (s->fcr & UART_FCR_FE) {
|
||
|
|
s->tx_fifo += String.fromCharCode(val);
|
||
|
|
s->lsr &= ~UART_LSR_THRE;
|
||
|
|
serial_update_irq(s);
|
||
|
|
if (s->tx_fifo.length >= UART_FIFO_LENGTH) {
|
||
|
|
/* write to the terminal */
|
||
|
|
s->write_tx_fifo();
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
uint8_t ch;
|
||
|
|
s->lsr &= ~UART_LSR_THRE;
|
||
|
|
serial_update_irq(s);
|
||
|
|
|
||
|
|
/* write to the terminal */
|
||
|
|
ch = val;
|
||
|
|
s->write_func(s->opaque, &ch, 1);
|
||
|
|
s->lsr |= UART_LSR_THRE;
|
||
|
|
s->lsr |= UART_LSR_TEMT;
|
||
|
|
serial_update_irq(s);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
if (s->lcr & UART_LCR_DLAB) {
|
||
|
|
s->divider = (s->divider & 0x00ff) | (val << 8);
|
||
|
|
} else {
|
||
|
|
s->ier = val;
|
||
|
|
serial_update_irq(s);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
#if 0
|
||
|
|
if ((s->fcr ^ val) & UART_FCR_FE) {
|
||
|
|
/* clear fifos */
|
||
|
|
val |= UART_FCR_XFR | UART_FCR_RFR;
|
||
|
|
}
|
||
|
|
if (val & UART_FCR_XFR)
|
||
|
|
s->tx_fifo = "";
|
||
|
|
if (val & UART_FCR_RFR)
|
||
|
|
s->rx_fifo = "";
|
||
|
|
s->fcr = val & UART_FCR_FE;
|
||
|
|
#endif
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
s->lcr = val;
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
s->mcr = val;
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
break;
|
||
|
|
case 6:
|
||
|
|
s->msr = val;
|
||
|
|
break;
|
||
|
|
case 7:
|
||
|
|
s->scr = val;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t serial_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
SerialState *s = opaque;
|
||
|
|
int ret, addr;
|
||
|
|
|
||
|
|
addr = offset & 7;
|
||
|
|
switch(addr) {
|
||
|
|
default:
|
||
|
|
case 0:
|
||
|
|
if (s->lcr & UART_LCR_DLAB) {
|
||
|
|
ret = s->divider & 0xff;
|
||
|
|
} else {
|
||
|
|
ret = s->rbr;
|
||
|
|
s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
|
||
|
|
serial_update_irq(s);
|
||
|
|
#if 0
|
||
|
|
/* try to receive next chars */
|
||
|
|
s->send_char_from_fifo();
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 1:
|
||
|
|
if (s->lcr & UART_LCR_DLAB) {
|
||
|
|
ret = (s->divider >> 8) & 0xff;
|
||
|
|
} else {
|
||
|
|
ret = s->ier;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
ret = s->iir;
|
||
|
|
if (s->fcr & UART_FCR_FE)
|
||
|
|
ret |= UART_IIR_FE;
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
ret = s->lcr;
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
ret = s->mcr;
|
||
|
|
break;
|
||
|
|
case 5:
|
||
|
|
ret = s->lsr;
|
||
|
|
break;
|
||
|
|
case 6:
|
||
|
|
ret = s->msr;
|
||
|
|
break;
|
||
|
|
case 7:
|
||
|
|
ret = s->scr;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
void serial_send_break(SerialState *s)
|
||
|
|
{
|
||
|
|
s->rbr = 0;
|
||
|
|
s->lsr |= UART_LSR_BI | UART_LSR_DR;
|
||
|
|
serial_update_irq(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
#if 0
|
||
|
|
static void serial_send_char(SerialState *s, int ch)
|
||
|
|
{
|
||
|
|
s->rbr = ch;
|
||
|
|
s->lsr |= UART_LSR_DR;
|
||
|
|
serial_update_irq(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
Serial.prototype.send_char_from_fifo = function()
|
||
|
|
{
|
||
|
|
var fifo;
|
||
|
|
|
||
|
|
fifo = s->rx_fifo;
|
||
|
|
if (fifo != "" && !(s->lsr & UART_LSR_DR)) {
|
||
|
|
s->send_char(fifo.charCodeAt(0));
|
||
|
|
s->rx_fifo = fifo.substr(1, fifo.length - 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* queue the string in the UART receive fifo and send it ASAP */
|
||
|
|
Serial.prototype.send_chars = function(str)
|
||
|
|
{
|
||
|
|
s->rx_fifo += str;
|
||
|
|
s->send_char_from_fifo();
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef DEBUG_BIOS
|
||
|
|
static void bios_debug_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
#ifdef EMSCRIPTEN
|
||
|
|
static char line_buf[256];
|
||
|
|
static int line_buf_index;
|
||
|
|
line_buf[line_buf_index++] = val;
|
||
|
|
if (val == '\n' || line_buf_index >= sizeof(line_buf) - 1) {
|
||
|
|
line_buf[line_buf_index] = '\0';
|
||
|
|
printf("%s", line_buf);
|
||
|
|
line_buf_index = 0;
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
putchar(val & 0xff);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t bios_debug_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
typedef struct PCMachine {
|
||
|
|
VirtMachine common;
|
||
|
|
uint64_t ram_size;
|
||
|
|
PhysMemoryMap *mem_map;
|
||
|
|
PhysMemoryMap *port_map;
|
||
|
|
|
||
|
|
X86CPUState *cpu_state;
|
||
|
|
PIC2State *pic_state;
|
||
|
|
IRQSignal pic_irq[16];
|
||
|
|
PITState *pit_state;
|
||
|
|
I440FXState *i440fx_state;
|
||
|
|
CMOSState *cmos_state;
|
||
|
|
SerialState *serial_state;
|
||
|
|
|
||
|
|
/* input */
|
||
|
|
VIRTIODevice *keyboard_dev;
|
||
|
|
VIRTIODevice *mouse_dev;
|
||
|
|
KBDState *kbd_state;
|
||
|
|
PS2MouseState *ps2_mouse;
|
||
|
|
VMMouseState *vm_mouse;
|
||
|
|
PS2KbdState *ps2_kbd;
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
BOOL kvm_enabled;
|
||
|
|
int kvm_fd;
|
||
|
|
int vm_fd;
|
||
|
|
int vcpu_fd;
|
||
|
|
int kvm_run_size;
|
||
|
|
struct kvm_run *kvm_run;
|
||
|
|
#endif
|
||
|
|
} PCMachine;
|
||
|
|
|
||
|
|
static void copy_kernel(PCMachine *s, const uint8_t *buf, int buf_len,
|
||
|
|
const char *cmd_line);
|
||
|
|
|
||
|
|
static void port80_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val64, int size_log2)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t port80_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
return 0xff;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void port92_write(void *opaque, uint32_t offset,
|
||
|
|
uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t port92_read(void *opaque, uint32_t offset, int size_log2)
|
||
|
|
{
|
||
|
|
int a20 = 1; /* A20=0 is not supported */
|
||
|
|
return a20 << 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
#define VMPORT_MAGIC 0x564D5868
|
||
|
|
#define REG_EAX 0
|
||
|
|
#define REG_EBX 1
|
||
|
|
#define REG_ECX 2
|
||
|
|
#define REG_EDX 3
|
||
|
|
#define REG_ESI 4
|
||
|
|
#define REG_EDI 5
|
||
|
|
|
||
|
|
static uint32_t vmport_read(void *opaque, uint32_t addr, int size_log2)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
uint32_t regs[6];
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (s->kvm_enabled) {
|
||
|
|
struct kvm_regs r;
|
||
|
|
|
||
|
|
ioctl(s->vcpu_fd, KVM_GET_REGS, &r);
|
||
|
|
regs[REG_EAX] = r.rax;
|
||
|
|
regs[REG_EBX] = r.rbx;
|
||
|
|
regs[REG_ECX] = r.rcx;
|
||
|
|
regs[REG_EDX] = r.rdx;
|
||
|
|
regs[REG_ESI] = r.rsi;
|
||
|
|
regs[REG_EDI] = r.rdi;
|
||
|
|
|
||
|
|
if (regs[REG_EAX] == VMPORT_MAGIC) {
|
||
|
|
|
||
|
|
vmmouse_handler(s->vm_mouse, regs);
|
||
|
|
|
||
|
|
/* Note: in 64 bits the high parts are reset to zero
|
||
|
|
in all cases. */
|
||
|
|
r.rax = regs[REG_EAX];
|
||
|
|
r.rbx = regs[REG_EBX];
|
||
|
|
r.rcx = regs[REG_ECX];
|
||
|
|
r.rdx = regs[REG_EDX];
|
||
|
|
r.rsi = regs[REG_ESI];
|
||
|
|
r.rdi = regs[REG_EDI];
|
||
|
|
ioctl(s->vcpu_fd, KVM_SET_REGS, &r);
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
regs[REG_EAX] = x86_cpu_get_reg(s->cpu_state, 0);
|
||
|
|
regs[REG_EBX] = x86_cpu_get_reg(s->cpu_state, 3);
|
||
|
|
regs[REG_ECX] = x86_cpu_get_reg(s->cpu_state, 1);
|
||
|
|
regs[REG_EDX] = x86_cpu_get_reg(s->cpu_state, 2);
|
||
|
|
regs[REG_ESI] = x86_cpu_get_reg(s->cpu_state, 6);
|
||
|
|
regs[REG_EDI] = x86_cpu_get_reg(s->cpu_state, 7);
|
||
|
|
|
||
|
|
if (regs[REG_EAX] == VMPORT_MAGIC) {
|
||
|
|
vmmouse_handler(s->vm_mouse, regs);
|
||
|
|
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 0, regs[REG_EAX]);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 3, regs[REG_EBX]);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 1, regs[REG_ECX]);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 2, regs[REG_EDX]);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 6, regs[REG_ESI]);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 7, regs[REG_EDI]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return regs[REG_EAX];
|
||
|
|
}
|
||
|
|
|
||
|
|
static void vmport_write(void *opaque, uint32_t addr, uint32_t val,
|
||
|
|
int size_log2)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pic_set_irq_cb(void *opaque, int level)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
x86_cpu_set_irq(s->cpu_state, level);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void serial_write_cb(void *opaque, const uint8_t *buf, int buf_len)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
if (s->common.console) {
|
||
|
|
s->common.console->write_data(s->common.console->opaque, buf, buf_len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int get_hard_intno_cb(void *opaque)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
return pic2_get_hard_intno(s->pic_state);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int64_t pit_get_ticks_cb(void *opaque)
|
||
|
|
{
|
||
|
|
struct timespec ts;
|
||
|
|
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (uint64_t)ts.tv_sec * PIT_FREQ +
|
||
|
|
((uint64_t)ts.tv_nsec * PIT_FREQ / 1000000000);
|
||
|
|
}
|
||
|
|
|
||
|
|
#define FRAMEBUFFER_BASE_ADDR 0xf0400000
|
||
|
|
|
||
|
|
static uint8_t *get_ram_ptr(PCMachine *s, uint64_t paddr)
|
||
|
|
{
|
||
|
|
PhysMemoryRange *pr;
|
||
|
|
pr = get_phys_mem_range(s->mem_map, paddr);
|
||
|
|
if (!pr || !pr->is_ram)
|
||
|
|
return NULL;
|
||
|
|
return pr->phys_mem + (uintptr_t)(paddr - pr->addr);
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef DUMP_IOPORT
|
||
|
|
static BOOL dump_port(int port)
|
||
|
|
{
|
||
|
|
return !((port >= 0x1f0 && port <= 0x1f7) ||
|
||
|
|
(port >= 0x20 && port <= 0x21) ||
|
||
|
|
(port >= 0xa0 && port <= 0xa1));
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static void st_port(void *opaque, uint32_t port, uint32_t val, int size_log2)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
PhysMemoryRange *pr;
|
||
|
|
#ifdef DUMP_IOPORT
|
||
|
|
if (dump_port(port))
|
||
|
|
printf("write port=0x%x val=0x%x s=%d\n", port, val, 1 << size_log2);
|
||
|
|
#endif
|
||
|
|
pr = get_phys_mem_range(s->port_map, port);
|
||
|
|
if (!pr) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
port -= pr->addr;
|
||
|
|
if ((pr->devio_flags >> size_log2) & 1) {
|
||
|
|
pr->write_func(pr->opaque, port, (uint32_t)val, size_log2);
|
||
|
|
} else if (size_log2 == 1 && (pr->devio_flags & DEVIO_SIZE8)) {
|
||
|
|
pr->write_func(pr->opaque, port, val & 0xff, 0);
|
||
|
|
pr->write_func(pr->opaque, port + 1, (val >> 8) & 0xff, 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static uint32_t ld_port(void *opaque, uint32_t port1, int size_log2)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
PhysMemoryRange *pr;
|
||
|
|
uint32_t val, port;
|
||
|
|
|
||
|
|
port = port1;
|
||
|
|
pr = get_phys_mem_range(s->port_map, port);
|
||
|
|
if (!pr) {
|
||
|
|
val = -1;
|
||
|
|
} else {
|
||
|
|
port -= pr->addr;
|
||
|
|
if ((pr->devio_flags >> size_log2) & 1) {
|
||
|
|
val = pr->read_func(pr->opaque, port, size_log2);
|
||
|
|
} else if (size_log2 == 1 && (pr->devio_flags & DEVIO_SIZE8)) {
|
||
|
|
val = pr->read_func(pr->opaque, port, 0) & 0xff;
|
||
|
|
val |= (pr->read_func(pr->opaque, port + 1, 0) & 0xff) << 8;
|
||
|
|
} else {
|
||
|
|
val = -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#ifdef DUMP_IOPORT
|
||
|
|
if (dump_port(port1))
|
||
|
|
printf("read port=0x%x val=0x%x s=%d\n", port1, val, 1 << size_log2);
|
||
|
|
#endif
|
||
|
|
return val;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pc_machine_set_defaults(VirtMachineParams *p)
|
||
|
|
{
|
||
|
|
p->accel_enable = TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
|
||
|
|
static void sigalrm_handler(int sig)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
#define CPUID_APIC (1 << 9)
|
||
|
|
#define CPUID_ACPI (1 << 22)
|
||
|
|
|
||
|
|
static void kvm_set_cpuid(PCMachine *s)
|
||
|
|
{
|
||
|
|
struct kvm_cpuid2 *kvm_cpuid;
|
||
|
|
int n_ent_max, i;
|
||
|
|
struct kvm_cpuid_entry2 *ent;
|
||
|
|
|
||
|
|
n_ent_max = 128;
|
||
|
|
kvm_cpuid = mallocz(sizeof(struct kvm_cpuid2) + n_ent_max * sizeof(kvm_cpuid->entries[0]));
|
||
|
|
|
||
|
|
kvm_cpuid->nent = n_ent_max;
|
||
|
|
if (ioctl(s->kvm_fd, KVM_GET_SUPPORTED_CPUID, kvm_cpuid) < 0) {
|
||
|
|
perror("KVM_GET_SUPPORTED_CPUID");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
for(i = 0; i < kvm_cpuid->nent; i++) {
|
||
|
|
ent = &kvm_cpuid->entries[i];
|
||
|
|
/* remove the APIC & ACPI to be in sync with the emulator */
|
||
|
|
if (ent->function == 1 || ent->function == 0x80000001) {
|
||
|
|
ent->edx &= ~(CPUID_APIC | CPUID_ACPI);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ioctl(s->vcpu_fd, KVM_SET_CPUID2, kvm_cpuid) < 0) {
|
||
|
|
perror("KVM_SET_CPUID2");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
free(kvm_cpuid);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* XXX: should check overlapping mappings */
|
||
|
|
static void kvm_map_ram(PhysMemoryMap *mem_map, PhysMemoryRange *pr)
|
||
|
|
{
|
||
|
|
PCMachine *s = mem_map->opaque;
|
||
|
|
struct kvm_userspace_memory_region region;
|
||
|
|
int flags;
|
||
|
|
|
||
|
|
region.slot = pr - mem_map->phys_mem_range;
|
||
|
|
flags = 0;
|
||
|
|
if (pr->devram_flags & DEVRAM_FLAG_ROM)
|
||
|
|
flags |= KVM_MEM_READONLY;
|
||
|
|
if (pr->devram_flags & DEVRAM_FLAG_DIRTY_BITS)
|
||
|
|
flags |= KVM_MEM_LOG_DIRTY_PAGES;
|
||
|
|
region.flags = flags;
|
||
|
|
region.guest_phys_addr = pr->addr;
|
||
|
|
region.memory_size = pr->size;
|
||
|
|
#if 0
|
||
|
|
printf("map slot %d: %08lx %08lx\n",
|
||
|
|
region.slot, pr->addr, pr->size);
|
||
|
|
#endif
|
||
|
|
region.userspace_addr = (uintptr_t)pr->phys_mem;
|
||
|
|
if (ioctl(s->vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion) < 0) {
|
||
|
|
perror("KVM_SET_USER_MEMORY_REGION");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* XXX: just for one region */
|
||
|
|
static PhysMemoryRange *kvm_register_ram(PhysMemoryMap *mem_map, uint64_t addr,
|
||
|
|
uint64_t size, int devram_flags)
|
||
|
|
{
|
||
|
|
PhysMemoryRange *pr;
|
||
|
|
uint8_t *phys_mem;
|
||
|
|
|
||
|
|
pr = register_ram_entry(mem_map, addr, size, devram_flags);
|
||
|
|
|
||
|
|
phys_mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
|
||
|
|
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||
|
|
if (!phys_mem)
|
||
|
|
return NULL;
|
||
|
|
pr->phys_mem = phys_mem;
|
||
|
|
if (devram_flags & DEVRAM_FLAG_DIRTY_BITS) {
|
||
|
|
int n_pages = size >> 12;
|
||
|
|
pr->dirty_bits_size = ((n_pages + 63) / 64) * 8;
|
||
|
|
pr->dirty_bits = mallocz(pr->dirty_bits_size);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pr->size != 0) {
|
||
|
|
kvm_map_ram(mem_map, pr);
|
||
|
|
}
|
||
|
|
return pr;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_set_ram_addr(PhysMemoryMap *mem_map,
|
||
|
|
PhysMemoryRange *pr, uint64_t addr, BOOL enabled)
|
||
|
|
{
|
||
|
|
if (enabled) {
|
||
|
|
if (pr->size == 0 || addr != pr->addr) {
|
||
|
|
/* move or create the region */
|
||
|
|
pr->size = pr->org_size;
|
||
|
|
pr->addr = addr;
|
||
|
|
kvm_map_ram(mem_map, pr);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (pr->size != 0) {
|
||
|
|
pr->addr = 0;
|
||
|
|
pr->size = 0;
|
||
|
|
/* map a zero size region to disable */
|
||
|
|
kvm_map_ram(mem_map, pr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static const uint32_t *kvm_get_dirty_bits(PhysMemoryMap *mem_map,
|
||
|
|
PhysMemoryRange *pr)
|
||
|
|
{
|
||
|
|
PCMachine *s = mem_map->opaque;
|
||
|
|
struct kvm_dirty_log dlog;
|
||
|
|
|
||
|
|
if (pr->size == 0) {
|
||
|
|
/* not mapped: we assume no modification was made */
|
||
|
|
memset(pr->dirty_bits, 0, pr->dirty_bits_size);
|
||
|
|
} else {
|
||
|
|
dlog.slot = pr - mem_map->phys_mem_range;
|
||
|
|
dlog.dirty_bitmap = pr->dirty_bits;
|
||
|
|
if (ioctl(s->vm_fd, KVM_GET_DIRTY_LOG, &dlog) < 0) {
|
||
|
|
perror("KVM_GET_DIRTY_LOG");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return pr->dirty_bits;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_free_ram(PhysMemoryMap *mem_map, PhysMemoryRange *pr)
|
||
|
|
{
|
||
|
|
/* XXX: do it */
|
||
|
|
munmap(pr->phys_mem, pr->org_size);
|
||
|
|
free(pr->dirty_bits);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_pic_set_irq(void *opaque, int irq_num, int level)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
struct kvm_irq_level irq_level;
|
||
|
|
irq_level.irq = irq_num;
|
||
|
|
irq_level.level = level;
|
||
|
|
if (ioctl(s->vm_fd, KVM_IRQ_LINE, &irq_level) < 0) {
|
||
|
|
perror("KVM_IRQ_LINE");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_init(PCMachine *s)
|
||
|
|
{
|
||
|
|
int ret, i;
|
||
|
|
struct sigaction act;
|
||
|
|
struct kvm_pit_config pit_config;
|
||
|
|
uint64_t base_addr;
|
||
|
|
|
||
|
|
s->kvm_enabled = FALSE;
|
||
|
|
s->kvm_fd = open("/dev/kvm", O_RDWR);
|
||
|
|
if (s->kvm_fd < 0) {
|
||
|
|
fprintf(stderr, "KVM not available\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
ret = ioctl(s->kvm_fd, KVM_GET_API_VERSION, 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
perror("KVM_GET_API_VERSION");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
if (ret != 12) {
|
||
|
|
fprintf(stderr, "Unsupported KVM version\n");
|
||
|
|
close(s->kvm_fd);
|
||
|
|
s->kvm_fd = -1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
s->vm_fd = ioctl(s->kvm_fd, KVM_CREATE_VM, 0);
|
||
|
|
if (s->vm_fd < 0) {
|
||
|
|
perror("KVM_CREATE_VM");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* just before the BIOS */
|
||
|
|
base_addr = 0xfffbc000;
|
||
|
|
if (ioctl(s->vm_fd, KVM_SET_IDENTITY_MAP_ADDR, &base_addr) < 0) {
|
||
|
|
perror("KVM_SET_IDENTITY_MAP_ADDR");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ioctl(s->vm_fd, KVM_SET_TSS_ADDR, (long)(base_addr + 0x1000)) < 0) {
|
||
|
|
perror("KVM_SET_TSS_ADDR");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ioctl(s->vm_fd, KVM_CREATE_IRQCHIP, 0) < 0) {
|
||
|
|
perror("KVM_CREATE_IRQCHIP");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
memset(&pit_config, 0, sizeof(pit_config));
|
||
|
|
pit_config.flags = KVM_PIT_SPEAKER_DUMMY;
|
||
|
|
if (ioctl(s->vm_fd, KVM_CREATE_PIT2, &pit_config)) {
|
||
|
|
perror("KVM_CREATE_PIT2");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
s->vcpu_fd = ioctl(s->vm_fd, KVM_CREATE_VCPU, 0);
|
||
|
|
if (s->vcpu_fd < 0) {
|
||
|
|
perror("KVM_CREATE_VCPU");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
kvm_set_cpuid(s);
|
||
|
|
|
||
|
|
/* map the kvm_run structure */
|
||
|
|
s->kvm_run_size = ioctl(s->kvm_fd, KVM_GET_VCPU_MMAP_SIZE, NULL);
|
||
|
|
if (s->kvm_run_size < 0) {
|
||
|
|
perror("KVM_GET_VCPU_MMAP_SIZE");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
s->kvm_run = mmap(NULL, s->kvm_run_size, PROT_READ | PROT_WRITE,
|
||
|
|
MAP_SHARED, s->vcpu_fd, 0);
|
||
|
|
if (!s->kvm_run) {
|
||
|
|
perror("mmap kvm_run");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
for(i = 0; i < 16; i++) {
|
||
|
|
irq_init(&s->pic_irq[i], kvm_pic_set_irq, s, i);
|
||
|
|
}
|
||
|
|
|
||
|
|
act.sa_handler = sigalrm_handler;
|
||
|
|
sigemptyset(&act.sa_mask);
|
||
|
|
act.sa_flags = 0;
|
||
|
|
sigaction(SIGALRM, &act, NULL);
|
||
|
|
|
||
|
|
s->kvm_enabled = TRUE;
|
||
|
|
|
||
|
|
s->mem_map->register_ram = kvm_register_ram;
|
||
|
|
s->mem_map->free_ram = kvm_free_ram;
|
||
|
|
s->mem_map->get_dirty_bits = kvm_get_dirty_bits;
|
||
|
|
s->mem_map->set_ram_addr = kvm_set_ram_addr;
|
||
|
|
s->mem_map->opaque = s;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_exit_io(PCMachine *s, struct kvm_run *run)
|
||
|
|
{
|
||
|
|
uint8_t *ptr;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
ptr = (uint8_t *)run + run->io.data_offset;
|
||
|
|
// printf("port: addr=%04x\n", run->io.port);
|
||
|
|
|
||
|
|
for(i = 0; i < run->io.count; i++) {
|
||
|
|
if (run->io.direction == KVM_EXIT_IO_OUT) {
|
||
|
|
switch(run->io.size) {
|
||
|
|
case 1:
|
||
|
|
st_port(s, run->io.port, *(uint8_t *)ptr, 0);
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
st_port(s, run->io.port, *(uint16_t *)ptr, 1);
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
st_port(s, run->io.port, *(uint32_t *)ptr, 2);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
switch(run->io.size) {
|
||
|
|
case 1:
|
||
|
|
*(uint8_t *)ptr = ld_port(s, run->io.port, 0);
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
*(uint16_t *)ptr = ld_port(s, run->io.port, 1);
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
*(uint32_t *)ptr = ld_port(s, run->io.port, 2);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ptr += run->io.size;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_exit_mmio(PCMachine *s, struct kvm_run *run)
|
||
|
|
{
|
||
|
|
uint8_t *data = run->mmio.data;
|
||
|
|
PhysMemoryRange *pr;
|
||
|
|
uint64_t addr;
|
||
|
|
|
||
|
|
pr = get_phys_mem_range(s->mem_map, run->mmio.phys_addr);
|
||
|
|
if (run->mmio.is_write) {
|
||
|
|
if (!pr || pr->is_ram)
|
||
|
|
return;
|
||
|
|
addr = run->mmio.phys_addr - pr->addr;
|
||
|
|
switch(run->mmio.len) {
|
||
|
|
case 1:
|
||
|
|
if (pr->devio_flags & DEVIO_SIZE8) {
|
||
|
|
pr->write_func(pr->opaque, addr, *(uint8_t *)data, 0);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
if (pr->devio_flags & DEVIO_SIZE16) {
|
||
|
|
pr->write_func(pr->opaque, addr, *(uint16_t *)data, 1);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
if (pr->devio_flags & DEVIO_SIZE32) {
|
||
|
|
pr->write_func(pr->opaque, addr, *(uint32_t *)data, 2);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 8:
|
||
|
|
if (pr->devio_flags & DEVIO_SIZE32) {
|
||
|
|
pr->write_func(pr->opaque, addr, *(uint32_t *)data, 2);
|
||
|
|
pr->write_func(pr->opaque, addr + 4, *(uint32_t *)(data + 4), 2);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (!pr || pr->is_ram)
|
||
|
|
goto no_dev;
|
||
|
|
addr = run->mmio.phys_addr - pr->addr;
|
||
|
|
switch(run->mmio.len) {
|
||
|
|
case 1:
|
||
|
|
if (!(pr->devio_flags & DEVIO_SIZE8))
|
||
|
|
goto no_dev;
|
||
|
|
*(uint8_t *)data = pr->read_func(pr->opaque, addr, 0);
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
if (!(pr->devio_flags & DEVIO_SIZE16))
|
||
|
|
goto no_dev;
|
||
|
|
*(uint16_t *)data = pr->read_func(pr->opaque, addr, 1);
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
if (!(pr->devio_flags & DEVIO_SIZE32))
|
||
|
|
goto no_dev;
|
||
|
|
*(uint32_t *)data = pr->read_func(pr->opaque, addr, 2);
|
||
|
|
break;
|
||
|
|
case 8:
|
||
|
|
if (pr->devio_flags & DEVIO_SIZE32) {
|
||
|
|
*(uint32_t *)data =
|
||
|
|
pr->read_func(pr->opaque, addr, 2);
|
||
|
|
*(uint32_t *)(data + 4) =
|
||
|
|
pr->read_func(pr->opaque, addr + 4, 2);
|
||
|
|
} else {
|
||
|
|
no_dev:
|
||
|
|
memset(run->mmio.data, 0, run->mmio.len);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kvm_exec(PCMachine *s)
|
||
|
|
{
|
||
|
|
struct kvm_run *run = s->kvm_run;
|
||
|
|
struct itimerval ival;
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
/* Not efficient but simple: we use a timer to interrupt the
|
||
|
|
execution after a given time */
|
||
|
|
ival.it_interval.tv_sec = 0;
|
||
|
|
ival.it_interval.tv_usec = 0;
|
||
|
|
ival.it_value.tv_sec = 0;
|
||
|
|
ival.it_value.tv_usec = 10 * 1000; /* 10 ms max */
|
||
|
|
setitimer(ITIMER_REAL, &ival, NULL);
|
||
|
|
|
||
|
|
ret = ioctl(s->vcpu_fd, KVM_RUN, 0);
|
||
|
|
if (ret < 0) {
|
||
|
|
if (errno == EINTR || errno == EAGAIN) {
|
||
|
|
/* timeout */
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
perror("KVM_RUN");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
// printf("exit=%d\n", run->exit_reason);
|
||
|
|
switch(run->exit_reason) {
|
||
|
|
case KVM_EXIT_HLT:
|
||
|
|
break;
|
||
|
|
case KVM_EXIT_IO:
|
||
|
|
kvm_exit_io(s, run);
|
||
|
|
break;
|
||
|
|
case KVM_EXIT_MMIO:
|
||
|
|
kvm_exit_mmio(s, run);
|
||
|
|
break;
|
||
|
|
case KVM_EXIT_FAIL_ENTRY:
|
||
|
|
fprintf(stderr, "KVM_EXIT_FAIL_ENTRY: reason=0x%" PRIx64 "\n",
|
||
|
|
(uint64_t)run->fail_entry.hardware_entry_failure_reason);
|
||
|
|
#if 0
|
||
|
|
{
|
||
|
|
struct kvm_regs regs;
|
||
|
|
if (ioctl(s->vcpu_fd, KVM_GET_REGS, ®s) < 0) {
|
||
|
|
perror("KVM_SET_REGS");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
printf("RIP=%016" PRIx64 "\n", (uint64_t)regs.rip);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
exit(1);
|
||
|
|
case KVM_EXIT_INTERNAL_ERROR:
|
||
|
|
fprintf(stderr, "KVM_EXIT_INTERNAL_ERROR: suberror=0x%x\n",
|
||
|
|
(uint32_t)run->internal.suberror);
|
||
|
|
exit(1);
|
||
|
|
default:
|
||
|
|
fprintf(stderr, "KVM: unsupported exit_reason=%d\n", run->exit_reason);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if defined(EMSCRIPTEN)
|
||
|
|
/* with Javascript clock_gettime() is not enough precise enough to
|
||
|
|
have a reliable TSC counter. XXX: increment the cycles during the
|
||
|
|
power down time */
|
||
|
|
static uint64_t cpu_get_tsc(void *opaque)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
uint64_t c;
|
||
|
|
c = x86_cpu_get_cycles(s->cpu_state);
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
|
||
|
|
#define TSC_FREQ 100000000
|
||
|
|
|
||
|
|
static uint64_t cpu_get_tsc(void *opaque)
|
||
|
|
{
|
||
|
|
struct timespec ts;
|
||
|
|
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (uint64_t)ts.tv_sec * TSC_FREQ +
|
||
|
|
(ts.tv_nsec / (1000000000 / TSC_FREQ));
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static void pc_flush_tlb_write_range(void *opaque, uint8_t *ram_addr,
|
||
|
|
size_t ram_size)
|
||
|
|
{
|
||
|
|
PCMachine *s = opaque;
|
||
|
|
x86_cpu_flush_tlb_write_range_ram(s->cpu_state, ram_addr, ram_size);
|
||
|
|
}
|
||
|
|
|
||
|
|
static VirtMachine *pc_machine_init(const VirtMachineParams *p)
|
||
|
|
{
|
||
|
|
PCMachine *s;
|
||
|
|
int i, piix3_devfn;
|
||
|
|
PCIBus *pci_bus;
|
||
|
|
VIRTIOBusDef vbus_s, *vbus = &vbus_s;
|
||
|
|
|
||
|
|
if (strcmp(p->machine_name, "pc") != 0) {
|
||
|
|
vm_error("unsupported machine: %s\n", p->machine_name);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
assert(p->ram_size >= (1 << 20));
|
||
|
|
|
||
|
|
s = mallocz(sizeof(*s));
|
||
|
|
s->common.vmc = p->vmc;
|
||
|
|
s->ram_size = p->ram_size;
|
||
|
|
|
||
|
|
s->port_map = phys_mem_map_init();
|
||
|
|
s->mem_map = phys_mem_map_init();
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (p->accel_enable) {
|
||
|
|
kvm_init(s);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (!s->kvm_enabled)
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
s->cpu_state = x86_cpu_init(s->mem_map);
|
||
|
|
x86_cpu_set_get_tsc(s->cpu_state, cpu_get_tsc, s);
|
||
|
|
x86_cpu_set_port_io(s->cpu_state, ld_port, st_port, s);
|
||
|
|
|
||
|
|
/* needed to handle the RAM dirty bits */
|
||
|
|
s->mem_map->opaque = s;
|
||
|
|
s->mem_map->flush_tlb_write_range = pc_flush_tlb_write_range;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* set the RAM mapping and leave the VGA addresses empty */
|
||
|
|
cpu_register_ram(s->mem_map, 0xc0000, p->ram_size - 0xc0000, 0);
|
||
|
|
cpu_register_ram(s->mem_map, 0, 0xa0000, 0);
|
||
|
|
|
||
|
|
/* devices */
|
||
|
|
cpu_register_device(s->port_map, 0x80, 2, s, port80_read, port80_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
cpu_register_device(s->port_map, 0x92, 2, s, port92_read, port92_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
|
||
|
|
/* setup the bios */
|
||
|
|
if (p->files[VM_FILE_BIOS].len > 0) {
|
||
|
|
int bios_size, bios_size1;
|
||
|
|
uint8_t *bios_buf, *ptr;
|
||
|
|
uint32_t bios_addr;
|
||
|
|
|
||
|
|
bios_size = p->files[VM_FILE_BIOS].len;
|
||
|
|
bios_buf = p->files[VM_FILE_BIOS].buf;
|
||
|
|
assert((bios_size % 65536) == 0 && bios_size != 0);
|
||
|
|
bios_addr = -bios_size;
|
||
|
|
/* at the top of the 4GB memory */
|
||
|
|
cpu_register_ram(s->mem_map, bios_addr, bios_size, DEVRAM_FLAG_ROM);
|
||
|
|
ptr = get_ram_ptr(s, bios_addr);
|
||
|
|
memcpy(ptr, bios_buf, bios_size);
|
||
|
|
/* in the lower 1MB memory (currently set as RAM) */
|
||
|
|
bios_size1 = min_int(bios_size, 128 * 1024);
|
||
|
|
ptr = get_ram_ptr(s, 0x100000 - bios_size1);
|
||
|
|
memcpy(ptr, bios_buf + bios_size - bios_size1, bios_size1);
|
||
|
|
#ifdef DEBUG_BIOS
|
||
|
|
cpu_register_device(s->port_map, 0x402, 2, s,
|
||
|
|
bios_debug_read, bios_debug_write,
|
||
|
|
DEVIO_SIZE8);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (!s->kvm_enabled)
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
s->pic_state = pic2_init(s->port_map, 0x20, 0xa0,
|
||
|
|
0x4d0, 0x4d1,
|
||
|
|
pic_set_irq_cb, s,
|
||
|
|
s->pic_irq);
|
||
|
|
x86_cpu_set_get_hard_intno(s->cpu_state, get_hard_intno_cb, s);
|
||
|
|
s->pit_state = pit_init(s->port_map, 0x40, 0x61, &s->pic_irq[0],
|
||
|
|
pit_get_ticks_cb, s);
|
||
|
|
}
|
||
|
|
|
||
|
|
s->cmos_state = cmos_init(s->port_map, 0x70, &s->pic_irq[8],
|
||
|
|
p->rtc_local_time);
|
||
|
|
|
||
|
|
/* various cmos data */
|
||
|
|
{
|
||
|
|
int size;
|
||
|
|
/* memory size */
|
||
|
|
size = min_int((s->ram_size - (1 << 20)) >> 10, 65535);
|
||
|
|
put_le16(s->cmos_state->cmos_data + 0x30, size);
|
||
|
|
if (s->ram_size >= (16 << 20)) {
|
||
|
|
size = min_int((s->ram_size - (16 << 20)) >> 16, 65535);
|
||
|
|
put_le16(s->cmos_state->cmos_data + 0x34, size);
|
||
|
|
}
|
||
|
|
s->cmos_state->cmos_data[0x14] = 0x06; /* mouse + FPU present */
|
||
|
|
}
|
||
|
|
|
||
|
|
s->i440fx_state = i440fx_init(&pci_bus, &piix3_devfn, s->mem_map,
|
||
|
|
s->port_map, s->pic_irq);
|
||
|
|
|
||
|
|
s->common.console = p->console;
|
||
|
|
/* serial console */
|
||
|
|
if (0) {
|
||
|
|
s->serial_state = serial_init(s->port_map, 0x3f8, &s->pic_irq[4],
|
||
|
|
serial_write_cb, s);
|
||
|
|
}
|
||
|
|
|
||
|
|
memset(vbus, 0, sizeof(*vbus));
|
||
|
|
vbus->pci_bus = pci_bus;
|
||
|
|
|
||
|
|
if (p->console) {
|
||
|
|
/* virtio console */
|
||
|
|
s->common.console_dev = virtio_console_init(vbus, p->console);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* block devices */
|
||
|
|
for(i = 0; i < p->drive_count;) {
|
||
|
|
const VMDriveEntry *de = &p->tab_drive[i];
|
||
|
|
|
||
|
|
if (!de->device || !strcmp(de->device, "virtio")) {
|
||
|
|
virtio_block_init(vbus, p->tab_drive[i].block_dev);
|
||
|
|
i++;
|
||
|
|
} else if (!strcmp(de->device, "ide")) {
|
||
|
|
BlockDevice *tab_bs[2];
|
||
|
|
|
||
|
|
tab_bs[0] = p->tab_drive[i++].block_dev;
|
||
|
|
tab_bs[1] = NULL;
|
||
|
|
if (i < p->drive_count)
|
||
|
|
tab_bs[1] = p->tab_drive[i++].block_dev;
|
||
|
|
ide_init(s->port_map, 0x1f0, 0x3f6, &s->pic_irq[14], tab_bs);
|
||
|
|
piix3_ide_init(pci_bus, piix3_devfn + 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* virtio filesystem */
|
||
|
|
for(i = 0; i < p->fs_count; i++) {
|
||
|
|
virtio_9p_init(vbus, p->tab_fs[i].fs_dev,
|
||
|
|
p->tab_fs[i].tag);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p->display_device) {
|
||
|
|
FBDevice *fb_dev;
|
||
|
|
|
||
|
|
fb_dev = mallocz(sizeof(*fb_dev));
|
||
|
|
s->common.fb_dev = fb_dev;
|
||
|
|
if (!strcmp(p->display_device, "vga")) {
|
||
|
|
int bios_size;
|
||
|
|
uint8_t *bios_buf;
|
||
|
|
bios_size = p->files[VM_FILE_VGA_BIOS].len;
|
||
|
|
bios_buf = p->files[VM_FILE_VGA_BIOS].buf;
|
||
|
|
pci_vga_init(pci_bus, fb_dev, p->width, p->height,
|
||
|
|
bios_buf, bios_size);
|
||
|
|
} else if (!strcmp(p->display_device, "simplefb")) {
|
||
|
|
simplefb_init(s->mem_map,
|
||
|
|
FRAMEBUFFER_BASE_ADDR,
|
||
|
|
fb_dev, p->width, p->height);
|
||
|
|
} else {
|
||
|
|
vm_error("unsupported display device: %s\n", p->display_device);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p->input_device) {
|
||
|
|
if (!strcmp(p->input_device, "virtio")) {
|
||
|
|
s->keyboard_dev = virtio_input_init(vbus, VIRTIO_INPUT_TYPE_KEYBOARD);
|
||
|
|
|
||
|
|
s->mouse_dev = virtio_input_init(vbus, VIRTIO_INPUT_TYPE_TABLET);
|
||
|
|
} else if (!strcmp(p->input_device, "ps2")) {
|
||
|
|
s->kbd_state = i8042_init(&s->ps2_kbd, &s->ps2_mouse,
|
||
|
|
s->port_map,
|
||
|
|
&s->pic_irq[1], &s->pic_irq[12], 0x60);
|
||
|
|
/* vmmouse */
|
||
|
|
cpu_register_device(s->port_map, 0x5658, 1, s,
|
||
|
|
vmport_read, vmport_write,
|
||
|
|
DEVIO_SIZE32);
|
||
|
|
s->vm_mouse = vmmouse_init(s->ps2_mouse);
|
||
|
|
} else {
|
||
|
|
vm_error("unsupported input device: %s\n", p->input_device);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* virtio net device */
|
||
|
|
for(i = 0; i < p->eth_count; i++) {
|
||
|
|
virtio_net_init(vbus, p->tab_eth[i].net);
|
||
|
|
s->common.net = p->tab_eth[i].net;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (p->files[VM_FILE_KERNEL].buf) {
|
||
|
|
copy_kernel(s, p->files[VM_FILE_KERNEL].buf,
|
||
|
|
p->files[VM_FILE_KERNEL].len,
|
||
|
|
p->cmdline ? p->cmdline : "");
|
||
|
|
}
|
||
|
|
|
||
|
|
return (VirtMachine *)s;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pc_machine_end(VirtMachine *s1)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
/* XXX: free all */
|
||
|
|
if (s->cpu_state) {
|
||
|
|
x86_cpu_end(s->cpu_state);
|
||
|
|
}
|
||
|
|
phys_mem_map_end(s->mem_map);
|
||
|
|
phys_mem_map_end(s->port_map);
|
||
|
|
free(s);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pc_vm_send_key_event(VirtMachine *s1, BOOL is_down, uint16_t key_code)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
if (s->keyboard_dev) {
|
||
|
|
virtio_input_send_key_event(s->keyboard_dev, is_down, key_code);
|
||
|
|
} else if (s->ps2_kbd) {
|
||
|
|
ps2_put_keycode(s->ps2_kbd, is_down, key_code);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static BOOL pc_vm_mouse_is_absolute(VirtMachine *s1)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
if (s->mouse_dev) {
|
||
|
|
return TRUE;
|
||
|
|
} else if (s->vm_mouse) {
|
||
|
|
return vmmouse_is_absolute(s->vm_mouse);
|
||
|
|
} else {
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pc_vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz,
|
||
|
|
unsigned int buttons)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
if (s->mouse_dev) {
|
||
|
|
virtio_input_send_mouse_event(s->mouse_dev, dx, dy, dz, buttons);
|
||
|
|
} else if (s->vm_mouse) {
|
||
|
|
vmmouse_send_mouse_event(s->vm_mouse, dx, dy, dz, buttons);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
struct screen_info {
|
||
|
|
} __attribute__((packed));
|
||
|
|
|
||
|
|
/* from plex86 (BSD license) */
|
||
|
|
struct __attribute__ ((packed)) linux_params {
|
||
|
|
/* screen_info structure */
|
||
|
|
uint8_t orig_x; /* 0x00 */
|
||
|
|
uint8_t orig_y; /* 0x01 */
|
||
|
|
uint16_t ext_mem_k; /* 0x02 */
|
||
|
|
uint16_t orig_video_page; /* 0x04 */
|
||
|
|
uint8_t orig_video_mode; /* 0x06 */
|
||
|
|
uint8_t orig_video_cols; /* 0x07 */
|
||
|
|
uint8_t flags; /* 0x08 */
|
||
|
|
uint8_t unused2; /* 0x09 */
|
||
|
|
uint16_t orig_video_ega_bx;/* 0x0a */
|
||
|
|
uint16_t unused3; /* 0x0c */
|
||
|
|
uint8_t orig_video_lines; /* 0x0e */
|
||
|
|
uint8_t orig_video_isVGA; /* 0x0f */
|
||
|
|
uint16_t orig_video_points;/* 0x10 */
|
||
|
|
|
||
|
|
/* VESA graphic mode -- linear frame buffer */
|
||
|
|
uint16_t lfb_width; /* 0x12 */
|
||
|
|
uint16_t lfb_height; /* 0x14 */
|
||
|
|
uint16_t lfb_depth; /* 0x16 */
|
||
|
|
uint32_t lfb_base; /* 0x18 */
|
||
|
|
uint32_t lfb_size; /* 0x1c */
|
||
|
|
uint16_t cl_magic, cl_offset; /* 0x20 */
|
||
|
|
uint16_t lfb_linelength; /* 0x24 */
|
||
|
|
uint8_t red_size; /* 0x26 */
|
||
|
|
uint8_t red_pos; /* 0x27 */
|
||
|
|
uint8_t green_size; /* 0x28 */
|
||
|
|
uint8_t green_pos; /* 0x29 */
|
||
|
|
uint8_t blue_size; /* 0x2a */
|
||
|
|
uint8_t blue_pos; /* 0x2b */
|
||
|
|
uint8_t rsvd_size; /* 0x2c */
|
||
|
|
uint8_t rsvd_pos; /* 0x2d */
|
||
|
|
uint16_t vesapm_seg; /* 0x2e */
|
||
|
|
uint16_t vesapm_off; /* 0x30 */
|
||
|
|
uint16_t pages; /* 0x32 */
|
||
|
|
uint16_t vesa_attributes; /* 0x34 */
|
||
|
|
uint32_t capabilities; /* 0x36 */
|
||
|
|
uint32_t ext_lfb_base; /* 0x3a */
|
||
|
|
uint8_t _reserved[2]; /* 0x3e */
|
||
|
|
|
||
|
|
/* 0x040 */ uint8_t apm_bios_info[20]; // struct apm_bios_info
|
||
|
|
/* 0x054 */ uint8_t pad2[0x80 - 0x54];
|
||
|
|
|
||
|
|
// Following 2 from 'struct drive_info_struct' in drivers/block/cciss.h.
|
||
|
|
// Might be truncated?
|
||
|
|
/* 0x080 */ uint8_t hd0_info[16]; // hd0-disk-parameter from intvector 0x41
|
||
|
|
/* 0x090 */ uint8_t hd1_info[16]; // hd1-disk-parameter from intvector 0x46
|
||
|
|
|
||
|
|
// System description table truncated to 16 bytes
|
||
|
|
// From 'struct sys_desc_table_struct' in linux/arch/i386/kernel/setup.c.
|
||
|
|
/* 0x0a0 */ uint16_t sys_description_len;
|
||
|
|
/* 0x0a2 */ uint8_t sys_description_table[14];
|
||
|
|
// [0] machine id
|
||
|
|
// [1] machine submodel id
|
||
|
|
// [2] BIOS revision
|
||
|
|
// [3] bit1: MCA bus
|
||
|
|
|
||
|
|
/* 0x0b0 */ uint8_t pad3[0x1e0 - 0xb0];
|
||
|
|
/* 0x1e0 */ uint32_t alt_mem_k;
|
||
|
|
/* 0x1e4 */ uint8_t pad4[4];
|
||
|
|
/* 0x1e8 */ uint8_t e820map_entries;
|
||
|
|
/* 0x1e9 */ uint8_t eddbuf_entries; // EDD_NR
|
||
|
|
/* 0x1ea */ uint8_t pad5[0x1f1 - 0x1ea];
|
||
|
|
/* 0x1f1 */ uint8_t setup_sects; // size of setup.S, number of sectors
|
||
|
|
/* 0x1f2 */ uint16_t mount_root_rdonly; // MOUNT_ROOT_RDONLY (if !=0)
|
||
|
|
/* 0x1f4 */ uint16_t sys_size; // size of compressed kernel-part in the
|
||
|
|
// (b)zImage-file (in 16 byte units, rounded up)
|
||
|
|
/* 0x1f6 */ uint16_t swap_dev; // (unused AFAIK)
|
||
|
|
/* 0x1f8 */ uint16_t ramdisk_flags;
|
||
|
|
/* 0x1fa */ uint16_t vga_mode; // (old one)
|
||
|
|
/* 0x1fc */ uint16_t orig_root_dev; // (high=Major, low=minor)
|
||
|
|
/* 0x1fe */ uint8_t pad6[1];
|
||
|
|
/* 0x1ff */ uint8_t aux_device_info;
|
||
|
|
/* 0x200 */ uint16_t jump_setup; // Jump to start of setup code,
|
||
|
|
// aka "reserved" field.
|
||
|
|
/* 0x202 */ uint8_t setup_signature[4]; // Signature for SETUP-header, ="HdrS"
|
||
|
|
/* 0x206 */ uint16_t header_format_version; // Version number of header format;
|
||
|
|
/* 0x208 */ uint8_t setup_S_temp0[8]; // Used by setup.S for communication with
|
||
|
|
// boot loaders, look there.
|
||
|
|
/* 0x210 */ uint8_t loader_type;
|
||
|
|
// 0 for old one.
|
||
|
|
// else 0xTV:
|
||
|
|
// T=0: LILO
|
||
|
|
// T=1: Loadlin
|
||
|
|
// T=2: bootsect-loader
|
||
|
|
// T=3: SYSLINUX
|
||
|
|
// T=4: ETHERBOOT
|
||
|
|
// V=version
|
||
|
|
/* 0x211 */ uint8_t loadflags;
|
||
|
|
// bit0 = 1: kernel is loaded high (bzImage)
|
||
|
|
// bit7 = 1: Heap and pointer (see below) set by boot
|
||
|
|
// loader.
|
||
|
|
/* 0x212 */ uint16_t setup_S_temp1;
|
||
|
|
/* 0x214 */ uint32_t kernel_start;
|
||
|
|
/* 0x218 */ uint32_t initrd_start;
|
||
|
|
/* 0x21c */ uint32_t initrd_size;
|
||
|
|
/* 0x220 */ uint8_t setup_S_temp2[4];
|
||
|
|
/* 0x224 */ uint16_t setup_S_heap_end_pointer;
|
||
|
|
/* 0x226 */ uint16_t pad70;
|
||
|
|
/* 0x228 */ uint32_t cmd_line_ptr;
|
||
|
|
/* 0x22c */ uint8_t pad7[0x2d0 - 0x22c];
|
||
|
|
|
||
|
|
/* 0x2d0 : Int 15, ax=e820 memory map. */
|
||
|
|
// (linux/include/asm-i386/e820.h, 'struct e820entry')
|
||
|
|
#define E820MAX 32
|
||
|
|
#define E820_RAM 1
|
||
|
|
#define E820_RESERVED 2
|
||
|
|
#define E820_ACPI 3 /* usable as RAM once ACPI tables have been read */
|
||
|
|
#define E820_NVS 4
|
||
|
|
struct {
|
||
|
|
uint64_t addr;
|
||
|
|
uint64_t size;
|
||
|
|
uint32_t type;
|
||
|
|
} e820map[E820MAX];
|
||
|
|
|
||
|
|
/* 0x550 */ uint8_t pad8[0x600 - 0x550];
|
||
|
|
|
||
|
|
// BIOS Enhanced Disk Drive Services.
|
||
|
|
// (From linux/include/asm-i386/edd.h, 'struct edd_info')
|
||
|
|
// Each 'struct edd_info is 78 bytes, times a max of 6 structs in array.
|
||
|
|
/* 0x600 */ uint8_t eddbuf[0x7d4 - 0x600];
|
||
|
|
|
||
|
|
/* 0x7d4 */ uint8_t pad9[0x800 - 0x7d4];
|
||
|
|
/* 0x800 */ uint8_t commandline[0x800];
|
||
|
|
|
||
|
|
uint64_t gdt_table[4];
|
||
|
|
};
|
||
|
|
|
||
|
|
#define KERNEL_PARAMS_ADDR 0x00090000
|
||
|
|
|
||
|
|
static void copy_kernel(PCMachine *s, const uint8_t *buf, int buf_len,
|
||
|
|
const char *cmd_line)
|
||
|
|
{
|
||
|
|
uint8_t *ram_ptr;
|
||
|
|
int setup_sects, header_len, copy_len, setup_hdr_start, setup_hdr_end;
|
||
|
|
uint32_t load_address;
|
||
|
|
struct linux_params *params;
|
||
|
|
FBDevice *fb_dev;
|
||
|
|
|
||
|
|
if (buf_len < 1024) {
|
||
|
|
too_small:
|
||
|
|
fprintf(stderr, "Kernel too small\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
if (buf[0x1fe] != 0x55 || buf[0x1ff] != 0xaa) {
|
||
|
|
fprintf(stderr, "Invalid kernel magic\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
setup_sects = buf[0x1f1];
|
||
|
|
if (setup_sects == 0)
|
||
|
|
setup_sects = 4;
|
||
|
|
header_len = (setup_sects + 1) * 512;
|
||
|
|
if (buf_len < header_len)
|
||
|
|
goto too_small;
|
||
|
|
if (memcmp(buf + 0x202, "HdrS", 4) != 0) {
|
||
|
|
fprintf(stderr, "Kernel too old\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
load_address = 0x100000; /* we don't support older protocols */
|
||
|
|
|
||
|
|
ram_ptr = get_ram_ptr(s, load_address);
|
||
|
|
copy_len = buf_len - header_len;
|
||
|
|
if (copy_len > (s->ram_size - load_address)) {
|
||
|
|
fprintf(stderr, "Not enough RAM\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
memcpy(ram_ptr, buf + header_len, copy_len);
|
||
|
|
|
||
|
|
params = (void *)get_ram_ptr(s, KERNEL_PARAMS_ADDR);
|
||
|
|
|
||
|
|
memset(params, 0, sizeof(struct linux_params));
|
||
|
|
|
||
|
|
/* copy the setup header */
|
||
|
|
setup_hdr_start = 0x1f1;
|
||
|
|
setup_hdr_end = 0x202 + buf[0x201];
|
||
|
|
memcpy((uint8_t *)params + setup_hdr_start, buf + setup_hdr_start,
|
||
|
|
setup_hdr_end - setup_hdr_start);
|
||
|
|
|
||
|
|
strcpy((char *)params->commandline, cmd_line);
|
||
|
|
|
||
|
|
params->mount_root_rdonly = 0;
|
||
|
|
params->cmd_line_ptr = KERNEL_PARAMS_ADDR +
|
||
|
|
offsetof(struct linux_params, commandline);
|
||
|
|
params->alt_mem_k = (s->ram_size / 1024) - 1024;
|
||
|
|
params->loader_type = 0x01;
|
||
|
|
#if 0
|
||
|
|
if (initrd_size > 0) {
|
||
|
|
params->initrd_start = INITRD_LOAD_ADDR;
|
||
|
|
params->initrd_size = initrd_size;
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
params->orig_video_lines = 0;
|
||
|
|
params->orig_video_cols = 0;
|
||
|
|
|
||
|
|
fb_dev = s->common.fb_dev;
|
||
|
|
if (fb_dev) {
|
||
|
|
|
||
|
|
params->orig_video_isVGA = 0x23; /* VIDEO_TYPE_VLFB */
|
||
|
|
|
||
|
|
params->lfb_depth = 32;
|
||
|
|
params->red_size = 8;
|
||
|
|
params->red_pos = 16;
|
||
|
|
params->green_size = 8;
|
||
|
|
params->green_pos = 8;
|
||
|
|
params->blue_size = 8;
|
||
|
|
params->blue_pos = 0;
|
||
|
|
params->rsvd_size = 8;
|
||
|
|
params->rsvd_pos = 24;
|
||
|
|
|
||
|
|
params->lfb_width = fb_dev->width;
|
||
|
|
params->lfb_height = fb_dev->height;
|
||
|
|
params->lfb_linelength = fb_dev->stride;
|
||
|
|
params->lfb_size = fb_dev->fb_size;
|
||
|
|
params->lfb_base = FRAMEBUFFER_BASE_ADDR;
|
||
|
|
}
|
||
|
|
|
||
|
|
params->gdt_table[2] = 0x00cf9b000000ffffLL; /* CS */
|
||
|
|
params->gdt_table[3] = 0x00cf93000000ffffLL; /* DS */
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (s->kvm_enabled) {
|
||
|
|
struct kvm_sregs sregs;
|
||
|
|
struct kvm_segment seg;
|
||
|
|
struct kvm_regs regs;
|
||
|
|
|
||
|
|
/* init flat protected mode */
|
||
|
|
|
||
|
|
if (ioctl(s->vcpu_fd, KVM_GET_SREGS, &sregs) < 0) {
|
||
|
|
perror("KVM_GET_SREGS");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
sregs.cr0 |= (1 << 0); /* CR0_PE */
|
||
|
|
sregs.gdt.base = KERNEL_PARAMS_ADDR +
|
||
|
|
offsetof(struct linux_params, gdt_table);
|
||
|
|
sregs.gdt.limit = sizeof(params->gdt_table) - 1;
|
||
|
|
|
||
|
|
memset(&seg, 0, sizeof(seg));
|
||
|
|
seg.limit = 0xffffffff;
|
||
|
|
seg.present = 1;
|
||
|
|
seg.db = 1;
|
||
|
|
seg.s = 1; /* code/data */
|
||
|
|
seg.g = 1; /* 4KB granularity */
|
||
|
|
|
||
|
|
seg.type = 0xb; /* code */
|
||
|
|
seg.selector = 2 << 3;
|
||
|
|
sregs.cs = seg;
|
||
|
|
|
||
|
|
seg.type = 0x3; /* data */
|
||
|
|
seg.selector = 3 << 3;
|
||
|
|
sregs.ds = seg;
|
||
|
|
sregs.es = seg;
|
||
|
|
sregs.ss = seg;
|
||
|
|
sregs.fs = seg;
|
||
|
|
sregs.gs = seg;
|
||
|
|
|
||
|
|
if (ioctl(s->vcpu_fd, KVM_SET_SREGS, &sregs) < 0) {
|
||
|
|
perror("KVM_SET_SREGS");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
memset(®s, 0, sizeof(regs));
|
||
|
|
regs.rip = load_address;
|
||
|
|
regs.rsi = KERNEL_PARAMS_ADDR;
|
||
|
|
regs.rflags = 0x2;
|
||
|
|
if (ioctl(s->vcpu_fd, KVM_SET_REGS, ®s) < 0) {
|
||
|
|
perror("KVM_SET_REGS");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
X86CPUSeg sd;
|
||
|
|
uint32_t val;
|
||
|
|
val = x86_cpu_get_reg(s->cpu_state, X86_CPU_REG_CR0);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, X86_CPU_REG_CR0, val | (1 << 0));
|
||
|
|
|
||
|
|
sd.base = KERNEL_PARAMS_ADDR +
|
||
|
|
offsetof(struct linux_params, gdt_table);
|
||
|
|
sd.limit = sizeof(params->gdt_table) - 1;
|
||
|
|
x86_cpu_set_seg(s->cpu_state, X86_CPU_SEG_GDT, &sd);
|
||
|
|
sd.sel = 2 << 3;
|
||
|
|
sd.base = 0;
|
||
|
|
sd.limit = 0xffffffff;
|
||
|
|
sd.flags = 0xc09b;
|
||
|
|
x86_cpu_set_seg(s->cpu_state, X86_CPU_SEG_CS, &sd);
|
||
|
|
sd.sel = 3 << 3;
|
||
|
|
sd.flags = 0xc093;
|
||
|
|
for(i = 0; i < 6; i++) {
|
||
|
|
if (i != X86_CPU_SEG_CS) {
|
||
|
|
x86_cpu_set_seg(s->cpu_state, i, &sd);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
x86_cpu_set_reg(s->cpu_state, X86_CPU_REG_EIP, load_address);
|
||
|
|
x86_cpu_set_reg(s->cpu_state, 6, KERNEL_PARAMS_ADDR); /* esi */
|
||
|
|
}
|
||
|
|
|
||
|
|
/* map PCI interrupts (no BIOS, so we must do it) */
|
||
|
|
{
|
||
|
|
uint8_t elcr[2];
|
||
|
|
static const uint8_t pci_irqs[4] = { 9, 10, 11, 12 };
|
||
|
|
|
||
|
|
i440fx_map_interrupts(s->i440fx_state, elcr, pci_irqs);
|
||
|
|
/* XXX: KVM support */
|
||
|
|
if (s->pic_state) {
|
||
|
|
pic2_set_elcr(s->pic_state, elcr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* in ms */
|
||
|
|
static int pc_machine_get_sleep_duration(VirtMachine *s1, int delay)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (s->kvm_enabled) {
|
||
|
|
/* XXX: improve */
|
||
|
|
cmos_update_irq(s->cmos_state);
|
||
|
|
delay = 0;
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
cmos_update_irq(s->cmos_state);
|
||
|
|
delay = min_int(delay, pit_update_irq(s->pit_state));
|
||
|
|
if (!x86_cpu_get_power_down(s->cpu_state))
|
||
|
|
delay = 0;
|
||
|
|
}
|
||
|
|
return delay;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void pc_machine_interp(VirtMachine *s1, int max_exec_cycles)
|
||
|
|
{
|
||
|
|
PCMachine *s = (PCMachine *)s1;
|
||
|
|
#ifdef USE_KVM
|
||
|
|
if (s->kvm_enabled) {
|
||
|
|
kvm_exec(s);
|
||
|
|
} else
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
x86_cpu_interp(s->cpu_state, max_exec_cycles);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const VirtMachineClass pc_machine_class = {
|
||
|
|
"pc",
|
||
|
|
pc_machine_set_defaults,
|
||
|
|
pc_machine_init,
|
||
|
|
pc_machine_end,
|
||
|
|
pc_machine_get_sleep_duration,
|
||
|
|
pc_machine_interp,
|
||
|
|
pc_vm_mouse_is_absolute,
|
||
|
|
pc_vm_send_mouse_event,
|
||
|
|
pc_vm_send_key_event,
|
||
|
|
};
|