From 86797937017f52bff088d02edf64fb931177a7ea Mon Sep 17 00:00:00 2001 From: Jun Nakajima Date: Sat, 29 Jan 2011 14:24:24 -0800 Subject: New files are from the upstream QEMU (0.10.5), and the code is based on the following commit (dated 2009-06-19, see CHANGES.TXT): d2e9fd8f703203c2eeeed120b1ef6c3a6574e0ab new file: hw/apic.c new file: hw/fdc.h new file: hw/fw_cfg.c new file: hw/fw_cfg.h new file: hw/i8254.c new file: hw/i8259.c new file: hw/ioapic.c new file: hw/mc146818rtc.c new file: hw/ne2000.c new file: hw/pc.c new file: hw/pckbd.c new file: hw/piix_pci.c new file: hw/ps2.c new file: hw/ps2.h new file: hw/smbios.c new file: target-i386/TODO new file: target-i386/cpu.h new file: target-i386/exec.h new file: target-i386/helper.c new file: target-i386/helper.h new file: target-i386/helper_template.h new file: target-i386/kvm.c new file: target-i386/machine.c new file: target-i386/op_helper.c new file: target-i386/ops_sse.h new file: target-i386/ops_sse_header.h new file: target-i386/svm.h new file: target-i386/translate.c Change-Id: I55c62ed7516f002c882705545e7c21997ece9927 --- hw/apic.c | 967 ++++++++++++++++++++++++++++++++++++++++ hw/fdc.h | 11 + hw/fw_cfg.c | 288 ++++++++++++ hw/fw_cfg.h | 40 ++ hw/i8254.c | 507 +++++++++++++++++++++ hw/i8259.c | 570 ++++++++++++++++++++++++ hw/ioapic.c | 261 +++++++++++ hw/mc146818rtc.c | 754 +++++++++++++++++++++++++++++++ hw/ne2000.c | 840 ++++++++++++++++++++++++++++++++++ hw/pc.c | 1310 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pckbd.c | 446 +++++++++++++++++++ hw/piix_pci.c | 374 ++++++++++++++++ hw/ps2.c | 611 +++++++++++++++++++++++++ hw/ps2.h | 9 + hw/smbios.c | 224 ++++++++++ 15 files changed, 7212 insertions(+) create mode 100644 hw/apic.c create mode 100644 hw/fdc.h create mode 100644 hw/fw_cfg.c create mode 100644 hw/fw_cfg.h create mode 100644 hw/i8254.c create mode 100644 hw/i8259.c create mode 100644 hw/ioapic.c create mode 100644 hw/mc146818rtc.c create mode 100644 hw/ne2000.c create mode 100644 hw/pc.c create mode 100644 hw/pckbd.c create mode 100644 hw/piix_pci.c create mode 100644 hw/ps2.c create mode 100644 hw/ps2.h create mode 100644 hw/smbios.c (limited to 'hw') diff --git a/hw/apic.c b/hw/apic.c new file mode 100644 index 0000000..b059185 --- /dev/null +++ b/hw/apic.c @@ -0,0 +1,967 @@ +/* + * APIC support + * + * Copyright (c) 2004-2005 Fabrice Bellard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ +#include "hw.h" +#include "pc.h" +#include "qemu-timer.h" +#include "host-utils.h" + +//#define DEBUG_APIC + +/* APIC Local Vector Table */ +#define APIC_LVT_TIMER 0 +#define APIC_LVT_THERMAL 1 +#define APIC_LVT_PERFORM 2 +#define APIC_LVT_LINT0 3 +#define APIC_LVT_LINT1 4 +#define APIC_LVT_ERROR 5 +#define APIC_LVT_NB 6 + +/* APIC delivery modes */ +#define APIC_DM_FIXED 0 +#define APIC_DM_LOWPRI 1 +#define APIC_DM_SMI 2 +#define APIC_DM_NMI 4 +#define APIC_DM_INIT 5 +#define APIC_DM_SIPI 6 +#define APIC_DM_EXTINT 7 + +/* APIC destination mode */ +#define APIC_DESTMODE_FLAT 0xf +#define APIC_DESTMODE_CLUSTER 1 + +#define APIC_TRIGGER_EDGE 0 +#define APIC_TRIGGER_LEVEL 1 + +#define APIC_LVT_TIMER_PERIODIC (1<<17) +#define APIC_LVT_MASKED (1<<16) +#define APIC_LVT_LEVEL_TRIGGER (1<<15) +#define APIC_LVT_REMOTE_IRR (1<<14) +#define APIC_INPUT_POLARITY (1<<13) +#define APIC_SEND_PENDING (1<<12) + +#define ESR_ILLEGAL_ADDRESS (1 << 7) + +#define APIC_SV_ENABLE (1 << 8) + +#define MAX_APICS 255 +#define MAX_APIC_WORDS 8 + +typedef struct APICState { + CPUState *cpu_env; + uint32_t apicbase; + uint8_t id; + uint8_t arb_id; + uint8_t tpr; + uint32_t spurious_vec; + uint8_t log_dest; + uint8_t dest_mode; + uint32_t isr[8]; /* in service register */ + uint32_t tmr[8]; /* trigger mode register */ + uint32_t irr[8]; /* interrupt request register */ + uint32_t lvt[APIC_LVT_NB]; + uint32_t esr; /* error register */ + uint32_t icr[2]; + + uint32_t divide_conf; + int count_shift; + uint32_t initial_count; + int64_t initial_count_load_time, next_time; + uint32_t idx; + QEMUTimer *timer; + int sipi_vector; + int wait_for_sipi; +} APICState; + +static int apic_io_memory; +static APICState *local_apics[MAX_APICS + 1]; +static int last_apic_idx = 0; +static int apic_irq_delivered; + + +static void apic_set_irq(APICState *s, int vector_num, int trigger_mode); +static void apic_update_irq(APICState *s); +static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask, + uint8_t dest, uint8_t dest_mode); + +/* Find first bit starting from msb */ +static int fls_bit(uint32_t value) +{ + return 31 - clz32(value); +} + +/* Find first bit starting from lsb */ +static int ffs_bit(uint32_t value) +{ + return ctz32(value); +} + +static inline void set_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] |= mask; +} + +static inline void reset_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] &= ~mask; +} + +static inline int get_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + return !!(tab[i] & mask); +} + +static void apic_local_deliver(CPUState *env, int vector) +{ + APICState *s = env->apic_state; + uint32_t lvt = s->lvt[vector]; + int trigger_mode; + + if (lvt & APIC_LVT_MASKED) + return; + + switch ((lvt >> 8) & 7) { + case APIC_DM_SMI: + cpu_interrupt(env, CPU_INTERRUPT_SMI); + break; + + case APIC_DM_NMI: + cpu_interrupt(env, CPU_INTERRUPT_NMI); + break; + + case APIC_DM_EXTINT: + cpu_interrupt(env, CPU_INTERRUPT_HARD); + break; + + case APIC_DM_FIXED: + trigger_mode = APIC_TRIGGER_EDGE; + if ((vector == APIC_LVT_LINT0 || vector == APIC_LVT_LINT1) && + (lvt & APIC_LVT_LEVEL_TRIGGER)) + trigger_mode = APIC_TRIGGER_LEVEL; + apic_set_irq(s, lvt & 0xff, trigger_mode); + } +} + +void apic_deliver_pic_intr(CPUState *env, int level) +{ + if (level) + apic_local_deliver(env, APIC_LVT_LINT0); + else { + APICState *s = env->apic_state; + uint32_t lvt = s->lvt[APIC_LVT_LINT0]; + + switch ((lvt >> 8) & 7) { + case APIC_DM_FIXED: + if (!(lvt & APIC_LVT_LEVEL_TRIGGER)) + break; + reset_bit(s->irr, lvt & 0xff); + /* fall through */ + case APIC_DM_EXTINT: + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + break; + } + } +} + +#define foreach_apic(apic, deliver_bitmask, code) \ +{\ + int __i, __j, __mask;\ + for(__i = 0; __i < MAX_APIC_WORDS; __i++) {\ + __mask = deliver_bitmask[__i];\ + if (__mask) {\ + for(__j = 0; __j < 32; __j++) {\ + if (__mask & (1 << __j)) {\ + apic = local_apics[__i * 32 + __j];\ + if (apic) {\ + code;\ + }\ + }\ + }\ + }\ + }\ +} + +static void apic_bus_deliver(const uint32_t *deliver_bitmask, + uint8_t delivery_mode, + uint8_t vector_num, uint8_t polarity, + uint8_t trigger_mode) +{ + APICState *apic_iter; + + switch (delivery_mode) { + case APIC_DM_LOWPRI: + /* XXX: search for focus processor, arbitration */ + { + int i, d; + d = -1; + for(i = 0; i < MAX_APIC_WORDS; i++) { + if (deliver_bitmask[i]) { + d = i * 32 + ffs_bit(deliver_bitmask[i]); + break; + } + } + if (d >= 0) { + apic_iter = local_apics[d]; + if (apic_iter) { + apic_set_irq(apic_iter, vector_num, trigger_mode); + } + } + } + return; + + case APIC_DM_FIXED: + break; + + case APIC_DM_SMI: + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(apic_iter->cpu_env, CPU_INTERRUPT_SMI) ); + return; + + case APIC_DM_NMI: + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(apic_iter->cpu_env, CPU_INTERRUPT_NMI) ); + return; + + case APIC_DM_INIT: + /* normal INIT IPI sent to processors */ + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(apic_iter->cpu_env, CPU_INTERRUPT_INIT) ); + return; + + case APIC_DM_EXTINT: + /* handled in I/O APIC code */ + break; + + default: + return; + } + + foreach_apic(apic_iter, deliver_bitmask, + apic_set_irq(apic_iter, vector_num, trigger_mode) ); +} + +void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, uint8_t vector_num, + uint8_t polarity, uint8_t trigger_mode) +{ + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + + apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode); + apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, polarity, + trigger_mode); +} + +void cpu_set_apic_base(CPUState *env, uint64_t val) +{ + APICState *s = env->apic_state; +#ifdef DEBUG_APIC + printf("cpu_set_apic_base: %016" PRIx64 "\n", val); +#endif + if (!s) + return; + s->apicbase = (val & 0xfffff000) | + (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE)); + /* if disabled, cannot be enabled again */ + if (!(val & MSR_IA32_APICBASE_ENABLE)) { + s->apicbase &= ~MSR_IA32_APICBASE_ENABLE; + env->cpuid_features &= ~CPUID_APIC; + s->spurious_vec &= ~APIC_SV_ENABLE; + } +} + +uint64_t cpu_get_apic_base(CPUState *env) +{ + APICState *s = env->apic_state; +#ifdef DEBUG_APIC + printf("cpu_get_apic_base: %016" PRIx64 "\n", + s ? (uint64_t)s->apicbase: 0); +#endif + return s ? s->apicbase : 0; +} + +void cpu_set_apic_tpr(CPUX86State *env, uint8_t val) +{ + APICState *s = env->apic_state; + if (!s) + return; + s->tpr = (val & 0x0f) << 4; + apic_update_irq(s); +} + +uint8_t cpu_get_apic_tpr(CPUX86State *env) +{ + APICState *s = env->apic_state; + return s ? s->tpr >> 4 : 0; +} + +/* return -1 if no bit is set */ +static int get_highest_priority_int(uint32_t *tab) +{ + int i; + for(i = 7; i >= 0; i--) { + if (tab[i] != 0) { + return i * 32 + fls_bit(tab[i]); + } + } + return -1; +} + +static int apic_get_ppr(APICState *s) +{ + int tpr, isrv, ppr; + + tpr = (s->tpr >> 4); + isrv = get_highest_priority_int(s->isr); + if (isrv < 0) + isrv = 0; + isrv >>= 4; + if (tpr >= isrv) + ppr = s->tpr; + else + ppr = isrv << 4; + return ppr; +} + +static int apic_get_arb_pri(APICState *s) +{ + /* XXX: arbitration */ + return 0; +} + +/* signal the CPU if an irq is pending */ +static void apic_update_irq(APICState *s) +{ + int irrv, ppr; + if (!(s->spurious_vec & APIC_SV_ENABLE)) + return; + irrv = get_highest_priority_int(s->irr); + if (irrv < 0) + return; + ppr = apic_get_ppr(s); + if (ppr && (irrv & 0xf0) <= (ppr & 0xf0)) + return; + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_HARD); +} + +void apic_reset_irq_delivered(void) +{ + apic_irq_delivered = 0; +} + +int apic_get_irq_delivered(void) +{ + return apic_irq_delivered; +} + +static void apic_set_irq(APICState *s, int vector_num, int trigger_mode) +{ + apic_irq_delivered += !get_bit(s->irr, vector_num); + + set_bit(s->irr, vector_num); + if (trigger_mode) + set_bit(s->tmr, vector_num); + else + reset_bit(s->tmr, vector_num); + apic_update_irq(s); +} + +static void apic_eoi(APICState *s) +{ + int isrv; + isrv = get_highest_priority_int(s->isr); + if (isrv < 0) + return; + reset_bit(s->isr, isrv); + /* XXX: send the EOI packet to the APIC bus to allow the I/O APIC to + set the remote IRR bit for level triggered interrupts. */ + apic_update_irq(s); +} + +static int apic_find_dest(uint8_t dest) +{ + APICState *apic = local_apics[dest]; + int i; + + if (apic && apic->id == dest) + return dest; /* shortcut in case apic->id == apic->idx */ + + for (i = 0; i < MAX_APICS; i++) { + apic = local_apics[i]; + if (apic && apic->id == dest) + return i; + } + + return -1; +} + +static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask, + uint8_t dest, uint8_t dest_mode) +{ + APICState *apic_iter; + int i; + + if (dest_mode == 0) { + if (dest == 0xff) { + memset(deliver_bitmask, 0xff, MAX_APIC_WORDS * sizeof(uint32_t)); + } else { + int idx = apic_find_dest(dest); + memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t)); + if (idx >= 0) + set_bit(deliver_bitmask, idx); + } + } else { + /* XXX: cluster mode */ + memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t)); + for(i = 0; i < MAX_APICS; i++) { + apic_iter = local_apics[i]; + if (apic_iter) { + if (apic_iter->dest_mode == 0xf) { + if (dest & apic_iter->log_dest) + set_bit(deliver_bitmask, i); + } else if (apic_iter->dest_mode == 0x0) { + if ((dest & 0xf0) == (apic_iter->log_dest & 0xf0) && + (dest & apic_iter->log_dest & 0x0f)) { + set_bit(deliver_bitmask, i); + } + } + } + } + } +} + + +void apic_init_reset(CPUState *env) +{ + APICState *s = env->apic_state; + int i; + + if (!s) + return; + + s->tpr = 0; + s->spurious_vec = 0xff; + s->log_dest = 0; + s->dest_mode = 0xf; + memset(s->isr, 0, sizeof(s->isr)); + memset(s->tmr, 0, sizeof(s->tmr)); + memset(s->irr, 0, sizeof(s->irr)); + for(i = 0; i < APIC_LVT_NB; i++) + s->lvt[i] = 1 << 16; /* mask LVT */ + s->esr = 0; + memset(s->icr, 0, sizeof(s->icr)); + s->divide_conf = 0; + s->count_shift = 0; + s->initial_count = 0; + s->initial_count_load_time = 0; + s->next_time = 0; + s->wait_for_sipi = 1; + + env->halted = !(s->apicbase & MSR_IA32_APICBASE_BSP); +} + +static void apic_startup(APICState *s, int vector_num) +{ + s->sipi_vector = vector_num; + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_SIPI); +} + +void apic_sipi(CPUState *env) +{ + APICState *s = env->apic_state; + + cpu_reset_interrupt(env, CPU_INTERRUPT_SIPI); + + if (!s->wait_for_sipi) + return; + + env->eip = 0; + cpu_x86_load_seg_cache(env, R_CS, s->sipi_vector << 8, s->sipi_vector << 12, + 0xffff, 0); + env->halted = 0; + s->wait_for_sipi = 0; +} + +static void apic_deliver(APICState *s, uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, uint8_t vector_num, + uint8_t polarity, uint8_t trigger_mode) +{ + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + int dest_shorthand = (s->icr[0] >> 18) & 3; + APICState *apic_iter; + + switch (dest_shorthand) { + case 0: + apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode); + break; + case 1: + memset(deliver_bitmask, 0x00, sizeof(deliver_bitmask)); + set_bit(deliver_bitmask, s->idx); + break; + case 2: + memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask)); + break; + case 3: + memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask)); + reset_bit(deliver_bitmask, s->idx); + break; + } + + switch (delivery_mode) { + case APIC_DM_INIT: + { + int trig_mode = (s->icr[0] >> 15) & 1; + int level = (s->icr[0] >> 14) & 1; + if (level == 0 && trig_mode == 1) { + foreach_apic(apic_iter, deliver_bitmask, + apic_iter->arb_id = apic_iter->id ); + return; + } + } + break; + + case APIC_DM_SIPI: + foreach_apic(apic_iter, deliver_bitmask, + apic_startup(apic_iter, vector_num) ); + return; + } + + apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, polarity, + trigger_mode); +} + +int apic_get_interrupt(CPUState *env) +{ + APICState *s = env->apic_state; + int intno; + + /* if the APIC is installed or enabled, we let the 8259 handle the + IRQs */ + if (!s) + return -1; + if (!(s->spurious_vec & APIC_SV_ENABLE)) + return -1; + + /* XXX: spurious IRQ handling */ + intno = get_highest_priority_int(s->irr); + if (intno < 0) + return -1; + if (s->tpr && intno <= s->tpr) + return s->spurious_vec & 0xff; + reset_bit(s->irr, intno); + set_bit(s->isr, intno); + apic_update_irq(s); + return intno; +} + +int apic_accept_pic_intr(CPUState *env) +{ + APICState *s = env->apic_state; + uint32_t lvt0; + + if (!s) + return -1; + + lvt0 = s->lvt[APIC_LVT_LINT0]; + + if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) == 0 || + (lvt0 & APIC_LVT_MASKED) == 0) + return 1; + + return 0; +} + +static uint32_t apic_get_current_count(APICState *s) +{ + int64_t d; + uint32_t val; + d = (qemu_get_clock(vm_clock) - s->initial_count_load_time) >> + s->count_shift; + if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) { + /* periodic */ + val = s->initial_count - (d % ((uint64_t)s->initial_count + 1)); + } else { + if (d >= s->initial_count) + val = 0; + else + val = s->initial_count - d; + } + return val; +} + +static void apic_timer_update(APICState *s, int64_t current_time) +{ + int64_t next_time, d; + + if (!(s->lvt[APIC_LVT_TIMER] & APIC_LVT_MASKED)) { + d = (current_time - s->initial_count_load_time) >> + s->count_shift; + if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) { + if (!s->initial_count) + goto no_timer; + d = ((d / ((uint64_t)s->initial_count + 1)) + 1) * ((uint64_t)s->initial_count + 1); + } else { + if (d >= s->initial_count) + goto no_timer; + d = (uint64_t)s->initial_count + 1; + } + next_time = s->initial_count_load_time + (d << s->count_shift); + qemu_mod_timer(s->timer, next_time); + s->next_time = next_time; + } else { + no_timer: + qemu_del_timer(s->timer); + } +} + +static void apic_timer(void *opaque) +{ + APICState *s = opaque; + + apic_local_deliver(s->cpu_env, APIC_LVT_TIMER); + apic_timer_update(s, s->next_time); +} + +static uint32_t apic_mem_readb(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static uint32_t apic_mem_readw(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static void apic_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +} + +static void apic_mem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +} + +static uint32_t apic_mem_readl(void *opaque, target_phys_addr_t addr) +{ + CPUState *env; + APICState *s; + uint32_t val; + int index; + + env = cpu_single_env; + if (!env) + return 0; + s = env->apic_state; + + index = (addr >> 4) & 0xff; + switch(index) { + case 0x02: /* id */ + val = s->id << 24; + break; + case 0x03: /* version */ + val = 0x11 | ((APIC_LVT_NB - 1) << 16); /* version 0x11 */ + break; + case 0x08: + val = s->tpr; + break; + case 0x09: + val = apic_get_arb_pri(s); + break; + case 0x0a: + /* ppr */ + val = apic_get_ppr(s); + break; + case 0x0b: + val = 0; + break; + case 0x0d: + val = s->log_dest << 24; + break; + case 0x0e: + val = s->dest_mode << 28; + break; + case 0x0f: + val = s->spurious_vec; + break; + case 0x10 ... 0x17: + val = s->isr[index & 7]; + break; + case 0x18 ... 0x1f: + val = s->tmr[index & 7]; + break; + case 0x20 ... 0x27: + val = s->irr[index & 7]; + break; + case 0x28: + val = s->esr; + break; + case 0x30: + case 0x31: + val = s->icr[index & 1]; + break; + case 0x32 ... 0x37: + val = s->lvt[index - 0x32]; + break; + case 0x38: + val = s->initial_count; + break; + case 0x39: + val = apic_get_current_count(s); + break; + case 0x3e: + val = s->divide_conf; + break; + default: + s->esr |= ESR_ILLEGAL_ADDRESS; + val = 0; + break; + } +#ifdef DEBUG_APIC + printf("APIC read: %08x = %08x\n", (uint32_t)addr, val); +#endif + return val; +} + +static void apic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + CPUState *env; + APICState *s; + int index; + + env = cpu_single_env; + if (!env) + return; + s = env->apic_state; + +#ifdef DEBUG_APIC + printf("APIC write: %08x = %08x\n", (uint32_t)addr, val); +#endif + + index = (addr >> 4) & 0xff; + switch(index) { + case 0x02: + s->id = (val >> 24); + break; + case 0x03: + break; + case 0x08: + s->tpr = val; + apic_update_irq(s); + break; + case 0x09: + case 0x0a: + break; + case 0x0b: /* EOI */ + apic_eoi(s); + break; + case 0x0d: + s->log_dest = val >> 24; + break; + case 0x0e: + s->dest_mode = val >> 28; + break; + case 0x0f: + s->spurious_vec = val & 0x1ff; + apic_update_irq(s); + break; + case 0x10 ... 0x17: + case 0x18 ... 0x1f: + case 0x20 ... 0x27: + case 0x28: + break; + case 0x30: + s->icr[0] = val; + apic_deliver(s, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1, + (s->icr[0] >> 8) & 7, (s->icr[0] & 0xff), + (s->icr[0] >> 14) & 1, (s->icr[0] >> 15) & 1); + break; + case 0x31: + s->icr[1] = val; + break; + case 0x32 ... 0x37: + { + int n = index - 0x32; + s->lvt[n] = val; + if (n == APIC_LVT_TIMER) + apic_timer_update(s, qemu_get_clock(vm_clock)); + } + break; + case 0x38: + s->initial_count = val; + s->initial_count_load_time = qemu_get_clock(vm_clock); + apic_timer_update(s, s->initial_count_load_time); + break; + case 0x39: + break; + case 0x3e: + { + int v; + s->divide_conf = val & 0xb; + v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4); + s->count_shift = (v + 1) & 7; + } + break; + default: + s->esr |= ESR_ILLEGAL_ADDRESS; + break; + } +} + +static void apic_save(QEMUFile *f, void *opaque) +{ + APICState *s = opaque; + int i; + + qemu_put_be32s(f, &s->apicbase); + qemu_put_8s(f, &s->id); + qemu_put_8s(f, &s->arb_id); + qemu_put_8s(f, &s->tpr); + qemu_put_be32s(f, &s->spurious_vec); + qemu_put_8s(f, &s->log_dest); + qemu_put_8s(f, &s->dest_mode); + for (i = 0; i < 8; i++) { + qemu_put_be32s(f, &s->isr[i]); + qemu_put_be32s(f, &s->tmr[i]); + qemu_put_be32s(f, &s->irr[i]); + } + for (i = 0; i < APIC_LVT_NB; i++) { + qemu_put_be32s(f, &s->lvt[i]); + } + qemu_put_be32s(f, &s->esr); + qemu_put_be32s(f, &s->icr[0]); + qemu_put_be32s(f, &s->icr[1]); + qemu_put_be32s(f, &s->divide_conf); + qemu_put_be32(f, s->count_shift); + qemu_put_be32s(f, &s->initial_count); + qemu_put_be64(f, s->initial_count_load_time); + qemu_put_be64(f, s->next_time); + + qemu_put_timer(f, s->timer); +} + +static int apic_load(QEMUFile *f, void *opaque, int version_id) +{ + APICState *s = opaque; + int i; + + if (version_id > 2) + return -EINVAL; + + /* XXX: what if the base changes? (registered memory regions) */ + qemu_get_be32s(f, &s->apicbase); + qemu_get_8s(f, &s->id); + qemu_get_8s(f, &s->arb_id); + qemu_get_8s(f, &s->tpr); + qemu_get_be32s(f, &s->spurious_vec); + qemu_get_8s(f, &s->log_dest); + qemu_get_8s(f, &s->dest_mode); + for (i = 0; i < 8; i++) { + qemu_get_be32s(f, &s->isr[i]); + qemu_get_be32s(f, &s->tmr[i]); + qemu_get_be32s(f, &s->irr[i]); + } + for (i = 0; i < APIC_LVT_NB; i++) { + qemu_get_be32s(f, &s->lvt[i]); + } + qemu_get_be32s(f, &s->esr); + qemu_get_be32s(f, &s->icr[0]); + qemu_get_be32s(f, &s->icr[1]); + qemu_get_be32s(f, &s->divide_conf); + s->count_shift=qemu_get_be32(f); + qemu_get_be32s(f, &s->initial_count); + s->initial_count_load_time=qemu_get_be64(f); + s->next_time=qemu_get_be64(f); + + if (version_id >= 2) + qemu_get_timer(f, s->timer); + return 0; +} + +static void apic_reset(void *opaque) +{ + APICState *s = opaque; + int bsp = cpu_is_bsp(s->cpu_env); + + s->apicbase = 0xfee00000 | + (bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE; + + cpu_reset(s->cpu_env); + apic_init_reset(s->cpu_env); + + if (bsp) { + /* + * LINT0 delivery mode on CPU #0 is set to ExtInt at initialization + * time typically by BIOS, so PIC interrupt can be delivered to the + * processor when local APIC is enabled. + */ + s->lvt[APIC_LVT_LINT0] = 0x700; + } +} + +static CPUReadMemoryFunc *apic_mem_read[3] = { + apic_mem_readb, + apic_mem_readw, + apic_mem_readl, +}; + +static CPUWriteMemoryFunc *apic_mem_write[3] = { + apic_mem_writeb, + apic_mem_writew, + apic_mem_writel, +}; + +int apic_init(CPUState *env) +{ + APICState *s; + + if (last_apic_idx >= MAX_APICS) + return -1; + s = qemu_mallocz(sizeof(APICState)); + env->apic_state = s; + s->idx = last_apic_idx++; + s->id = env->cpuid_apic_id; + s->cpu_env = env; + + apic_reset(s); + + /* XXX: mapping more APICs at the same memory location */ + if (apic_io_memory == 0) { + /* NOTE: the APIC is directly connected to the CPU - it is not + on the global memory bus. */ + apic_io_memory = cpu_register_io_memory(apic_mem_read, + apic_mem_write, NULL); + cpu_register_physical_memory(s->apicbase & ~0xfff, 0x1000, + apic_io_memory); + } + s->timer = qemu_new_timer(vm_clock, apic_timer, s); + + register_savevm("apic", s->idx, 2, apic_save, apic_load, s); + qemu_register_reset(apic_reset, 0, s); + + local_apics[s->idx] = s; + return 0; +} + diff --git a/hw/fdc.h b/hw/fdc.h new file mode 100644 index 0000000..7b6a9de --- /dev/null +++ b/hw/fdc.h @@ -0,0 +1,11 @@ +/* fdc.c */ +#define MAX_FD 2 + +typedef struct fdctrl_t fdctrl_t; + +fdctrl_t *fdctrl_init (qemu_irq irq, int dma_chann, int mem_mapped, + target_phys_addr_t io_base, + BlockDriverState **fds); +fdctrl_t *sun4m_fdctrl_init (qemu_irq irq, target_phys_addr_t io_base, + BlockDriverState **fds, qemu_irq *fdc_tc); +int fdctrl_get_drive_type(fdctrl_t *fdctrl, int drive_num); diff --git a/hw/fw_cfg.c b/hw/fw_cfg.c new file mode 100644 index 0000000..276c396 --- /dev/null +++ b/hw/fw_cfg.c @@ -0,0 +1,288 @@ +/* + * QEMU Firmware configuration device emulation + * + * Copyright (c) 2008 Gleb Natapov + * + * 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 "hw.h" +#include "sysemu.h" +#include "isa.h" +#include "fw_cfg.h" + +/* debug firmware config */ +//#define DEBUG_FW_CFG + +#ifdef DEBUG_FW_CFG +#define FW_CFG_DPRINTF(fmt, ...) \ + do { printf("FW_CFG: " fmt , ## __VA_ARGS__); } while (0) +#else +#define FW_CFG_DPRINTF(fmt, ...) +#endif + +#define FW_CFG_SIZE 2 + +typedef struct _FWCfgEntry { + uint16_t len; + uint8_t *data; + void *callback_opaque; + FWCfgCallback callback; +} FWCfgEntry; + +typedef struct _FWCfgState { + FWCfgEntry entries[2][FW_CFG_MAX_ENTRY]; + uint16_t cur_entry; + uint16_t cur_offset; +} FWCfgState; + +static void fw_cfg_write(FWCfgState *s, uint8_t value) +{ + int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); + FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; + + FW_CFG_DPRINTF("write %d\n", value); + + if (s->cur_entry & FW_CFG_WRITE_CHANNEL && s->cur_offset < e->len) { + e->data[s->cur_offset++] = value; + if (s->cur_offset == e->len) { + e->callback(e->callback_opaque, e->data); + s->cur_offset = 0; + } + } +} + +static int fw_cfg_select(FWCfgState *s, uint16_t key) +{ + int ret; + + s->cur_offset = 0; + if ((key & FW_CFG_ENTRY_MASK) >= FW_CFG_MAX_ENTRY) { + s->cur_entry = FW_CFG_INVALID; + ret = 0; + } else { + s->cur_entry = key; + ret = 1; + } + + FW_CFG_DPRINTF("select key %d (%sfound)\n", key, ret ? "" : "not "); + + return ret; +} + +static uint8_t fw_cfg_read(FWCfgState *s) +{ + int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); + FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; + uint8_t ret; + + if (s->cur_entry == FW_CFG_INVALID || !e->data || s->cur_offset >= e->len) + ret = 0; + else + ret = e->data[s->cur_offset++]; + + FW_CFG_DPRINTF("read %d\n", ret); + + return ret; +} + +static uint32_t fw_cfg_io_readb(void *opaque, uint32_t addr) +{ + return fw_cfg_read(opaque); +} + +static void fw_cfg_io_writeb(void *opaque, uint32_t addr, uint32_t value) +{ + fw_cfg_write(opaque, (uint8_t)value); +} + +static void fw_cfg_io_writew(void *opaque, uint32_t addr, uint32_t value) +{ + fw_cfg_select(opaque, (uint16_t)value); +} + +static uint32_t fw_cfg_mem_readb(void *opaque, target_phys_addr_t addr) +{ + return fw_cfg_read(opaque); +} + +static void fw_cfg_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + fw_cfg_write(opaque, (uint8_t)value); +} + +static void fw_cfg_mem_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + fw_cfg_select(opaque, (uint16_t)value); +} + +static CPUReadMemoryFunc *fw_cfg_ctl_mem_read[3] = { + NULL, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc *fw_cfg_ctl_mem_write[3] = { + NULL, + fw_cfg_mem_writew, + NULL, +}; + +static CPUReadMemoryFunc *fw_cfg_data_mem_read[3] = { + fw_cfg_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc *fw_cfg_data_mem_write[3] = { + fw_cfg_mem_writeb, + NULL, + NULL, +}; + +static void fw_cfg_reset(void *opaque) +{ + FWCfgState *s = opaque; + + fw_cfg_select(s, 0); +} + +static void fw_cfg_save(QEMUFile *f, void *opaque) +{ + FWCfgState *s = opaque; + + qemu_put_be16s(f, &s->cur_entry); + qemu_put_be16s(f, &s->cur_offset); +} + +static int fw_cfg_load(QEMUFile *f, void *opaque, int version_id) +{ + FWCfgState *s = opaque; + + if (version_id > 1) + return -EINVAL; + + qemu_get_be16s(f, &s->cur_entry); + qemu_get_be16s(f, &s->cur_offset); + + return 0; +} + +int fw_cfg_add_bytes(void *opaque, uint16_t key, uint8_t *data, uint16_t len) +{ + FWCfgState *s = opaque; + int arch = !!(key & FW_CFG_ARCH_LOCAL); + + key &= FW_CFG_ENTRY_MASK; + + if (key >= FW_CFG_MAX_ENTRY) + return 0; + + s->entries[arch][key].data = data; + s->entries[arch][key].len = len; + + return 1; +} + +int fw_cfg_add_i16(void *opaque, uint16_t key, uint16_t value) +{ + uint16_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le16(value); + return fw_cfg_add_bytes(opaque, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_i32(void *opaque, uint16_t key, uint32_t value) +{ + uint32_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le32(value); + return fw_cfg_add_bytes(opaque, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_i64(void *opaque, uint16_t key, uint64_t value) +{ + uint64_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le64(value); + return fw_cfg_add_bytes(opaque, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_callback(void *opaque, uint16_t key, FWCfgCallback callback, + void *callback_opaque, uint8_t *data, size_t len) +{ + FWCfgState *s = opaque; + int arch = !!(key & FW_CFG_ARCH_LOCAL); + + if (!(key & FW_CFG_WRITE_CHANNEL)) + return 0; + + key &= FW_CFG_ENTRY_MASK; + + if (key >= FW_CFG_MAX_ENTRY || len > 65535) + return 0; + + s->entries[arch][key].data = data; + s->entries[arch][key].len = len; + s->entries[arch][key].callback_opaque = callback_opaque; + s->entries[arch][key].callback = callback; + + return 1; +} + +void *fw_cfg_init(uint32_t ctl_port, uint32_t data_port, + target_phys_addr_t ctl_addr, target_phys_addr_t data_addr) +{ + FWCfgState *s; + int io_ctl_memory, io_data_memory; + + s = qemu_mallocz(sizeof(FWCfgState)); + + if (ctl_port) { + register_ioport_write(ctl_port, 2, 2, fw_cfg_io_writew, s); + } + if (data_port) { + register_ioport_read(data_port, 1, 1, fw_cfg_io_readb, s); + register_ioport_write(data_port, 1, 1, fw_cfg_io_writeb, s); + } + if (ctl_addr) { + io_ctl_memory = cpu_register_io_memory(fw_cfg_ctl_mem_read, + fw_cfg_ctl_mem_write, s); + cpu_register_physical_memory(ctl_addr, FW_CFG_SIZE, io_ctl_memory); + } + if (data_addr) { + io_data_memory = cpu_register_io_memory(fw_cfg_data_mem_read, + fw_cfg_data_mem_write, s); + cpu_register_physical_memory(data_addr, FW_CFG_SIZE, io_data_memory); + } + fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (uint8_t *)"QEMU", 4); + fw_cfg_add_bytes(s, FW_CFG_UUID, qemu_uuid, 16); + fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, (uint16_t)(display_type == DT_NOGRAPHIC)); + fw_cfg_add_i16(s, FW_CFG_NB_CPUS, (uint16_t)smp_cpus); + + register_savevm("fw_cfg", -1, 1, fw_cfg_save, fw_cfg_load, s); + qemu_register_reset(fw_cfg_reset, 0, s); + fw_cfg_reset(s); + + return s; +} diff --git a/hw/fw_cfg.h b/hw/fw_cfg.h new file mode 100644 index 0000000..f616ed2 --- /dev/null +++ b/hw/fw_cfg.h @@ -0,0 +1,40 @@ +#ifndef FW_CFG_H +#define FW_CFG_H + +#define FW_CFG_SIGNATURE 0x00 +#define FW_CFG_ID 0x01 +#define FW_CFG_UUID 0x02 +#define FW_CFG_RAM_SIZE 0x03 +#define FW_CFG_NOGRAPHIC 0x04 +#define FW_CFG_NB_CPUS 0x05 +#define FW_CFG_MACHINE_ID 0x06 +#define FW_CFG_KERNEL_ADDR 0x07 +#define FW_CFG_KERNEL_SIZE 0x08 +#define FW_CFG_KERNEL_CMDLINE 0x09 +#define FW_CFG_INITRD_ADDR 0x0a +#define FW_CFG_INITRD_SIZE 0x0b +#define FW_CFG_BOOT_DEVICE 0x0c +#define FW_CFG_NUMA 0x0d +#define FW_CFG_MAX_ENTRY 0x10 + +#define FW_CFG_WRITE_CHANNEL 0x4000 +#define FW_CFG_ARCH_LOCAL 0x8000 +#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) + +#define FW_CFG_INVALID 0xffff + +#ifndef NO_QEMU_PROTOS +typedef void (*FWCfgCallback)(void *opaque, uint8_t *data); + +int fw_cfg_add_bytes(void *opaque, uint16_t key, uint8_t *data, uint16_t len); +int fw_cfg_add_i16(void *opaque, uint16_t key, uint16_t value); +int fw_cfg_add_i32(void *opaque, uint16_t key, uint32_t value); +int fw_cfg_add_i64(void *opaque, uint16_t key, uint64_t value); +int fw_cfg_add_callback(void *opaque, uint16_t key, FWCfgCallback callback, + void *callback_opaque, uint8_t *data, size_t len); +void *fw_cfg_init(uint32_t ctl_port, uint32_t data_port, + target_phys_addr_t crl_addr, target_phys_addr_t data_addr); + +#endif /* NO_QEMU_PROTOS */ + +#endif diff --git a/hw/i8254.c b/hw/i8254.c new file mode 100644 index 0000000..c202c9c --- /dev/null +++ b/hw/i8254.c @@ -0,0 +1,507 @@ +/* + * QEMU 8253/8254 interval timer emulation + * + * Copyright (c) 2003-2004 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 "hw.h" +#include "pc.h" +#include "isa.h" +#include "qemu-timer.h" + +//#define DEBUG_PIT + +#define RW_STATE_LSB 1 +#define RW_STATE_MSB 2 +#define RW_STATE_WORD0 3 +#define RW_STATE_WORD1 4 + +typedef struct PITChannelState { + int count; /* can be 65536 */ + uint16_t latched_count; + uint8_t count_latched; + uint8_t status_latched; + uint8_t status; + uint8_t read_state; + uint8_t write_state; + uint8_t write_latch; + uint8_t rw_mode; + uint8_t mode; + uint8_t bcd; /* not supported */ + uint8_t gate; /* timer start */ + int64_t count_load_time; + /* irq handling */ + int64_t next_transition_time; + QEMUTimer *irq_timer; + qemu_irq irq; +} PITChannelState; + +struct PITState { + PITChannelState channels[3]; +}; + +static PITState pit_state; + +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time); + +static int pit_get_count(PITChannelState *s) +{ + uint64_t d; + int counter; + + d = muldiv64(qemu_get_clock(vm_clock) - s->count_load_time, PIT_FREQ, get_ticks_per_sec()); + switch(s->mode) { + case 0: + case 1: + case 4: + case 5: + counter = (s->count - d) & 0xffff; + break; + case 3: + /* XXX: may be incorrect for odd counts */ + counter = s->count - ((2 * d) % s->count); + break; + default: + counter = s->count - (d % s->count); + break; + } + return counter; +} + +/* get pit output bit */ +static int pit_get_out1(PITChannelState *s, int64_t current_time) +{ + uint64_t d; + int out; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, get_ticks_per_sec()); + switch(s->mode) { + default: + case 0: + out = (d >= s->count); + break; + case 1: + out = (d < s->count); + break; + case 2: + if ((d % s->count) == 0 && d != 0) + out = 1; + else + out = 0; + break; + case 3: + out = (d % s->count) < ((s->count + 1) >> 1); + break; + case 4: + case 5: + out = (d == s->count); + break; + } + return out; +} + +int pit_get_out(PITState *pit, int channel, int64_t current_time) +{ + PITChannelState *s = &pit->channels[channel]; + return pit_get_out1(s, current_time); +} + +/* return -1 if no transition will occur. */ +static int64_t pit_get_next_transition_time(PITChannelState *s, + int64_t current_time) +{ + uint64_t d, next_time, base; + int period2; + + d = muldiv64(current_time - s->count_load_time, PIT_FREQ, get_ticks_per_sec()); + switch(s->mode) { + default: + case 0: + case 1: + if (d < s->count) + next_time = s->count; + else + return -1; + break; + case 2: + base = (d / s->count) * s->count; + if ((d - base) == 0 && d != 0) + next_time = base + s->count; + else + next_time = base + s->count + 1; + break; + case 3: + base = (d / s->count) * s->count; + period2 = ((s->count + 1) >> 1); + if ((d - base) < period2) + next_time = base + period2; + else + next_time = base + s->count; + break; + case 4: + case 5: + if (d < s->count) + next_time = s->count; + else if (d == s->count) + next_time = s->count + 1; + else + return -1; + break; + } + /* convert to timer units */ + next_time = s->count_load_time + muldiv64(next_time, get_ticks_per_sec(), PIT_FREQ); + /* fix potential rounding problems */ + /* XXX: better solution: use a clock at PIT_FREQ Hz */ + if (next_time <= current_time) + next_time = current_time + 1; + return next_time; +} + +/* val must be 0 or 1 */ +void pit_set_gate(PITState *pit, int channel, int val) +{ + PITChannelState *s = &pit->channels[channel]; + + switch(s->mode) { + default: + case 0: + case 4: + /* XXX: just disable/enable counting */ + break; + case 1: + case 5: + if (s->gate < val) { + /* restart counting on rising edge */ + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); + } + break; + case 2: + case 3: + if (s->gate < val) { + /* restart counting on rising edge */ + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); + } + /* XXX: disable/enable counting */ + break; + } + s->gate = val; +} + +int pit_get_gate(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->gate; +} + +int pit_get_initial_count(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->count; +} + +int pit_get_mode(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->mode; +} + +static inline void pit_load_count(PITChannelState *s, int val) +{ + if (val == 0) + val = 0x10000; + s->count_load_time = qemu_get_clock(vm_clock); + s->count = val; + pit_irq_timer_update(s, s->count_load_time); +} + +/* if already latched, do not latch again */ +static void pit_latch_count(PITChannelState *s) +{ + if (!s->count_latched) { + s->latched_count = pit_get_count(s); + s->count_latched = s->rw_mode; + } +} + +static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + PITState *pit = opaque; + int channel, access; + PITChannelState *s; + + addr &= 3; + if (addr == 3) { + channel = val >> 6; + if (channel == 3) { + /* read back command */ + for(channel = 0; channel < 3; channel++) { + s = &pit->channels[channel]; + if (val & (2 << channel)) { + if (!(val & 0x20)) { + pit_latch_count(s); + } + if (!(val & 0x10) && !s->status_latched) { + /* status latch */ + /* XXX: add BCD and null count */ + s->status = (pit_get_out1(s, qemu_get_clock(vm_clock)) << 7) | + (s->rw_mode << 4) | + (s->mode << 1) | + s->bcd; + s->status_latched = 1; + } + } + } + } else { + s = &pit->channels[channel]; + access = (val >> 4) & 3; + if (access == 0) { + pit_latch_count(s); + } else { + s->rw_mode = access; + s->read_state = access; + s->write_state = access; + + s->mode = (val >> 1) & 7; + s->bcd = val & 1; + /* XXX: update irq timer ? */ + } + } + } else { + s = &pit->channels[addr]; + switch(s->write_state) { + default: + 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: + s->write_latch = val; + s->write_state = RW_STATE_WORD1; + break; + case RW_STATE_WORD1: + pit_load_count(s, s->write_latch | (val << 8)); + s->write_state = RW_STATE_WORD0; + break; + } + } +} + +static uint32_t pit_ioport_read(void *opaque, uint32_t addr) +{ + PITState *pit = opaque; + int ret, count; + PITChannelState *s; + + addr &= 3; + s = &pit->channels[addr]; + if (s->status_latched) { + s->status_latched = 0; + ret = s->status; + } else if (s->count_latched) { + switch(s->count_latched) { + default: + case RW_STATE_LSB: + ret = s->latched_count & 0xff; + s->count_latched = 0; + break; + case RW_STATE_MSB: + ret = s->latched_count >> 8; + s->count_latched = 0; + break; + case RW_STATE_WORD0: + ret = s->latched_count & 0xff; + s->count_latched = RW_STATE_MSB; + break; + } + } else { + switch(s->read_state) { + default: + case RW_STATE_LSB: + count = pit_get_count(s); + ret = count & 0xff; + break; + case RW_STATE_MSB: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + break; + case RW_STATE_WORD0: + count = pit_get_count(s); + ret = count & 0xff; + s->read_state = RW_STATE_WORD1; + break; + case RW_STATE_WORD1: + count = pit_get_count(s); + ret = (count >> 8) & 0xff; + s->read_state = RW_STATE_WORD0; + break; + } + } + return ret; +} + +static void pit_irq_timer_update(PITChannelState *s, int64_t current_time) +{ + int64_t expire_time; + int irq_level; + + if (!s->irq_timer) + return; + expire_time = pit_get_next_transition_time(s, current_time); + irq_level = pit_get_out1(s, current_time); + qemu_set_irq(s->irq, irq_level); +#ifdef DEBUG_PIT + printf("irq_level=%d next_delay=%f\n", + irq_level, + (double)(expire_time - current_time) / get_ticks_per_sec()); +#endif + s->next_transition_time = expire_time; + if (expire_time != -1) + qemu_mod_timer(s->irq_timer, expire_time); + else + qemu_del_timer(s->irq_timer); +} + +static void pit_irq_timer(void *opaque) +{ + PITChannelState *s = opaque; + + pit_irq_timer_update(s, s->next_transition_time); +} + +static void pit_save(QEMUFile *f, void *opaque) +{ + PITState *pit = opaque; + PITChannelState *s; + int i; + + for(i = 0; i < 3; i++) { + s = &pit->channels[i]; + qemu_put_be32(f, s->count); + qemu_put_be16s(f, &s->latched_count); + qemu_put_8s(f, &s->count_latched); + qemu_put_8s(f, &s->status_latched); + qemu_put_8s(f, &s->status); + qemu_put_8s(f, &s->read_state); + qemu_put_8s(f, &s->write_state); + qemu_put_8s(f, &s->write_latch); + qemu_put_8s(f, &s->rw_mode); + qemu_put_8s(f, &s->mode); + qemu_put_8s(f, &s->bcd); + qemu_put_8s(f, &s->gate); + qemu_put_be64(f, s->count_load_time); + if (s->irq_timer) { + qemu_put_be64(f, s->next_transition_time); + qemu_put_timer(f, s->irq_timer); + } + } +} + +static int pit_load(QEMUFile *f, void *opaque, int version_id) +{ + PITState *pit = opaque; + PITChannelState *s; + int i; + + if (version_id != 1) + return -EINVAL; + + for(i = 0; i < 3; i++) { + s = &pit->channels[i]; + s->count=qemu_get_be32(f); + qemu_get_be16s(f, &s->latched_count); + qemu_get_8s(f, &s->count_latched); + qemu_get_8s(f, &s->status_latched); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->read_state); + qemu_get_8s(f, &s->write_state); + qemu_get_8s(f, &s->write_latch); + qemu_get_8s(f, &s->rw_mode); + qemu_get_8s(f, &s->mode); + qemu_get_8s(f, &s->bcd); + qemu_get_8s(f, &s->gate); + s->count_load_time=qemu_get_be64(f); + if (s->irq_timer) { + s->next_transition_time=qemu_get_be64(f); + qemu_get_timer(f, s->irq_timer); + } + } + return 0; +} + +static void pit_reset(void *opaque) +{ + PITState *pit = opaque; + PITChannelState *s; + int i; + + for(i = 0;i < 3; i++) { + s = &pit->channels[i]; + s->mode = 3; + s->gate = (i != 2); + pit_load_count(s, 0); + } +} + +/* When HPET is operating in legacy mode, i8254 timer0 is disabled */ +void hpet_pit_disable(void) { + PITChannelState *s; + s = &pit_state.channels[0]; + if (s->irq_timer) + qemu_del_timer(s->irq_timer); +} + +/* When HPET is reset or leaving legacy mode, it must reenable i8254 + * timer 0 + */ + +void hpet_pit_enable(void) +{ + PITState *pit = &pit_state; + PITChannelState *s; + s = &pit->channels[0]; + s->mode = 3; + s->gate = 1; + pit_load_count(s, 0); +} + +PITState *pit_init(int base, qemu_irq irq) +{ + PITState *pit = &pit_state; + PITChannelState *s; + + s = &pit->channels[0]; + /* the timer 0 is connected to an IRQ */ + s->irq_timer = qemu_new_timer(vm_clock, pit_irq_timer, s); + s->irq = irq; + + register_savevm("i8254", base, 1, pit_save, pit_load, pit); + + qemu_register_reset(pit_reset, 0, pit); + register_ioport_write(base, 4, 1, pit_ioport_write, pit); + register_ioport_read(base, 3, 1, pit_ioport_read, pit); + + pit_reset(pit); + + return pit; +} diff --git a/hw/i8259.c b/hw/i8259.c new file mode 100644 index 0000000..091ba7a --- /dev/null +++ b/hw/i8259.c @@ -0,0 +1,570 @@ +/* + * QEMU 8259 interrupt controller emulation + * + * Copyright (c) 2003-2004 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 "hw.h" +#include "pc.h" +#include "isa.h" +#include "monitor.h" + +/* debug PIC */ +//#define DEBUG_PIC + +//#define DEBUG_IRQ_LATENCY +//#define DEBUG_IRQ_COUNT + +typedef struct PicState { + 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; /* highest irq priority */ + uint8_t irq_base; + uint8_t read_reg_select; + uint8_t poll; + uint8_t special_mask; + uint8_t init_state; + uint8_t auto_eoi; + uint8_t rotate_on_auto_eoi; + uint8_t special_fully_nested_mode; + uint8_t init4; /* true if 4 byte init */ + uint8_t single_mode; /* true if slave pic is not initialized */ + uint8_t elcr; /* PIIX edge/trigger selection*/ + uint8_t elcr_mask; + PicState2 *pics_state; +} PicState; + +struct PicState2 { + /* 0 is master pic, 1 is slave pic */ + /* XXX: better separation between the two pics */ + PicState pics[2]; + qemu_irq parent_irq; + void *irq_request_opaque; + /* IOAPIC callback support */ + SetIRQFunc *alt_irq_func; + void *alt_irq_opaque; +}; + +#if defined(DEBUG_PIC) || defined (DEBUG_IRQ_COUNT) +static int irq_level[16]; +#endif +#ifdef DEBUG_IRQ_COUNT +static uint64_t irq_count[16]; +#endif + +/* set irq level. If an edge is detected, then the IRR is set to 1 */ +static inline 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; + } + } +} + +/* return the highest priority found in mask (highest = smallest + number). Return 8 if no irq */ +static inline int get_priority(PicState *s, int mask) +{ + int priority; + if (mask == 0) + return 8; + priority = 0; + 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 = get_priority(s, mask); + if (priority == 8) + return -1; + /* compute current priority. If special fully nested mode on the + master, the IRQ coming from the slave is not taken into account + for the priority computation. */ + mask = s->isr; + if (s->special_mask) + mask &= ~s->imr; + if (s->special_fully_nested_mode && s == &s->pics_state->pics[0]) + mask &= ~(1 << 2); + cur_priority = get_priority(s, mask); + if (priority < cur_priority) { + /* higher priority found: an irq should be generated */ + return (priority + s->priority_add) & 7; + } else { + return -1; + } +} + +/* raise irq to CPU if necessary. must be called every time the active + irq may change */ +/* XXX: should not export it, but it is needed for an APIC kludge */ +void pic_update_irq(PicState2 *s) +{ + 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 (irq >= 0) { +#if defined(DEBUG_PIC) + { + int i; + for(i = 0; i < 2; i++) { + printf("pic%d: imr=%x irr=%x padd=%d\n", + i, s->pics[i].imr, s->pics[i].irr, + s->pics[i].priority_add); + + } + } + printf("pic: cpu_interrupt\n"); +#endif + qemu_irq_raise(s->parent_irq); + } + +/* all targets should do this rather than acking the IRQ in the cpu */ +#if defined(TARGET_MIPS) || defined(TARGET_PPC) || defined(TARGET_ALPHA) + else { + qemu_irq_lower(s->parent_irq); + } +#endif +} + +#ifdef DEBUG_IRQ_LATENCY +int64_t irq_time[16]; +#endif + +static void i8259_set_irq(void *opaque, int irq, int level) +{ + PicState2 *s = opaque; + +#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_COUNT) + if (level != irq_level[irq]) { +#if defined(DEBUG_PIC) + printf("i8259_set_irq: irq=%d level=%d\n", irq, level); +#endif + irq_level[irq] = level; +#ifdef DEBUG_IRQ_COUNT + if (level == 1) + irq_count[irq]++; +#endif + } +#endif +#ifdef DEBUG_IRQ_LATENCY + if (level) { + irq_time[irq] = qemu_get_clock(vm_clock); + } +#endif + pic_set_irq1(&s->pics[irq >> 3], irq & 7, level); + /* used for IOAPIC irqs */ + if (s->alt_irq_func) + s->alt_irq_func(s->alt_irq_opaque, irq, level); + pic_update_irq(s); +} + +/* acknowledge interrupt 'irq' */ +static inline void pic_intack(PicState *s, int irq) +{ + if (s->auto_eoi) { + if (s->rotate_on_auto_eoi) + 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); +} + +int pic_read_irq(PicState2 *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; + } + pic_update_irq(s); + +#ifdef DEBUG_IRQ_LATENCY + printf("IRQ%d latency=%0.3fus\n", + irq, + (double)(qemu_get_clock(vm_clock) - irq_time[irq]) * 1000000.0 / get_ticks_per_sec); +#endif +#if defined(DEBUG_PIC) + printf("pic_interrupt: irq=%d\n", irq); +#endif + return intno; +} + +static void pic_reset(void *opaque) +{ + PicState *s = opaque; + + s->last_irr = 0; + s->irr = 0; + s->imr = 0; + s->isr = 0; + s->priority_add = 0; + s->irq_base = 0; + s->read_reg_select = 0; + s->poll = 0; + s->special_mask = 0; + s->init_state = 0; + s->auto_eoi = 0; + s->rotate_on_auto_eoi = 0; + s->special_fully_nested_mode = 0; + s->init4 = 0; + s->single_mode = 0; + /* Note: ELCR is not reset */ +} + +static void pic_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + PicState *s = opaque; + int priority, cmd, irq; + +#ifdef DEBUG_PIC + printf("pic_write: addr=0x%02x val=0x%02x\n", addr, val); +#endif + addr &= 1; + if (addr == 0) { + if (val & 0x10) { + /* init */ + pic_reset(s); + /* deassert a pending interrupt */ + qemu_irq_lower(s->pics_state->parent_irq); + s->init_state = 1; + s->init4 = val & 1; + s->single_mode = val & 2; + if (val & 0x08) + hw_error("level sensitive irq not supported"); + } else if (val & 0x08) { + if (val & 0x04) + s->poll = 1; + if (val & 0x02) + s->read_reg_select = val & 1; + if (val & 0x40) + s->special_mask = (val >> 5) & 1; + } else { + cmd = val >> 5; + switch(cmd) { + case 0: + case 4: + s->rotate_on_auto_eoi = cmd >> 2; + break; + case 1: /* end of interrupt */ + case 5: + priority = get_priority(s, s->isr); + if (priority != 8) { + irq = (priority + s->priority_add) & 7; + s->isr &= ~(1 << irq); + if (cmd == 5) + s->priority_add = (irq + 1) & 7; + pic_update_irq(s->pics_state); + } + break; + case 3: + irq = val & 7; + s->isr &= ~(1 << irq); + pic_update_irq(s->pics_state); + break; + case 6: + s->priority_add = (val + 1) & 7; + pic_update_irq(s->pics_state); + break; + case 7: + irq = val & 7; + s->isr &= ~(1 << irq); + s->priority_add = (irq + 1) & 7; + pic_update_irq(s->pics_state); + break; + default: + /* no operation */ + break; + } + } + } else { + switch(s->init_state) { + case 0: + /* normal mode */ + s->imr = val; + pic_update_irq(s->pics_state); + break; + case 1: + s->irq_base = val & 0xf8; + s->init_state = s->single_mode ? (s->init4 ? 3 : 0) : 2; + break; + case 2: + if (s->init4) { + s->init_state = 3; + } else { + s->init_state = 0; + } + break; + case 3: + s->special_fully_nested_mode = (val >> 4) & 1; + s->auto_eoi = (val >> 1) & 1; + s->init_state = 0; + break; + } + } +} + +static uint32_t pic_poll_read (PicState *s, uint32_t addr1) +{ + int ret; + + ret = pic_get_irq(s); + if (ret >= 0) { + if (addr1 >> 7) { + s->pics_state->pics[0].isr &= ~(1 << 2); + s->pics_state->pics[0].irr &= ~(1 << 2); + } + s->irr &= ~(1 << ret); + s->isr &= ~(1 << ret); + if (addr1 >> 7 || ret != 2) + pic_update_irq(s->pics_state); + } else { + ret = 0x07; + pic_update_irq(s->pics_state); + } + + return ret; +} + +static uint32_t pic_ioport_read(void *opaque, uint32_t addr1) +{ + PicState *s = opaque; + unsigned int addr; + int ret; + + addr = addr1; + addr &= 1; + if (s->poll) { + ret = pic_poll_read(s, addr1); + s->poll = 0; + } else { + if (addr == 0) { + if (s->read_reg_select) + ret = s->isr; + else + ret = s->irr; + } else { + ret = s->imr; + } + } +#ifdef DEBUG_PIC + printf("pic_read: addr=0x%02x val=0x%02x\n", addr1, ret); +#endif + return ret; +} + +/* memory mapped interrupt status */ +/* XXX: may be the same than pic_read_irq() */ +uint32_t pic_intack_read(PicState2 *s) +{ + int ret; + + ret = pic_poll_read(&s->pics[0], 0x00); + if (ret == 2) + ret = pic_poll_read(&s->pics[1], 0x80) + 8; + /* Prepare for ISR read */ + s->pics[0].read_reg_select = 1; + + return ret; +} + +static void elcr_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + PicState *s = opaque; + s->elcr = val & s->elcr_mask; +} + +static uint32_t elcr_ioport_read(void *opaque, uint32_t addr1) +{ + PicState *s = opaque; + return s->elcr; +} + +static void pic_save(QEMUFile *f, void *opaque) +{ + PicState *s = opaque; + + qemu_put_8s(f, &s->last_irr); + qemu_put_8s(f, &s->irr); + qemu_put_8s(f, &s->imr); + qemu_put_8s(f, &s->isr); + qemu_put_8s(f, &s->priority_add); + qemu_put_8s(f, &s->irq_base); + qemu_put_8s(f, &s->read_reg_select); + qemu_put_8s(f, &s->poll); + qemu_put_8s(f, &s->special_mask); + qemu_put_8s(f, &s->init_state); + qemu_put_8s(f, &s->auto_eoi); + qemu_put_8s(f, &s->rotate_on_auto_eoi); + qemu_put_8s(f, &s->special_fully_nested_mode); + qemu_put_8s(f, &s->init4); + qemu_put_8s(f, &s->single_mode); + qemu_put_8s(f, &s->elcr); +} + +static int pic_load(QEMUFile *f, void *opaque, int version_id) +{ + PicState *s = opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_8s(f, &s->last_irr); + qemu_get_8s(f, &s->irr); + qemu_get_8s(f, &s->imr); + qemu_get_8s(f, &s->isr); + qemu_get_8s(f, &s->priority_add); + qemu_get_8s(f, &s->irq_base); + qemu_get_8s(f, &s->read_reg_select); + qemu_get_8s(f, &s->poll); + qemu_get_8s(f, &s->special_mask); + qemu_get_8s(f, &s->init_state); + qemu_get_8s(f, &s->auto_eoi); + qemu_get_8s(f, &s->rotate_on_auto_eoi); + qemu_get_8s(f, &s->special_fully_nested_mode); + qemu_get_8s(f, &s->init4); + qemu_get_8s(f, &s->single_mode); + qemu_get_8s(f, &s->elcr); + return 0; +} + +/* XXX: add generic master/slave system */ +static void pic_init1(int io_addr, int elcr_addr, PicState *s) +{ + register_ioport_write(io_addr, 2, 1, pic_ioport_write, s); + register_ioport_read(io_addr, 2, 1, pic_ioport_read, s); + if (elcr_addr >= 0) { + register_ioport_write(elcr_addr, 1, 1, elcr_ioport_write, s); + register_ioport_read(elcr_addr, 1, 1, elcr_ioport_read, s); + } + register_savevm("i8259", io_addr, 1, pic_save, pic_load, s); + qemu_register_reset(pic_reset, 0, s); +} + +void pic_info(Monitor *mon) +{ + int i; + PicState *s; + + if (!isa_pic) + return; + + for(i=0;i<2;i++) { + s = &isa_pic->pics[i]; + monitor_printf(mon, "pic%d: irr=%02x imr=%02x isr=%02x hprio=%d " + "irq_base=%02x rr_sel=%d elcr=%02x fnm=%d\n", + i, s->irr, s->imr, s->isr, s->priority_add, + s->irq_base, s->read_reg_select, s->elcr, + s->special_fully_nested_mode); + } +} + +void irq_info(Monitor *mon) +{ +#ifndef DEBUG_IRQ_COUNT + monitor_printf(mon, "irq statistic code not compiled.\n"); +#else + int i; + int64_t count; + + monitor_printf(mon, "IRQ statistics:\n"); + for (i = 0; i < 16; i++) { + count = irq_count[i]; + if (count > 0) + monitor_printf(mon, "%2d: %" PRId64 "\n", i, count); + } +#endif +} + +qemu_irq *i8259_init(qemu_irq parent_irq) +{ + PicState2 *s; + + s = qemu_mallocz(sizeof(PicState2)); + pic_init1(0x20, 0x4d0, &s->pics[0]); + pic_init1(0xa0, 0x4d1, &s->pics[1]); + s->pics[0].elcr_mask = 0xf8; + s->pics[1].elcr_mask = 0xde; + s->parent_irq = parent_irq; + s->pics[0].pics_state = s; + s->pics[1].pics_state = s; + isa_pic = s; + return qemu_allocate_irqs(i8259_set_irq, s, 16); +} + +void pic_set_alt_irq_func(PicState2 *s, SetIRQFunc *alt_irq_func, + void *alt_irq_opaque) +{ + s->alt_irq_func = alt_irq_func; + s->alt_irq_opaque = alt_irq_opaque; +} diff --git a/hw/ioapic.c b/hw/ioapic.c new file mode 100644 index 0000000..b179e6e --- /dev/null +++ b/hw/ioapic.c @@ -0,0 +1,261 @@ +/* + * ioapic.c IOAPIC emulation logic + * + * Copyright (c) 2004-2005 Fabrice Bellard + * + * Split the ioapic logic from apic.c + * Xiantao Zhang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + */ + +#include "hw.h" +#include "pc.h" +#include "qemu-timer.h" +#include "host-utils.h" + +//#define DEBUG_IOAPIC + +#define IOAPIC_NUM_PINS 0x18 +#define IOAPIC_LVT_MASKED (1<<16) + +#define IOAPIC_TRIGGER_EDGE 0 +#define IOAPIC_TRIGGER_LEVEL 1 + +/*io{apic,sapic} delivery mode*/ +#define IOAPIC_DM_FIXED 0x0 +#define IOAPIC_DM_LOWEST_PRIORITY 0x1 +#define IOAPIC_DM_PMI 0x2 +#define IOAPIC_DM_NMI 0x4 +#define IOAPIC_DM_INIT 0x5 +#define IOAPIC_DM_SIPI 0x5 +#define IOAPIC_DM_EXTINT 0x7 + +struct IOAPICState { + uint8_t id; + uint8_t ioregsel; + + uint32_t irr; + uint64_t ioredtbl[IOAPIC_NUM_PINS]; +}; + +static void ioapic_service(IOAPICState *s) +{ + uint8_t i; + uint8_t trig_mode; + uint8_t vector; + uint8_t delivery_mode; + uint32_t mask; + uint64_t entry; + uint8_t dest; + uint8_t dest_mode; + uint8_t polarity; + + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + mask = 1 << i; + if (s->irr & mask) { + entry = s->ioredtbl[i]; + if (!(entry & IOAPIC_LVT_MASKED)) { + trig_mode = ((entry >> 15) & 1); + dest = entry >> 56; + dest_mode = (entry >> 11) & 1; + delivery_mode = (entry >> 8) & 7; + polarity = (entry >> 13) & 1; + if (trig_mode == IOAPIC_TRIGGER_EDGE) + s->irr &= ~mask; + if (delivery_mode == IOAPIC_DM_EXTINT) + vector = pic_read_irq(isa_pic); + else + vector = entry & 0xff; + + apic_deliver_irq(dest, dest_mode, delivery_mode, + vector, polarity, trig_mode); + } + } + } +} + +void ioapic_set_irq(void *opaque, int vector, int level) +{ + IOAPICState *s = opaque; + + /* ISA IRQs map to GSI 1-1 except for IRQ0 which maps + * to GSI 2. GSI maps to ioapic 1-1. This is not + * the cleanest way of doing it but it should work. */ + + if (vector == 0) + vector = 2; + + if (vector >= 0 && vector < IOAPIC_NUM_PINS) { + uint32_t mask = 1 << vector; + uint64_t entry = s->ioredtbl[vector]; + + if ((entry >> 15) & 1) { + /* level triggered */ + if (level) { + s->irr |= mask; + ioapic_service(s); + } else { + s->irr &= ~mask; + } + } else { + /* edge triggered */ + if (level) { + s->irr |= mask; + ioapic_service(s); + } + } + } +} + +static uint32_t ioapic_mem_readl(void *opaque, target_phys_addr_t addr) +{ + IOAPICState *s = opaque; + int index; + uint32_t val = 0; + + addr &= 0xff; + if (addr == 0x00) { + val = s->ioregsel; + } else if (addr == 0x10) { + switch (s->ioregsel) { + case 0x00: + val = s->id << 24; + break; + case 0x01: + val = 0x11 | ((IOAPIC_NUM_PINS - 1) << 16); /* version 0x11 */ + break; + case 0x02: + val = 0; + break; + default: + index = (s->ioregsel - 0x10) >> 1; + if (index >= 0 && index < IOAPIC_NUM_PINS) { + if (s->ioregsel & 1) + val = s->ioredtbl[index] >> 32; + else + val = s->ioredtbl[index] & 0xffffffff; + } + } +#ifdef DEBUG_IOAPIC + printf("I/O APIC read: %08x = %08x\n", s->ioregsel, val); +#endif + } + return val; +} + +static void ioapic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + IOAPICState *s = opaque; + int index; + + addr &= 0xff; + if (addr == 0x00) { + s->ioregsel = val; + return; + } else if (addr == 0x10) { +#ifdef DEBUG_IOAPIC + printf("I/O APIC write: %08x = %08x\n", s->ioregsel, val); +#endif + switch (s->ioregsel) { + case 0x00: + s->id = (val >> 24) & 0xff; + return; + case 0x01: + case 0x02: + return; + default: + index = (s->ioregsel - 0x10) >> 1; + if (index >= 0 && index < IOAPIC_NUM_PINS) { + if (s->ioregsel & 1) { + s->ioredtbl[index] &= 0xffffffff; + s->ioredtbl[index] |= (uint64_t)val << 32; + } else { + s->ioredtbl[index] &= ~0xffffffffULL; + s->ioredtbl[index] |= val; + } + ioapic_service(s); + } + } + } +} + +static void ioapic_save(QEMUFile *f, void *opaque) +{ + IOAPICState *s = opaque; + int i; + + qemu_put_8s(f, &s->id); + qemu_put_8s(f, &s->ioregsel); + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + qemu_put_be64s(f, &s->ioredtbl[i]); + } +} + +static int ioapic_load(QEMUFile *f, void *opaque, int version_id) +{ + IOAPICState *s = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + qemu_get_8s(f, &s->id); + qemu_get_8s(f, &s->ioregsel); + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + qemu_get_be64s(f, &s->ioredtbl[i]); + } + return 0; +} + +static void ioapic_reset(void *opaque) +{ + IOAPICState *s = opaque; + int i; + + memset(s, 0, sizeof(*s)); + for(i = 0; i < IOAPIC_NUM_PINS; i++) + s->ioredtbl[i] = 1 << 16; /* mask LVT */ +} + +static CPUReadMemoryFunc *ioapic_mem_read[3] = { + ioapic_mem_readl, + ioapic_mem_readl, + ioapic_mem_readl, +}; + +static CPUWriteMemoryFunc *ioapic_mem_write[3] = { + ioapic_mem_writel, + ioapic_mem_writel, + ioapic_mem_writel, +}; + +IOAPICState *ioapic_init(void) +{ + IOAPICState *s; + int io_memory; + + s = qemu_mallocz(sizeof(IOAPICState)); + ioapic_reset(s); + + io_memory = cpu_register_io_memory(ioapic_mem_read, + ioapic_mem_write, s); + cpu_register_physical_memory(0xfec00000, 0x1000, io_memory); + + register_savevm("ioapic", 0, 1, ioapic_save, ioapic_load, s); + qemu_register_reset(ioapic_reset, 0, s); + + return s; +} diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c new file mode 100644 index 0000000..f93a3cb --- /dev/null +++ b/hw/mc146818rtc.c @@ -0,0 +1,754 @@ +/* + * QEMU MC146818 RTC emulation + * + * Copyright (c) 2003-2004 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 "hw.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "pc.h" +#include "isa.h" +//#include "hpet_emul.h" + +//#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 +#define REG_B_SQWE 0x08 +#define REG_B_DM 0x04 + +#define REG_C_UF 0x10 +#define REG_C_IRQF 0x80 +#define REG_C_PF 0x40 +#define REG_C_AF 0x20 + +struct RTCState { + uint8_t cmos_data[128]; + uint8_t cmos_index; + struct tm current_tm; + int base_year; + qemu_irq irq; + qemu_irq sqw_irq; + int it_shift; + /* periodic timer */ + QEMUTimer *periodic_timer; + int64_t next_periodic_time; + /* second update */ + int64_t next_second_time; +#ifdef TARGET_I386 + uint32_t irq_coalesced; + uint32_t period; + QEMUTimer *coalesced_timer; +#endif + QEMUTimer *second_timer; + QEMUTimer *second_timer2; +}; + +static void rtc_irq_raise(qemu_irq irq) { + /* When HPET is operating in legacy mode, RTC interrupts are disabled + * We block qemu_irq_raise, but not qemu_irq_lower, in case legacy + * mode is established while interrupt is raised. We want it to + * be lowered in any case + */ +#ifndef CONFIG_ANDROID +#if defined TARGET_I386 || defined TARGET_X86_64 + if (!hpet_in_legacy_mode()) +#endif +#endif + qemu_irq_raise(irq); +} + +static void rtc_set_time(RTCState *s); +static void rtc_copy_date(RTCState *s); + +#ifdef TARGET_I386 +static void rtc_coalesced_timer_update(RTCState *s) +{ + if (s->irq_coalesced == 0) { + qemu_del_timer(s->coalesced_timer); + } else { + /* divide each RTC interval to 2 - 8 smaller intervals */ + int c = MIN(s->irq_coalesced, 7) + 1; + int64_t next_clock = qemu_get_clock(vm_clock) + + muldiv64(s->period / c, get_ticks_per_sec(), 32768); + qemu_mod_timer(s->coalesced_timer, next_clock); + } +} + +static void rtc_coalesced_timer(void *opaque) +{ + RTCState *s = opaque; + + if (s->irq_coalesced != 0) { + apic_reset_irq_delivered(); + s->cmos_data[RTC_REG_C] |= 0xc0; + rtc_irq_raise(s->irq); + if (apic_get_irq_delivered()) { + s->irq_coalesced--; + } + } + + rtc_coalesced_timer_update(s); +} +#endif + +static void rtc_timer_update(RTCState *s, int64_t current_time) +{ + int period_code, period; + int64_t cur_clock, next_irq_clock; + int enable_pie; + + period_code = s->cmos_data[RTC_REG_A] & 0x0f; +#ifndef CONFIG_ANDROID +#if defined TARGET_I386 || defined TARGET_X86_64 + /* disable periodic timer if hpet is in legacy mode, since interrupts are + * disabled anyway. + */ + enable_pie = !hpet_in_legacy_mode(); +#else + enable_pie = 1; +#endif +#endif + enable_pie = 1; + + if (period_code != 0 + && (((s->cmos_data[RTC_REG_B] & REG_B_PIE) && enable_pie) + || ((s->cmos_data[RTC_REG_B] & REG_B_SQWE) && s->sqw_irq))) { + if (period_code <= 2) + period_code += 7; + /* period in 32 Khz cycles */ + period = 1 << (period_code - 1); +#ifdef TARGET_I386 + if(period != s->period) + s->irq_coalesced = (s->irq_coalesced * s->period) / period; + s->period = period; +#endif + /* compute 32 khz clock */ + cur_clock = muldiv64(current_time, 32768, get_ticks_per_sec()); + next_irq_clock = (cur_clock & ~(period - 1)) + period; + s->next_periodic_time = muldiv64(next_irq_clock, get_ticks_per_sec(), 32768) + 1; + qemu_mod_timer(s->periodic_timer, s->next_periodic_time); + } else { +#ifdef TARGET_I386 + s->irq_coalesced = 0; +#endif + qemu_del_timer(s->periodic_timer); + } +} + +static void rtc_periodic_timer(void *opaque) +{ + RTCState *s = opaque; + + rtc_timer_update(s, s->next_periodic_time); + if (s->cmos_data[RTC_REG_B] & REG_B_PIE) { + s->cmos_data[RTC_REG_C] |= 0xc0; +#ifdef TARGET_I386 + if(rtc_td_hack) { + apic_reset_irq_delivered(); + rtc_irq_raise(s->irq); + if (!apic_get_irq_delivered()) { + s->irq_coalesced++; + rtc_coalesced_timer_update(s); + } + } else +#endif + rtc_irq_raise(s->irq); + } + if (s->cmos_data[RTC_REG_B] & REG_B_SQWE) { + /* Not square wave at all but we don't want 2048Hz interrupts! + Must be seen as a pulse. */ + qemu_irq_raise(s->sqw_irq); + } +} + +static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) +{ + RTCState *s = opaque; + + if ((addr & 1) == 0) { + s->cmos_index = data & 0x7f; + } else { +#ifdef DEBUG_CMOS + printf("cmos: write index=0x%02x val=0x%02x\n", + s->cmos_index, data); +#endif + switch(s->cmos_index) { + case RTC_SECONDS_ALARM: + case RTC_MINUTES_ALARM: + case RTC_HOURS_ALARM: + /* XXX: not supported */ + s->cmos_data[s->cmos_index] = data; + break; + 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: + s->cmos_data[s->cmos_index] = data; + /* if in set mode, do not update the time */ + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_set_time(s); + } + break; + case RTC_REG_A: + /* UIP bit is read only */ + s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) | + (s->cmos_data[RTC_REG_A] & REG_A_UIP); + rtc_timer_update(s, qemu_get_clock(vm_clock)); + break; + case RTC_REG_B: + if (data & REG_B_SET) { + /* set mode: reset UIP mode */ + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + data &= ~REG_B_UIE; + } else { + /* if disabling set mode, update the time */ + if (s->cmos_data[RTC_REG_B] & REG_B_SET) { + rtc_set_time(s); + } + } + s->cmos_data[RTC_REG_B] = data; + rtc_timer_update(s, qemu_get_clock(vm_clock)); + break; + case RTC_REG_C: + case RTC_REG_D: + /* cannot write to them */ + break; + default: + s->cmos_data[s->cmos_index] = data; + break; + } + } +} + +static inline int rtc_to_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & REG_B_DM) { + return a; + } else { + return ((a / 10) << 4) | (a % 10); + } +} + +static inline int rtc_from_bcd(RTCState *s, int a) +{ + if (s->cmos_data[RTC_REG_B] & REG_B_DM) { + return a; + } else { + return ((a >> 4) * 10) + (a & 0x0f); + } +} + +static void rtc_set_time(RTCState *s) +{ + struct tm *tm = &s->current_tm; + + tm->tm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]); + tm->tm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]); + tm->tm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS] & 0x7f); + if (!(s->cmos_data[RTC_REG_B] & 0x02) && + (s->cmos_data[RTC_HOURS] & 0x80)) { + tm->tm_hour += 12; + } + tm->tm_wday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_WEEK]) - 1; + tm->tm_mday = rtc_from_bcd(s, s->cmos_data[RTC_DAY_OF_MONTH]); + tm->tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1; + tm->tm_year = rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year - 1900; +} + +static void rtc_copy_date(RTCState *s) +{ + const struct tm *tm = &s->current_tm; + int year; + + s->cmos_data[RTC_SECONDS] = rtc_to_bcd(s, tm->tm_sec); + s->cmos_data[RTC_MINUTES] = rtc_to_bcd(s, tm->tm_min); + if (s->cmos_data[RTC_REG_B] & 0x02) { + /* 24 hour format */ + s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour); + } else { + /* 12 hour format */ + s->cmos_data[RTC_HOURS] = rtc_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] = rtc_to_bcd(s, tm->tm_wday + 1); + s->cmos_data[RTC_DAY_OF_MONTH] = rtc_to_bcd(s, tm->tm_mday); + s->cmos_data[RTC_MONTH] = rtc_to_bcd(s, tm->tm_mon + 1); + year = (tm->tm_year - s->base_year) % 100; + if (year < 0) + year += 100; + s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year); +} + +/* month is between 0 and 11. */ +static int get_days_in_month(int month, int year) +{ + static const int days_tab[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int d; + if ((unsigned )month >= 12) + return 31; + d = days_tab[month]; + if (month == 1) { + if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) + d++; + } + return d; +} + +/* update 'tm' to the next second */ +static void rtc_next_second(struct tm *tm) +{ + int days_in_month; + + tm->tm_sec++; + if ((unsigned)tm->tm_sec >= 60) { + tm->tm_sec = 0; + tm->tm_min++; + if ((unsigned)tm->tm_min >= 60) { + tm->tm_min = 0; + tm->tm_hour++; + if ((unsigned)tm->tm_hour >= 24) { + tm->tm_hour = 0; + /* next day */ + tm->tm_wday++; + if ((unsigned)tm->tm_wday >= 7) + tm->tm_wday = 0; + days_in_month = get_days_in_month(tm->tm_mon, + tm->tm_year + 1900); + tm->tm_mday++; + if (tm->tm_mday < 1) { + tm->tm_mday = 1; + } else if (tm->tm_mday > days_in_month) { + tm->tm_mday = 1; + tm->tm_mon++; + if (tm->tm_mon >= 12) { + tm->tm_mon = 0; + tm->tm_year++; + } + } + } + } + } +} + + +static void rtc_update_second(void *opaque) +{ + RTCState *s = opaque; + int64_t delay; + + /* if the oscillator is not in normal operation, we do not update */ + if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) { + s->next_second_time += get_ticks_per_sec(); + qemu_mod_timer(s->second_timer, s->next_second_time); + } else { + rtc_next_second(&s->current_tm); + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + /* update in progress bit */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + } + /* should be 244 us = 8 / 32768 seconds, but currently the + timers do not have the necessary resolution. */ + delay = (get_ticks_per_sec() * 1) / 100; + if (delay < 1) + delay = 1; + qemu_mod_timer(s->second_timer2, + s->next_second_time + delay); + } +} + +static void rtc_update_second2(void *opaque) +{ + RTCState *s = opaque; + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_copy_date(s); + } + + /* check alarm */ + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_SECONDS_ALARM] == s->current_tm.tm_sec) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_MINUTES_ALARM] == s->current_tm.tm_mon) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || + s->cmos_data[RTC_HOURS_ALARM] == s->current_tm.tm_hour)) { + + s->cmos_data[RTC_REG_C] |= 0xa0; + rtc_irq_raise(s->irq); + } + } + + /* update ended interrupt */ + if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { + s->cmos_data[RTC_REG_C] |= 0x90; + rtc_irq_raise(s->irq); + } + + /* clear update in progress bit */ + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + + s->next_second_time += get_ticks_per_sec(); + qemu_mod_timer(s->second_timer, s->next_second_time); +} + +static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) +{ + RTCState *s = opaque; + int ret; + if ((addr & 1) == 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: + ret = s->cmos_data[s->cmos_index]; + break; + case RTC_REG_A: + ret = s->cmos_data[s->cmos_index]; + break; + case RTC_REG_C: + ret = s->cmos_data[s->cmos_index]; + qemu_irq_lower(s->irq); + s->cmos_data[RTC_REG_C] = 0x00; + break; + default: + ret = s->cmos_data[s->cmos_index]; + break; + } +#ifdef DEBUG_CMOS + printf("cmos: read index=0x%02x val=0x%02x\n", + s->cmos_index, ret); +#endif + return ret; + } +} + +void rtc_set_memory(RTCState *s, int addr, int val) +{ + if (addr >= 0 && addr <= 127) + s->cmos_data[addr] = val; +} + +void rtc_set_date(RTCState *s, const struct tm *tm) +{ + s->current_tm = *tm; + rtc_copy_date(s); +} + +/* PC cmos mappings */ +#define REG_IBM_CENTURY_BYTE 0x32 +#define REG_IBM_PS2_CENTURY_BYTE 0x37 + +static void rtc_set_date_from_host(RTCState *s) +{ + struct tm tm; + int val; + + /* set the CMOS date */ + qemu_get_timedate(&tm, 0); + rtc_set_date(s, &tm); + + val = rtc_to_bcd(s, (tm.tm_year / 100) + 19); + rtc_set_memory(s, REG_IBM_CENTURY_BYTE, val); + rtc_set_memory(s, REG_IBM_PS2_CENTURY_BYTE, val); +} + +static void rtc_save(QEMUFile *f, void *opaque) +{ + RTCState *s = opaque; + + qemu_put_buffer(f, s->cmos_data, 128); + qemu_put_8s(f, &s->cmos_index); + + qemu_put_be32(f, s->current_tm.tm_sec); + qemu_put_be32(f, s->current_tm.tm_min); + qemu_put_be32(f, s->current_tm.tm_hour); + qemu_put_be32(f, s->current_tm.tm_wday); + qemu_put_be32(f, s->current_tm.tm_mday); + qemu_put_be32(f, s->current_tm.tm_mon); + qemu_put_be32(f, s->current_tm.tm_year); + + qemu_put_timer(f, s->periodic_timer); + qemu_put_be64(f, s->next_periodic_time); + + qemu_put_be64(f, s->next_second_time); + qemu_put_timer(f, s->second_timer); + qemu_put_timer(f, s->second_timer2); +} + +static int rtc_load(QEMUFile *f, void *opaque, int version_id) +{ + RTCState *s = opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_buffer(f, s->cmos_data, 128); + qemu_get_8s(f, &s->cmos_index); + + s->current_tm.tm_sec=qemu_get_be32(f); + s->current_tm.tm_min=qemu_get_be32(f); + s->current_tm.tm_hour=qemu_get_be32(f); + s->current_tm.tm_wday=qemu_get_be32(f); + s->current_tm.tm_mday=qemu_get_be32(f); + s->current_tm.tm_mon=qemu_get_be32(f); + s->current_tm.tm_year=qemu_get_be32(f); + + qemu_get_timer(f, s->periodic_timer); + s->next_periodic_time=qemu_get_be64(f); + + s->next_second_time=qemu_get_be64(f); + qemu_get_timer(f, s->second_timer); + qemu_get_timer(f, s->second_timer2); + return 0; +} + +#ifdef TARGET_I386 +static void rtc_save_td(QEMUFile *f, void *opaque) +{ + RTCState *s = opaque; + + qemu_put_be32(f, s->irq_coalesced); + qemu_put_be32(f, s->period); +} + +static int rtc_load_td(QEMUFile *f, void *opaque, int version_id) +{ + RTCState *s = opaque; + + if (version_id != 1) + return -EINVAL; + + s->irq_coalesced = qemu_get_be32(f); + s->period = qemu_get_be32(f); + rtc_coalesced_timer_update(s); + return 0; +} +#endif + +static void rtc_reset(void *opaque) +{ + RTCState *s = opaque; + + s->cmos_data[RTC_REG_B] &= ~(REG_B_PIE | REG_B_AIE | REG_B_SQWE); + s->cmos_data[RTC_REG_C] &= ~(REG_C_UF | REG_C_IRQF | REG_C_PF | REG_C_AF); + + qemu_irq_lower(s->irq); + +#ifdef TARGET_I386 + if (rtc_td_hack) + s->irq_coalesced = 0; +#endif +} + +RTCState *rtc_init_sqw(int base, qemu_irq irq, qemu_irq sqw_irq, int base_year) +{ + RTCState *s; + + s = qemu_mallocz(sizeof(RTCState)); + + s->irq = irq; + s->sqw_irq = sqw_irq; + 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; + + s->base_year = base_year; + rtc_set_date_from_host(s); + + s->periodic_timer = qemu_new_timer(vm_clock, + rtc_periodic_timer, s); +#ifdef TARGET_I386 + if (rtc_td_hack) + s->coalesced_timer = qemu_new_timer(vm_clock, rtc_coalesced_timer, s); +#endif + s->second_timer = qemu_new_timer(vm_clock, + rtc_update_second, s); + s->second_timer2 = qemu_new_timer(vm_clock, + rtc_update_second2, s); + + s->next_second_time = qemu_get_clock(vm_clock) + (get_ticks_per_sec() * 99) / 100; + qemu_mod_timer(s->second_timer2, s->next_second_time); + + register_ioport_write(base, 2, 1, cmos_ioport_write, s); + register_ioport_read(base, 2, 1, cmos_ioport_read, s); + + register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s); +#ifdef TARGET_I386 + if (rtc_td_hack) + register_savevm("mc146818rtc-td", base, 1, rtc_save_td, rtc_load_td, s); +#endif + qemu_register_reset(rtc_reset, 0, s); + + return s; +} + +RTCState *rtc_init(int base, qemu_irq irq, int base_year) +{ + return rtc_init_sqw(base, irq, NULL, base_year); +} + +/* Memory mapped interface */ +static uint32_t cmos_mm_readb (void *opaque, target_phys_addr_t addr) +{ + RTCState *s = opaque; + + return cmos_ioport_read(s, addr >> s->it_shift) & 0xFF; +} + +static void cmos_mm_writeb (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + RTCState *s = opaque; + + cmos_ioport_write(s, addr >> s->it_shift, value & 0xFF); +} + +static uint32_t cmos_mm_readw (void *opaque, target_phys_addr_t addr) +{ + RTCState *s = opaque; + uint32_t val; + + val = cmos_ioport_read(s, addr >> s->it_shift) & 0xFFFF; +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap16(val); +#endif + return val; +} + +static void cmos_mm_writew (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + RTCState *s = opaque; +#ifdef TARGET_WORDS_BIGENDIAN + value = bswap16(value); +#endif + cmos_ioport_write(s, addr >> s->it_shift, value & 0xFFFF); +} + +static uint32_t cmos_mm_readl (void *opaque, target_phys_addr_t addr) +{ + RTCState *s = opaque; + uint32_t val; + + val = cmos_ioport_read(s, addr >> s->it_shift); +#ifdef TARGET_WORDS_BIGENDIAN + val = bswap32(val); +#endif + return val; +} + +static void cmos_mm_writel (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + RTCState *s = opaque; +#ifdef TARGET_WORDS_BIGENDIAN + value = bswap32(value); +#endif + cmos_ioport_write(s, addr >> s->it_shift, value); +} + +static CPUReadMemoryFunc *rtc_mm_read[] = { + &cmos_mm_readb, + &cmos_mm_readw, + &cmos_mm_readl, +}; + +static CPUWriteMemoryFunc *rtc_mm_write[] = { + &cmos_mm_writeb, + &cmos_mm_writew, + &cmos_mm_writel, +}; + +RTCState *rtc_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq, + int base_year) +{ + RTCState *s; + int io_memory; + + s = qemu_mallocz(sizeof(RTCState)); + + s->irq = irq; + 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; + + s->base_year = base_year; + rtc_set_date_from_host(s); + + s->periodic_timer = qemu_new_timer(vm_clock, + rtc_periodic_timer, s); + s->second_timer = qemu_new_timer(vm_clock, + rtc_update_second, s); + s->second_timer2 = qemu_new_timer(vm_clock, + rtc_update_second2, s); + + s->next_second_time = qemu_get_clock(vm_clock) + (get_ticks_per_sec() * 99) / 100; + qemu_mod_timer(s->second_timer2, s->next_second_time); + + io_memory = cpu_register_io_memory(rtc_mm_read, rtc_mm_write, s); + cpu_register_physical_memory(base, 2 << it_shift, io_memory); + + register_savevm("mc146818rtc", base, 1, rtc_save, rtc_load, s); +#ifdef TARGET_I386 + if (rtc_td_hack) + register_savevm("mc146818rtc-td", base, 1, rtc_save_td, rtc_load_td, s); +#endif + qemu_register_reset(rtc_reset, 0, s); + return s; +} diff --git a/hw/ne2000.c b/hw/ne2000.c new file mode 100644 index 0000000..66ae9ab --- /dev/null +++ b/hw/ne2000.c @@ -0,0 +1,840 @@ +/* + * QEMU NE2000 emulation + * + * Copyright (c) 2003-2004 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 "hw.h" +#include "pci.h" +#include "pc.h" +#include "net.h" + +/* debug NE2000 card */ +//#define DEBUG_NE2000 + +#define MAX_ETH_FRAME_SIZE 1514 + +#define E8390_CMD 0x00 /* The command register (for all pages) */ +/* Page 0 register offsets. */ +#define EN0_CLDALO 0x01 /* Low byte of current local dma addr RD */ +#define EN0_STARTPG 0x01 /* Starting page of ring bfr WR */ +#define EN0_CLDAHI 0x02 /* High byte of current local dma addr RD */ +#define EN0_STOPPG 0x02 /* Ending page +1 of ring bfr WR */ +#define EN0_BOUNDARY 0x03 /* Boundary page of ring bfr RD WR */ +#define EN0_TSR 0x04 /* Transmit status reg RD */ +#define EN0_TPSR 0x04 /* Transmit starting page WR */ +#define EN0_NCR 0x05 /* Number of collision reg RD */ +#define EN0_TCNTLO 0x05 /* Low byte of tx byte count WR */ +#define EN0_FIFO 0x06 /* FIFO RD */ +#define EN0_TCNTHI 0x06 /* High byte of tx byte count WR */ +#define EN0_ISR 0x07 /* Interrupt status reg RD WR */ +#define EN0_CRDALO 0x08 /* low byte of current remote dma address RD */ +#define EN0_RSARLO 0x08 /* Remote start address reg 0 */ +#define EN0_CRDAHI 0x09 /* high byte, current remote dma address RD */ +#define EN0_RSARHI 0x09 /* Remote start address reg 1 */ +#define EN0_RCNTLO 0x0a /* Remote byte count reg WR */ +#define EN0_RTL8029ID0 0x0a /* Realtek ID byte #1 RD */ +#define EN0_RCNTHI 0x0b /* Remote byte count reg WR */ +#define EN0_RTL8029ID1 0x0b /* Realtek ID byte #2 RD */ +#define EN0_RSR 0x0c /* rx status reg RD */ +#define EN0_RXCR 0x0c /* RX configuration reg WR */ +#define EN0_TXCR 0x0d /* TX configuration reg WR */ +#define EN0_COUNTER0 0x0d /* Rcv alignment error counter RD */ +#define EN0_DCFG 0x0e /* Data configuration reg WR */ +#define EN0_COUNTER1 0x0e /* Rcv CRC error counter RD */ +#define EN0_IMR 0x0f /* Interrupt mask reg WR */ +#define EN0_COUNTER2 0x0f /* Rcv missed frame error counter RD */ + +#define EN1_PHYS 0x11 +#define EN1_CURPAG 0x17 +#define EN1_MULT 0x18 + +#define EN2_STARTPG 0x21 /* Starting page of ring bfr RD */ +#define EN2_STOPPG 0x22 /* Ending page +1 of ring bfr RD */ + +#define EN3_CONFIG0 0x33 +#define EN3_CONFIG1 0x34 +#define EN3_CONFIG2 0x35 +#define EN3_CONFIG3 0x36 + +/* Register accessed at EN_CMD, the 8390 base addr. */ +#define E8390_STOP 0x01 /* Stop and reset the chip */ +#define E8390_START 0x02 /* Start the chip, clear reset */ +#define E8390_TRANS 0x04 /* Transmit a frame */ +#define E8390_RREAD 0x08 /* Remote read */ +#define E8390_RWRITE 0x10 /* Remote write */ +#define E8390_NODMA 0x20 /* Remote DMA */ +#define E8390_PAGE0 0x00 /* Select page chip registers */ +#define E8390_PAGE1 0x40 /* using the two high-order bits */ +#define E8390_PAGE2 0x80 /* Page 3 is invalid. */ + +/* Bits in EN0_ISR - Interrupt status register */ +#define ENISR_RX 0x01 /* Receiver, no error */ +#define ENISR_TX 0x02 /* Transmitter, no error */ +#define ENISR_RX_ERR 0x04 /* Receiver, with error */ +#define ENISR_TX_ERR 0x08 /* Transmitter, with error */ +#define ENISR_OVER 0x10 /* Receiver overwrote the ring */ +#define ENISR_COUNTERS 0x20 /* Counters need emptying */ +#define ENISR_RDC 0x40 /* remote dma complete */ +#define ENISR_RESET 0x80 /* Reset completed */ +#define ENISR_ALL 0x3f /* Interrupts we will enable */ + +/* Bits in received packet status byte and EN0_RSR*/ +#define ENRSR_RXOK 0x01 /* Received a good packet */ +#define ENRSR_CRC 0x02 /* CRC error */ +#define ENRSR_FAE 0x04 /* frame alignment error */ +#define ENRSR_FO 0x08 /* FIFO overrun */ +#define ENRSR_MPA 0x10 /* missed pkt */ +#define ENRSR_PHY 0x20 /* physical/multicast address */ +#define ENRSR_DIS 0x40 /* receiver disable. set in monitor mode */ +#define ENRSR_DEF 0x80 /* deferring */ + +/* Transmitted packet status, EN0_TSR. */ +#define ENTSR_PTX 0x01 /* Packet transmitted without error */ +#define ENTSR_ND 0x02 /* The transmit wasn't deferred. */ +#define ENTSR_COL 0x04 /* The transmit collided at least once. */ +#define ENTSR_ABT 0x08 /* The transmit collided 16 times, and was deferred. */ +#define ENTSR_CRS 0x10 /* The carrier sense was lost. */ +#define ENTSR_FU 0x20 /* A "FIFO underrun" occurred during transmit. */ +#define ENTSR_CDH 0x40 /* The collision detect "heartbeat" signal was lost. */ +#define ENTSR_OWC 0x80 /* There was an out-of-window collision. */ + +#define NE2000_PMEM_SIZE (32*1024) +#define NE2000_PMEM_START (16*1024) +#define NE2000_PMEM_END (NE2000_PMEM_SIZE+NE2000_PMEM_START) +#define NE2000_MEM_SIZE NE2000_PMEM_END + +typedef struct NE2000State { + uint8_t cmd; + uint32_t start; + uint32_t stop; + uint8_t boundary; + uint8_t tsr; + uint8_t tpsr; + uint16_t tcnt; + uint16_t rcnt; + uint32_t rsar; + uint8_t rsr; + uint8_t rxcr; + uint8_t isr; + uint8_t dcfg; + uint8_t imr; + uint8_t phys[6]; /* mac address */ + uint8_t curpag; + uint8_t mult[8]; /* multicast mask array */ + qemu_irq irq; + int isa_io_base; + PCIDevice *pci_dev; + VLANClientState *vc; + uint8_t macaddr[6]; + uint8_t mem[NE2000_MEM_SIZE]; +} NE2000State; + +static void ne2000_reset(NE2000State *s) +{ + int i; + + s->isr = ENISR_RESET; + memcpy(s->mem, s->macaddr, 6); + s->mem[14] = 0x57; + s->mem[15] = 0x57; + + /* duplicate prom data */ + for(i = 15;i >= 0; i--) { + s->mem[2 * i] = s->mem[i]; + s->mem[2 * i + 1] = s->mem[i]; + } +} + +static void ne2000_update_irq(NE2000State *s) +{ + int isr; + isr = (s->isr & s->imr) & 0x7f; +#if defined(DEBUG_NE2000) + printf("NE2000: Set IRQ to %d (%02x %02x)\n", + isr ? 1 : 0, s->isr, s->imr); +#endif + qemu_set_irq(s->irq, (isr != 0)); +} + +#define POLYNOMIAL 0x04c11db6 + +/* From FreeBSD */ +/* XXX: optimize */ +static int compute_mcast_idx(const uint8_t *ep) +{ + uint32_t crc; + int carry, i, j; + uint8_t b; + + crc = 0xffffffff; + for (i = 0; i < 6; i++) { + b = *ep++; + for (j = 0; j < 8; j++) { + carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); + crc <<= 1; + b >>= 1; + if (carry) + crc = ((crc ^ POLYNOMIAL) | carry); + } + } + return (crc >> 26); +} + +static int ne2000_buffer_full(NE2000State *s) +{ + int avail, index, boundary; + + index = s->curpag << 8; + boundary = s->boundary << 8; + if (index < boundary) + avail = boundary - index; + else + avail = (s->stop - s->start) - (index - boundary); + if (avail < (MAX_ETH_FRAME_SIZE + 4)) + return 1; + return 0; +} + +static int ne2000_can_receive(VLANClientState *vc) +{ + NE2000State *s = vc->opaque; + + if (s->cmd & E8390_STOP) + return 1; + return !ne2000_buffer_full(s); +} + +#define MIN_BUF_SIZE 60 + +static ssize_t ne2000_receive(VLANClientState *vc, const uint8_t *buf, size_t size_) +{ + NE2000State *s = vc->opaque; + int size = size_; + uint8_t *p; + unsigned int total_len, next, avail, len, index, mcast_idx; + uint8_t buf1[60]; + static const uint8_t broadcast_macaddr[6] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +#if defined(DEBUG_NE2000) + printf("NE2000: received len=%d\n", size); +#endif + + if (s->cmd & E8390_STOP || ne2000_buffer_full(s)) + return -1; + + /* XXX: check this */ + if (s->rxcr & 0x10) { + /* promiscuous: receive all */ + } else { + if (!memcmp(buf, broadcast_macaddr, 6)) { + /* broadcast address */ + if (!(s->rxcr & 0x04)) + return size; + } else if (buf[0] & 0x01) { + /* multicast */ + if (!(s->rxcr & 0x08)) + return size; + mcast_idx = compute_mcast_idx(buf); + if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) + return size; + } else if (s->mem[0] == buf[0] && + s->mem[2] == buf[1] && + s->mem[4] == buf[2] && + s->mem[6] == buf[3] && + s->mem[8] == buf[4] && + s->mem[10] == buf[5]) { + /* match */ + } else { + return size; + } + } + + + /* if too small buffer, then expand it */ + if (size < MIN_BUF_SIZE) { + memcpy(buf1, buf, size); + memset(buf1 + size, 0, MIN_BUF_SIZE - size); + buf = buf1; + size = MIN_BUF_SIZE; + } + + index = s->curpag << 8; + /* 4 bytes for header */ + total_len = size + 4; + /* address for next packet (4 bytes for CRC) */ + next = index + ((total_len + 4 + 255) & ~0xff); + if (next >= s->stop) + next -= (s->stop - s->start); + /* prepare packet header */ + p = s->mem + index; + s->rsr = ENRSR_RXOK; /* receive status */ + /* XXX: check this */ + if (buf[0] & 0x01) + s->rsr |= ENRSR_PHY; + p[0] = s->rsr; + p[1] = next >> 8; + p[2] = total_len; + p[3] = total_len >> 8; + index += 4; + + /* write packet data */ + while (size > 0) { + if (index <= s->stop) + avail = s->stop - index; + else + avail = 0; + len = size; + if (len > avail) + len = avail; + memcpy(s->mem + index, buf, len); + buf += len; + index += len; + if (index == s->stop) + index = s->start; + size -= len; + } + s->curpag = next >> 8; + + /* now we can signal we have received something */ + s->isr |= ENISR_RX; + ne2000_update_irq(s); + + return size_; +} + +static void ne2000_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + NE2000State *s = opaque; + int offset, page, index; + + addr &= 0xf; +#ifdef DEBUG_NE2000 + printf("NE2000: write addr=0x%x val=0x%02x\n", addr, val); +#endif + if (addr == E8390_CMD) { + /* control register */ + s->cmd = val; + if (!(val & E8390_STOP)) { /* START bit makes no sense on RTL8029... */ + s->isr &= ~ENISR_RESET; + /* test specific case: zero length transfer */ + if ((val & (E8390_RREAD | E8390_RWRITE)) && + s->rcnt == 0) { + s->isr |= ENISR_RDC; + ne2000_update_irq(s); + } + if (val & E8390_TRANS) { + index = (s->tpsr << 8); + /* XXX: next 2 lines are a hack to make netware 3.11 work */ + if (index >= NE2000_PMEM_END) + index -= NE2000_PMEM_SIZE; + /* fail safe: check range on the transmitted length */ + if (index + s->tcnt <= NE2000_PMEM_END) { + qemu_send_packet(s->vc, s->mem + index, s->tcnt); + } + /* signal end of transfer */ + s->tsr = ENTSR_PTX; + s->isr |= ENISR_TX; + s->cmd &= ~E8390_TRANS; + ne2000_update_irq(s); + } + } + } else { + page = s->cmd >> 6; + offset = addr | (page << 4); + switch(offset) { + case EN0_STARTPG: + s->start = val << 8; + break; + case EN0_STOPPG: + s->stop = val << 8; + break; + case EN0_BOUNDARY: + s->boundary = val; + break; + case EN0_IMR: + s->imr = val; + ne2000_update_irq(s); + break; + case EN0_TPSR: + s->tpsr = val; + break; + case EN0_TCNTLO: + s->tcnt = (s->tcnt & 0xff00) | val; + break; + case EN0_TCNTHI: + s->tcnt = (s->tcnt & 0x00ff) | (val << 8); + break; + case EN0_RSARLO: + s->rsar = (s->rsar & 0xff00) | val; + break; + case EN0_RSARHI: + s->rsar = (s->rsar & 0x00ff) | (val << 8); + break; + case EN0_RCNTLO: + s->rcnt = (s->rcnt & 0xff00) | val; + break; + case EN0_RCNTHI: + s->rcnt = (s->rcnt & 0x00ff) | (val << 8); + break; + case EN0_RXCR: + s->rxcr = val; + break; + case EN0_DCFG: + s->dcfg = val; + break; + case EN0_ISR: + s->isr &= ~(val & 0x7f); + ne2000_update_irq(s); + break; + case EN1_PHYS ... EN1_PHYS + 5: + s->phys[offset - EN1_PHYS] = val; + break; + case EN1_CURPAG: + s->curpag = val; + break; + case EN1_MULT ... EN1_MULT + 7: + s->mult[offset - EN1_MULT] = val; + break; + } + } +} + +static uint32_t ne2000_ioport_read(void *opaque, uint32_t addr) +{ + NE2000State *s = opaque; + int offset, page, ret; + + addr &= 0xf; + if (addr == E8390_CMD) { + ret = s->cmd; + } else { + page = s->cmd >> 6; + offset = addr | (page << 4); + switch(offset) { + case EN0_TSR: + ret = s->tsr; + break; + case EN0_BOUNDARY: + ret = s->boundary; + break; + case EN0_ISR: + ret = s->isr; + break; + case EN0_RSARLO: + ret = s->rsar & 0x00ff; + break; + case EN0_RSARHI: + ret = s->rsar >> 8; + break; + case EN1_PHYS ... EN1_PHYS + 5: + ret = s->phys[offset - EN1_PHYS]; + break; + case EN1_CURPAG: + ret = s->curpag; + break; + case EN1_MULT ... EN1_MULT + 7: + ret = s->mult[offset - EN1_MULT]; + break; + case EN0_RSR: + ret = s->rsr; + break; + case EN2_STARTPG: + ret = s->start >> 8; + break; + case EN2_STOPPG: + ret = s->stop >> 8; + break; + case EN0_RTL8029ID0: + ret = 0x50; + break; + case EN0_RTL8029ID1: + ret = 0x43; + break; + case EN3_CONFIG0: + ret = 0; /* 10baseT media */ + break; + case EN3_CONFIG2: + ret = 0x40; /* 10baseT active */ + break; + case EN3_CONFIG3: + ret = 0x40; /* Full duplex */ + break; + default: + ret = 0x00; + break; + } + } +#ifdef DEBUG_NE2000 + printf("NE2000: read addr=0x%x val=%02x\n", addr, ret); +#endif + return ret; +} + +static inline void ne2000_mem_writeb(NE2000State *s, uint32_t addr, + uint32_t val) +{ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + s->mem[addr] = val; + } +} + +static inline void ne2000_mem_writew(NE2000State *s, uint32_t addr, + uint32_t val) +{ + addr &= ~1; /* XXX: check exact behaviour if not even */ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + *(uint16_t *)(s->mem + addr) = cpu_to_le16(val); + } +} + +static inline void ne2000_mem_writel(NE2000State *s, uint32_t addr, + uint32_t val) +{ + addr &= ~1; /* XXX: check exact behaviour if not even */ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + cpu_to_le32wu((uint32_t *)(s->mem + addr), val); + } +} + +static inline uint32_t ne2000_mem_readb(NE2000State *s, uint32_t addr) +{ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + return s->mem[addr]; + } else { + return 0xff; + } +} + +static inline uint32_t ne2000_mem_readw(NE2000State *s, uint32_t addr) +{ + addr &= ~1; /* XXX: check exact behaviour if not even */ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + return le16_to_cpu(*(uint16_t *)(s->mem + addr)); + } else { + return 0xffff; + } +} + +static inline uint32_t ne2000_mem_readl(NE2000State *s, uint32_t addr) +{ + addr &= ~1; /* XXX: check exact behaviour if not even */ + if (addr < 32 || + (addr >= NE2000_PMEM_START && addr < NE2000_MEM_SIZE)) { + return le32_to_cpupu((uint32_t *)(s->mem + addr)); + } else { + return 0xffffffff; + } +} + +static inline void ne2000_dma_update(NE2000State *s, int len) +{ + s->rsar += len; + /* wrap */ + /* XXX: check what to do if rsar > stop */ + if (s->rsar == s->stop) + s->rsar = s->start; + + if (s->rcnt <= len) { + s->rcnt = 0; + /* signal end of transfer */ + s->isr |= ENISR_RDC; + ne2000_update_irq(s); + } else { + s->rcnt -= len; + } +} + +static void ne2000_asic_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + NE2000State *s = opaque; + +#ifdef DEBUG_NE2000 + printf("NE2000: asic write val=0x%04x\n", val); +#endif + if (s->rcnt == 0) + return; + if (s->dcfg & 0x01) { + /* 16 bit access */ + ne2000_mem_writew(s, s->rsar, val); + ne2000_dma_update(s, 2); + } else { + /* 8 bit access */ + ne2000_mem_writeb(s, s->rsar, val); + ne2000_dma_update(s, 1); + } +} + +static uint32_t ne2000_asic_ioport_read(void *opaque, uint32_t addr) +{ + NE2000State *s = opaque; + int ret; + + if (s->dcfg & 0x01) { + /* 16 bit access */ + ret = ne2000_mem_readw(s, s->rsar); + ne2000_dma_update(s, 2); + } else { + /* 8 bit access */ + ret = ne2000_mem_readb(s, s->rsar); + ne2000_dma_update(s, 1); + } +#ifdef DEBUG_NE2000 + printf("NE2000: asic read val=0x%04x\n", ret); +#endif + return ret; +} + +static void ne2000_asic_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + NE2000State *s = opaque; + +#ifdef DEBUG_NE2000 + printf("NE2000: asic writel val=0x%04x\n", val); +#endif + if (s->rcnt == 0) + return; + /* 32 bit access */ + ne2000_mem_writel(s, s->rsar, val); + ne2000_dma_update(s, 4); +} + +static uint32_t ne2000_asic_ioport_readl(void *opaque, uint32_t addr) +{ + NE2000State *s = opaque; + int ret; + + /* 32 bit access */ + ret = ne2000_mem_readl(s, s->rsar); + ne2000_dma_update(s, 4); +#ifdef DEBUG_NE2000 + printf("NE2000: asic readl val=0x%04x\n", ret); +#endif + return ret; +} + +static void ne2000_reset_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + /* nothing to do (end of reset pulse) */ +} + +static uint32_t ne2000_reset_ioport_read(void *opaque, uint32_t addr) +{ + NE2000State *s = opaque; + ne2000_reset(s); + return 0; +} + +static void ne2000_save(QEMUFile* f,void* opaque) +{ + NE2000State* s=(NE2000State*)opaque; + uint32_t tmp; + + if (s->pci_dev) + pci_device_save(s->pci_dev, f); + + qemu_put_8s(f, &s->rxcr); + + qemu_put_8s(f, &s->cmd); + qemu_put_be32s(f, &s->start); + qemu_put_be32s(f, &s->stop); + qemu_put_8s(f, &s->boundary); + qemu_put_8s(f, &s->tsr); + qemu_put_8s(f, &s->tpsr); + qemu_put_be16s(f, &s->tcnt); + qemu_put_be16s(f, &s->rcnt); + qemu_put_be32s(f, &s->rsar); + qemu_put_8s(f, &s->rsr); + qemu_put_8s(f, &s->isr); + qemu_put_8s(f, &s->dcfg); + qemu_put_8s(f, &s->imr); + qemu_put_buffer(f, s->phys, 6); + qemu_put_8s(f, &s->curpag); + qemu_put_buffer(f, s->mult, 8); + tmp = 0; + qemu_put_be32s(f, &tmp); /* ignored, was irq */ + qemu_put_buffer(f, s->mem, NE2000_MEM_SIZE); +} + +static int ne2000_load(QEMUFile* f,void* opaque,int version_id) +{ + NE2000State* s=(NE2000State*)opaque; + int ret; + uint32_t tmp; + + if (version_id > 3) + return -EINVAL; + + if (s->pci_dev && version_id >= 3) { + ret = pci_device_load(s->pci_dev, f); + if (ret < 0) + return ret; + } + + if (version_id >= 2) { + qemu_get_8s(f, &s->rxcr); + } else { + s->rxcr = 0x0c; + } + + qemu_get_8s(f, &s->cmd); + qemu_get_be32s(f, &s->start); + qemu_get_be32s(f, &s->stop); + qemu_get_8s(f, &s->boundary); + qemu_get_8s(f, &s->tsr); + qemu_get_8s(f, &s->tpsr); + qemu_get_be16s(f, &s->tcnt); + qemu_get_be16s(f, &s->rcnt); + qemu_get_be32s(f, &s->rsar); + qemu_get_8s(f, &s->rsr); + qemu_get_8s(f, &s->isr); + qemu_get_8s(f, &s->dcfg); + qemu_get_8s(f, &s->imr); + qemu_get_buffer(f, s->phys, 6); + qemu_get_8s(f, &s->curpag); + qemu_get_buffer(f, s->mult, 8); + qemu_get_be32s(f, &tmp); /* ignored */ + qemu_get_buffer(f, s->mem, NE2000_MEM_SIZE); + + return 0; +} + +static void isa_ne2000_cleanup(VLANClientState *vc) +{ + NE2000State *s = vc->opaque; + + unregister_savevm("ne2000", s); + + isa_unassign_ioport(s->isa_io_base, 16); + isa_unassign_ioport(s->isa_io_base + 0x10, 2); + isa_unassign_ioport(s->isa_io_base + 0x1f, 1); + + qemu_free(s); +} + +void isa_ne2000_init(int base, qemu_irq irq, NICInfo *nd) +{ + NE2000State *s; + + qemu_check_nic_model(nd, "ne2k_isa"); + + s = qemu_mallocz(sizeof(NE2000State)); + + register_ioport_write(base, 16, 1, ne2000_ioport_write, s); + register_ioport_read(base, 16, 1, ne2000_ioport_read, s); + + register_ioport_write(base + 0x10, 1, 1, ne2000_asic_ioport_write, s); + register_ioport_read(base + 0x10, 1, 1, ne2000_asic_ioport_read, s); + register_ioport_write(base + 0x10, 2, 2, ne2000_asic_ioport_write, s); + register_ioport_read(base + 0x10, 2, 2, ne2000_asic_ioport_read, s); + + register_ioport_write(base + 0x1f, 1, 1, ne2000_reset_ioport_write, s); + register_ioport_read(base + 0x1f, 1, 1, ne2000_reset_ioport_read, s); + s->isa_io_base = base; + s->irq = irq; + memcpy(s->macaddr, nd->macaddr, 6); + + ne2000_reset(s); + + s->vc = qemu_new_vlan_client(nd->vlan, nd->model, nd->name, + ne2000_can_receive, ne2000_receive, NULL, + isa_ne2000_cleanup, s); + + qemu_format_nic_info_str(s->vc, s->macaddr); + + register_savevm("ne2000", -1, 2, ne2000_save, ne2000_load, s); +} + +/***********************************************************/ +/* PCI NE2000 definitions */ + +typedef struct PCINE2000State { + PCIDevice dev; + NE2000State ne2000; +} PCINE2000State; + +static void ne2000_map(PCIDevice *pci_dev, int region_num, + uint32_t addr, uint32_t size, int type) +{ + PCINE2000State *d = (PCINE2000State *)pci_dev; + NE2000State *s = &d->ne2000; + + register_ioport_write(addr, 16, 1, ne2000_ioport_write, s); + register_ioport_read(addr, 16, 1, ne2000_ioport_read, s); + + register_ioport_write(addr + 0x10, 1, 1, ne2000_asic_ioport_write, s); + register_ioport_read(addr + 0x10, 1, 1, ne2000_asic_ioport_read, s); + register_ioport_write(addr + 0x10, 2, 2, ne2000_asic_ioport_write, s); + register_ioport_read(addr + 0x10, 2, 2, ne2000_asic_ioport_read, s); + register_ioport_write(addr + 0x10, 4, 4, ne2000_asic_ioport_writel, s); + register_ioport_read(addr + 0x10, 4, 4, ne2000_asic_ioport_readl, s); + + register_ioport_write(addr + 0x1f, 1, 1, ne2000_reset_ioport_write, s); + register_ioport_read(addr + 0x1f, 1, 1, ne2000_reset_ioport_read, s); +} + +static void ne2000_cleanup(VLANClientState *vc) +{ + NE2000State *s = vc->opaque; + + unregister_savevm("ne2000", s); +} + +static void pci_ne2000_init(PCIDevice *pci_dev) +{ + PCINE2000State *d = (PCINE2000State *)pci_dev; + NE2000State *s; + uint8_t *pci_conf; + + pci_conf = d->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REALTEK); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REALTEK_8029); + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + pci_conf[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; // header_type + pci_conf[0x3d] = 1; // interrupt pin 0 + + pci_register_bar(&d->dev, 0, 0x100, + PCI_ADDRESS_SPACE_IO, ne2000_map); + s = &d->ne2000; + s->irq = d->dev.irq[0]; + s->pci_dev = (PCIDevice *)d; + qdev_get_macaddr(&d->dev.qdev, s->macaddr); + ne2000_reset(s); + s->vc = qdev_get_vlan_client(&d->dev.qdev, + ne2000_can_receive, ne2000_receive, NULL, + ne2000_cleanup, s); + + qemu_format_nic_info_str(s->vc, s->macaddr); + + register_savevm("ne2000", -1, 3, ne2000_save, ne2000_load, s); +} + +static void ne2000_register_devices(void) +{ + pci_qdev_register("ne2k_pci", sizeof(PCINE2000State), pci_ne2000_init); +} + +device_init(ne2000_register_devices) diff --git a/hw/pc.c b/hw/pc.c new file mode 100644 index 0000000..7c32211 --- /dev/null +++ b/hw/pc.c @@ -0,0 +1,1310 @@ +/* + * QEMU PC System Emulator + * + * Copyright (c) 2003-2004 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 "hw.h" +#include "pc.h" +#include "fdc.h" +#include "pci.h" +#include "block.h" +#include "sysemu.h" +#include "blockdev.h" +#include "audio/audio.h" +#include "net.h" +//#include "smbus.h" +#include "boards.h" +#include "android/globals.h" +#include "monitor.h" +#include "fw_cfg.h" +//#include "hpet_emul.h" +#include "watchdog.h" +#include "smbios.h" +#include "console.h" + +#include "goldfish_device.h" + +char* audio_input_source = NULL; +/* output Bochs bios info messages */ +//#define DEBUG_BIOS + +#define BIOS_FILENAME "bios.bin" +#define VGABIOS_FILENAME "vgabios.bin" +#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" + +#define PC_MAX_BIOS_SIZE (4 * 1024 * 1024) + +/* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables. */ +#define ACPI_DATA_SIZE 0x10000 +#define BIOS_CFG_IOPORT 0x510 +#define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0) +#define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1) + +#define MAX_IDE_BUS 2 +#ifndef CONFIG_ANDROID +static fdctrl_t *floppy_controller; +#endif +static RTCState *rtc_state; +static PITState *pit; +static IOAPICState *ioapic; +static PCIDevice *i440fx_state; + +typedef struct rom_reset_data { + uint8_t *data; + target_phys_addr_t addr; + unsigned size; +} RomResetData; + +static void option_rom_reset(void *_rrd) +{ + RomResetData *rrd = _rrd; + + cpu_physical_memory_write_rom(rrd->addr, rrd->data, rrd->size); +} + +static void option_rom_setup_reset(target_phys_addr_t addr, unsigned size) +{ + RomResetData *rrd = qemu_malloc(sizeof *rrd); + + rrd->data = qemu_malloc(size); + cpu_physical_memory_read(addr, rrd->data, size); + rrd->addr = addr; + rrd->size = size; + qemu_register_reset(option_rom_reset, 0, rrd); +} + +static void ioport80_write(void *opaque, uint32_t addr, uint32_t data) +{ +} + +/* MSDOS compatibility mode FPU exception support */ +static qemu_irq ferr_irq; +/* XXX: add IGNNE support */ +void cpu_set_ferr(CPUX86State *s) +{ + qemu_irq_raise(ferr_irq); +} + +static void ioportF0_write(void *opaque, uint32_t addr, uint32_t data) +{ + qemu_irq_lower(ferr_irq); +} + +/* TSC handling */ +uint64_t cpu_get_tsc(CPUX86State *env) +{ + /* Note: when using kqemu, it is more logical to return the host TSC + because kqemu does not trap the RDTSC instruction for + performance reasons */ +#ifdef CONFIG_KQEMU + if (env->kqemu_enabled) { + return cpu_get_real_ticks(); + } else +#endif + { + return cpu_get_ticks(); + } +} + +/* SMM support */ +void cpu_smm_update(CPUState *env) +{ + if (i440fx_state && env == first_cpu) + i440fx_set_smm(i440fx_state, (env->hflags >> HF_SMM_SHIFT) & 1); +} + + +/* IRQ handling */ +int cpu_get_pic_interrupt(CPUState *env) +{ + int intno; + + intno = apic_get_interrupt(env); + if (intno >= 0) { + /* set irq request if a PIC irq is still pending */ + /* XXX: improve that */ + pic_update_irq(isa_pic); + return intno; + } + /* read the irq from the PIC */ + if (!apic_accept_pic_intr(env)) + return -1; + + intno = pic_read_irq(isa_pic); + return intno; +} + +static void pic_irq_request(void *opaque, int irq, int level) +{ + CPUState *env = first_cpu; + + if (env->apic_state) { + while (env) { + if (apic_accept_pic_intr(env)) + apic_deliver_pic_intr(env, level); + env = env->next_cpu; + } + } else { + if (level) + cpu_interrupt(env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } +} + +/* PC cmos mappings */ + +#define REG_EQUIPMENT_BYTE 0x14 + +#ifndef CONFIG_ANDROID +static int cmos_get_fd_drive_type(int fd0) +{ + int val; + + switch (fd0) { + case 0: + /* 1.44 Mb 3"5 drive */ + val = 4; + break; + case 1: + /* 2.88 Mb 3"5 drive */ + val = 5; + break; + case 2: + /* 1.2 Mb 5"5 drive */ + val = 2; + break; + default: + val = 0; + break; + } + return val; +} +#endif + +static void cmos_init_hd(int type_ofs, int info_ofs, BlockDriverState *hd) +{ + RTCState *s = rtc_state; + int cylinders, heads, sectors; + bdrv_get_geometry_hint(hd, &cylinders, &heads, §ors); + rtc_set_memory(s, type_ofs, 47); + rtc_set_memory(s, info_ofs, cylinders); + rtc_set_memory(s, info_ofs + 1, cylinders >> 8); + rtc_set_memory(s, info_ofs + 2, heads); + rtc_set_memory(s, info_ofs + 3, 0xff); + rtc_set_memory(s, info_ofs + 4, 0xff); + rtc_set_memory(s, info_ofs + 5, 0xc0 | ((heads > 8) << 3)); + rtc_set_memory(s, info_ofs + 6, cylinders); + rtc_set_memory(s, info_ofs + 7, cylinders >> 8); + rtc_set_memory(s, info_ofs + 8, sectors); +} + +/* convert boot_device letter to something recognizable by the bios */ +static int boot_device2nibble(char boot_device) +{ + switch(boot_device) { + case 'a': + case 'b': + return 0x01; /* floppy boot */ + case 'c': + return 0x02; /* hard drive boot */ + case 'd': + return 0x03; /* CD-ROM boot */ + case 'n': + return 0x04; /* Network boot */ + } + return 0; +} + +/* copy/pasted from cmos_init, should be made a general function + and used there as well */ +static int pc_boot_set(void *opaque, const char *boot_device) +{ + Monitor *mon = cur_mon; +#define PC_MAX_BOOT_DEVICES 3 + RTCState *s = (RTCState *)opaque; + int nbds, bds[3] = { 0, }; + int i; + + nbds = strlen(boot_device); + if (nbds > PC_MAX_BOOT_DEVICES) { + monitor_printf(mon, "Too many boot devices for PC\n"); + return(1); + } + for (i = 0; i < nbds; i++) { + bds[i] = boot_device2nibble(boot_device[i]); + if (bds[i] == 0) { + monitor_printf(mon, "Invalid boot device for PC: '%c'\n", + boot_device[i]); + return(1); + } + } + rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]); + rtc_set_memory(s, 0x38, (bds[2] << 4)); + return(0); +} + +/* hd_table must contain 4 block drivers */ +static void cmos_init(ram_addr_t ram_size, ram_addr_t above_4g_mem_size, + const char *boot_device, BlockDriverState **hd_table) +{ + RTCState *s = rtc_state; + int nbds, bds[3] = { 0, }; + int val; +#ifndef CONFIG_ANDROID + int fd0, fd1, nb; +#endif + int i; + + /* various important CMOS locations needed by PC/Bochs bios */ + + /* memory size */ + val = 640; /* base memory in K */ + rtc_set_memory(s, 0x15, val); + rtc_set_memory(s, 0x16, val >> 8); + + val = (ram_size / 1024) - 1024; + if (val > 65535) + val = 65535; + rtc_set_memory(s, 0x17, val); + rtc_set_memory(s, 0x18, val >> 8); + rtc_set_memory(s, 0x30, val); + rtc_set_memory(s, 0x31, val >> 8); + + if (above_4g_mem_size) { + rtc_set_memory(s, 0x5b, (unsigned int)above_4g_mem_size >> 16); + rtc_set_memory(s, 0x5c, (unsigned int)above_4g_mem_size >> 24); + rtc_set_memory(s, 0x5d, (uint64_t)above_4g_mem_size >> 32); + } + + if (ram_size > (16 * 1024 * 1024)) + val = (ram_size / 65536) - ((16 * 1024 * 1024) / 65536); + else + val = 0; + if (val > 65535) + val = 65535; + rtc_set_memory(s, 0x34, val); + rtc_set_memory(s, 0x35, val >> 8); + + /* set the number of CPU */ + rtc_set_memory(s, 0x5f, smp_cpus - 1); + + /* set boot devices, and disable floppy signature check if requested */ +#define PC_MAX_BOOT_DEVICES 3 + nbds = strlen(boot_device); + if (nbds > PC_MAX_BOOT_DEVICES) { + fprintf(stderr, "Too many boot devices for PC\n"); + exit(1); + } + for (i = 0; i < nbds; i++) { + bds[i] = boot_device2nibble(boot_device[i]); + if (bds[i] == 0) { + fprintf(stderr, "Invalid boot device for PC: '%c'\n", + boot_device[i]); + exit(1); + } + } + rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]); + rtc_set_memory(s, 0x38, (bds[2] << 4) | (fd_bootchk ? 0x0 : 0x1)); + + /* floppy type */ + +#ifndef CONFIG_ANDROID + fd0 = fdctrl_get_drive_type(floppy_controller, 0); + fd1 = fdctrl_get_drive_type(floppy_controller, 1); + + val = (cmos_get_fd_drive_type(fd0) << 4) | cmos_get_fd_drive_type(fd1); + rtc_set_memory(s, 0x10, val); + + val = 0; + nb = 0; + if (fd0 < 3) + nb++; + if (fd1 < 3) + nb++; + switch (nb) { + case 0: + break; + case 1: + val |= 0x01; /* 1 drive, ready for boot */ + break; + case 2: + val |= 0x41; /* 2 drives, ready for boot */ + break; + } + val |= 0x02; /* FPU is there */ + val |= 0x04; /* PS/2 mouse installed */ + rtc_set_memory(s, REG_EQUIPMENT_BYTE, val); +#endif + + /* hard drives */ + + rtc_set_memory(s, 0x12, (hd_table[0] ? 0xf0 : 0) | (hd_table[1] ? 0x0f : 0)); + if (hd_table[0]) + cmos_init_hd(0x19, 0x1b, hd_table[0]); + if (hd_table[1]) + cmos_init_hd(0x1a, 0x24, hd_table[1]); + + val = 0; + for (i = 0; i < 4; i++) { + if (hd_table[i]) { + int cylinders, heads, sectors, translation; + /* NOTE: bdrv_get_geometry_hint() returns the physical + geometry. It is always such that: 1 <= sects <= 63, 1 + <= heads <= 16, 1 <= cylinders <= 16383. The BIOS + geometry can be different if a translation is done. */ + translation = bdrv_get_translation_hint(hd_table[i]); + if (translation == BIOS_ATA_TRANSLATION_AUTO) { + bdrv_get_geometry_hint(hd_table[i], &cylinders, &heads, §ors); + if (cylinders <= 1024 && heads <= 16 && sectors <= 63) { + /* No translation. */ + translation = 0; + } else { + /* LBA translation. */ + translation = 1; + } + } else { + translation--; + } + val |= translation << (i * 2); + } + } + rtc_set_memory(s, 0x39, val); +} + +void ioport_set_a20(int enable) +{ + /* XXX: send to all CPUs ? */ + cpu_x86_set_a20(first_cpu, enable); +} + +int ioport_get_a20(void) +{ + return ((first_cpu->a20_mask >> 20) & 1); +} + +static void ioport92_write(void *opaque, uint32_t addr, uint32_t val) +{ + ioport_set_a20((val >> 1) & 1); + /* XXX: bit 0 is fast reset */ +} + +static uint32_t ioport92_read(void *opaque, uint32_t addr) +{ + return ioport_get_a20() << 1; +} + +/***********************************************************/ +/* Bochs BIOS debug ports */ + +static void bochs_bios_write(void *opaque, uint32_t addr, uint32_t val) +{ + static const char shutdown_str[8] = "Shutdown"; + static int shutdown_index = 0; + + switch(addr) { + /* Bochs BIOS messages */ + case 0x400: + case 0x401: + fprintf(stderr, "BIOS panic at rombios.c, line %d\n", val); + exit(1); + case 0x402: + case 0x403: +#ifdef DEBUG_BIOS + fprintf(stderr, "%c", val); +#endif + break; + case 0x8900: + /* same as Bochs power off */ + if (val == shutdown_str[shutdown_index]) { + shutdown_index++; + if (shutdown_index == 8) { + shutdown_index = 0; + qemu_system_shutdown_request(); + } + } else { + shutdown_index = 0; + } + break; + + /* LGPL'ed VGA BIOS messages */ + case 0x501: + case 0x502: + fprintf(stderr, "VGA BIOS panic, line %d\n", val); + exit(1); + case 0x500: + case 0x503: +#ifdef DEBUG_BIOS + fprintf(stderr, "%c", val); +#endif + break; + } +} + +extern uint64_t node_cpumask[MAX_NODES]; + +static void bochs_bios_init(void) +{ + void *fw_cfg; + uint8_t *smbios_table; + size_t smbios_len; + uint64_t *numa_fw_cfg; + int i, j; + + register_ioport_write(0x400, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x401, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x402, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x403, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x8900, 1, 1, bochs_bios_write, NULL); + + register_ioport_write(0x501, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x502, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x500, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x503, 1, 1, bochs_bios_write, NULL); + + fw_cfg = fw_cfg_init(BIOS_CFG_IOPORT, BIOS_CFG_IOPORT + 1, 0, 0); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size); +#ifndef CONFIG_ANDROID + fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES, (uint8_t *)acpi_tables, + acpi_tables_len); +#endif + smbios_table = smbios_get_table(&smbios_len); + if (smbios_table) + fw_cfg_add_bytes(fw_cfg, FW_CFG_SMBIOS_ENTRIES, + smbios_table, smbios_len); + + /* allocate memory for the NUMA channel: one (64bit) word for the number + * of nodes, one word for each VCPU->node and one word for each node to + * hold the amount of memory. + */ + numa_fw_cfg = qemu_mallocz((1 + smp_cpus + nb_numa_nodes) * 8); + numa_fw_cfg[0] = cpu_to_le64(nb_numa_nodes); + for (i = 0; i < smp_cpus; i++) { + for (j = 0; j < nb_numa_nodes; j++) { + if (node_cpumask[j] & (1 << i)) { + numa_fw_cfg[i + 1] = cpu_to_le64(j); + break; + } + } + } + for (i = 0; i < nb_numa_nodes; i++) { + numa_fw_cfg[smp_cpus + 1 + i] = cpu_to_le64(node_mem[i]); + } + fw_cfg_add_bytes(fw_cfg, FW_CFG_NUMA, (uint8_t *)numa_fw_cfg, + (1 + smp_cpus + nb_numa_nodes) * 8); +} + +/* Generate an initial boot sector which sets state and jump to + a specified vector */ +static void generate_bootsect(target_phys_addr_t option_rom, + uint32_t gpr[8], uint16_t segs[6], uint16_t ip) +{ + uint8_t rom[512], *p, *reloc; + uint8_t sum; + int i; + + memset(rom, 0, sizeof(rom)); + + p = rom; + /* Make sure we have an option rom signature */ + *p++ = 0x55; + *p++ = 0xaa; + + /* ROM size in sectors*/ + *p++ = 1; + + /* Hook int19 */ + + *p++ = 0x50; /* push ax */ + *p++ = 0x1e; /* push ds */ + *p++ = 0x31; *p++ = 0xc0; /* xor ax, ax */ + *p++ = 0x8e; *p++ = 0xd8; /* mov ax, ds */ + + *p++ = 0xc7; *p++ = 0x06; /* movvw _start,0x64 */ + *p++ = 0x64; *p++ = 0x00; + reloc = p; + *p++ = 0x00; *p++ = 0x00; + + *p++ = 0x8c; *p++ = 0x0e; /* mov cs,0x66 */ + *p++ = 0x66; *p++ = 0x00; + + *p++ = 0x1f; /* pop ds */ + *p++ = 0x58; /* pop ax */ + *p++ = 0xcb; /* lret */ + + /* Actual code */ + *reloc = (p - rom); + + *p++ = 0xfa; /* CLI */ + *p++ = 0xfc; /* CLD */ + + for (i = 0; i < 6; i++) { + if (i == 1) /* Skip CS */ + continue; + + *p++ = 0xb8; /* MOV AX,imm16 */ + *p++ = segs[i]; + *p++ = segs[i] >> 8; + *p++ = 0x8e; /* MOV ,AX */ + *p++ = 0xc0 + (i << 3); + } + + for (i = 0; i < 8; i++) { + *p++ = 0x66; /* 32-bit operand size */ + *p++ = 0xb8 + i; /* MOV ,imm32 */ + *p++ = gpr[i]; + *p++ = gpr[i] >> 8; + *p++ = gpr[i] >> 16; + *p++ = gpr[i] >> 24; + } + + *p++ = 0xea; /* JMP FAR */ + *p++ = ip; /* IP */ + *p++ = ip >> 8; + *p++ = segs[1]; /* CS */ + *p++ = segs[1] >> 8; + + /* sign rom */ + sum = 0; + for (i = 0; i < (sizeof(rom) - 1); i++) + sum += rom[i]; + rom[sizeof(rom) - 1] = -sum; + + cpu_physical_memory_write_rom(option_rom, rom, sizeof(rom)); + option_rom_setup_reset(option_rom, sizeof (rom)); +} + +static long get_file_size(FILE *f) +{ + long where, size; + + /* XXX: on Unix systems, using fstat() probably makes more sense */ + + where = ftell(f); + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, where, SEEK_SET); + + return size; +} + +static void load_linux(target_phys_addr_t option_rom, + const char *kernel_filename, + const char *initrd_filename, + const char *kernel_cmdline, + target_phys_addr_t max_ram_size) +{ + uint16_t protocol; + uint32_t gpr[8]; + uint16_t seg[6]; + uint16_t real_seg; + int setup_size, kernel_size, initrd_size = 0, cmdline_size; + uint32_t initrd_max; + uint8_t header[1024]; + target_phys_addr_t real_addr, prot_addr, cmdline_addr, initrd_addr = 0; + FILE *f, *fi; + + /* Align to 16 bytes as a paranoia measure */ + cmdline_size = (strlen(kernel_cmdline)+16) & ~15; + + /* load the kernel header */ + f = fopen(kernel_filename, "rb"); + if (!f || !(kernel_size = get_file_size(f)) || + fread(header, 1, 1024, f) != 1024) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + kernel_filename); + exit(1); + } + + /* kernel protocol version */ +#if 0 + fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202)); +#endif + if (ldl_p(header+0x202) == 0x53726448) + protocol = lduw_p(header+0x206); + else + protocol = 0; + + if (protocol < 0x200 || !(header[0x211] & 0x01)) { + /* Low kernel */ + real_addr = 0x90000; + cmdline_addr = 0x9a000 - cmdline_size; + prot_addr = 0x10000; + } else if (protocol < 0x202) { + /* High but ancient kernel */ + real_addr = 0x90000; + cmdline_addr = 0x9a000 - cmdline_size; + prot_addr = 0x100000; + } else { + /* High and recent kernel */ + real_addr = 0x10000; + cmdline_addr = 0x20000; + prot_addr = 0x100000; + } + +#if 0 + fprintf(stderr, + "qemu: real_addr = 0x" TARGET_FMT_plx "\n" + "qemu: cmdline_addr = 0x" TARGET_FMT_plx "\n" + "qemu: prot_addr = 0x" TARGET_FMT_plx "\n", + real_addr, + cmdline_addr, + prot_addr); +#endif + + /* highest address for loading the initrd */ + if (protocol >= 0x203) + initrd_max = ldl_p(header+0x22c); + else + initrd_max = 0x37ffffff; + + if (initrd_max >= max_ram_size-ACPI_DATA_SIZE) + initrd_max = max_ram_size-ACPI_DATA_SIZE-1; + + /* kernel command line */ + pstrcpy_targphys(cmdline_addr, 4096, kernel_cmdline); + + if (protocol >= 0x202) { + stl_p(header+0x228, cmdline_addr); + } else { + stw_p(header+0x20, 0xA33F); + stw_p(header+0x22, cmdline_addr-real_addr); + } + + /* loader type */ + /* High nybble = B reserved for Qemu; low nybble is revision number. + If this code is substantially changed, you may want to consider + incrementing the revision. */ + if (protocol >= 0x200) + header[0x210] = 0xB0; + + /* heap */ + if (protocol >= 0x201) { + header[0x211] |= 0x80; /* CAN_USE_HEAP */ + stw_p(header+0x224, cmdline_addr-real_addr-0x200); + } + + /* load initrd */ + if (initrd_filename) { + if (protocol < 0x200) { + fprintf(stderr, "qemu: linux kernel too old to load a ram disk %s, %s, %s\n", + kernel_filename, initrd_filename, kernel_cmdline); + exit(1); + } + + fi = fopen(initrd_filename, "rb"); + if (!fi) { + fprintf(stderr, "qemu: could not load initial ram disk '%s'\n", + initrd_filename); + exit(1); + } + + initrd_size = get_file_size(fi); + initrd_addr = (initrd_max-initrd_size) & ~4095; + + if (!fread_targphys_ok(initrd_addr, initrd_size, fi)) { + fprintf(stderr, "qemu: read error on initial ram disk '%s'\n", + initrd_filename); + exit(1); + } + fclose(fi); + + stl_p(header+0x218, initrd_addr); + stl_p(header+0x21c, initrd_size); + } + + /* store the finalized header and load the rest of the kernel */ + cpu_physical_memory_write(real_addr, header, 1024); + + setup_size = header[0x1f1]; + if (setup_size == 0) + setup_size = 4; + + setup_size = (setup_size+1)*512; + kernel_size -= setup_size; /* Size of protected-mode code */ + + if (!fread_targphys_ok(real_addr+1024, setup_size-1024, f) || + !fread_targphys_ok(prot_addr, kernel_size, f)) { + fprintf(stderr, "qemu: read error on kernel '%s'\n", + kernel_filename); + exit(1); + } + fclose(f); + + /* generate bootsector to set up the initial register state */ + real_seg = real_addr >> 4; + seg[0] = seg[2] = seg[3] = seg[4] = seg[4] = real_seg; + seg[1] = real_seg+0x20; /* CS */ + memset(gpr, 0, sizeof gpr); + gpr[4] = cmdline_addr-real_addr-16; /* SP (-16 is paranoia) */ + + option_rom_setup_reset(real_addr, setup_size); + option_rom_setup_reset(prot_addr, kernel_size); + option_rom_setup_reset(cmdline_addr, cmdline_size); + if (initrd_filename) + option_rom_setup_reset(initrd_addr, initrd_size); + + generate_bootsect(option_rom, gpr, seg, 0); +} + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + cpu_reset(env); +} + +static const int ide_iobase[2] = { 0x1f0, 0x170 }; +static const int ide_iobase2[2] = { 0x3f6, 0x376 }; +static const int ide_irq[2] = { 14, 15 }; + +#define NE2000_NB_MAX 6 + +static int ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360, 0x280, 0x380 }; +static int ne2000_irq[NE2000_NB_MAX] = { 9, 10, 11, 3, 4, 5 }; + +/* static int serial_io[MAX_SERIAL_PORTS] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 }; +static int serial_irq[MAX_SERIAL_PORTS] = { 4, 3, 4, 3 }; + +static int parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc }; +static int parallel_irq[MAX_PARALLEL_PORTS] = { 7, 7, 7 }; */ + +#ifdef HAS_AUDIO +static void audio_init (PCIBus *pci_bus, qemu_irq *pic) +{ + struct soundhw *c; + + for (c = soundhw; c->name; ++c) { + if (c->enabled) { + if (c->isa) { + c->init.init_isa(pic); + } else { + if (pci_bus) { + c->init.init_pci(pci_bus); + } + } + } + } +} +#endif + +static void pc_init_ne2k_isa(NICInfo *nd, qemu_irq *pic) +{ + static int nb_ne2k = 0; + + if (nb_ne2k == NE2000_NB_MAX) + return; + isa_ne2000_init(ne2000_io[nb_ne2k], pic[ne2000_irq[nb_ne2k]], nd); + nb_ne2k++; +} + +static int load_option_rom(const char *oprom, target_phys_addr_t start, + target_phys_addr_t end) +{ + int size; + char *filename; + + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, oprom); + if (filename) { + size = get_image_size(filename); + if (size > 0 && start + size > end) { + fprintf(stderr, "Not enough space to load option rom '%s'\n", + oprom); + exit(1); + } + size = load_image_targphys(filename, start, end - start); + qemu_free(filename); + } else { + size = -1; + } + if (size < 0) { + fprintf(stderr, "Could not load option rom '%s'\n", oprom); + exit(1); + } + /* Round up optiom rom size to the next 2k boundary */ + size = (size + 2047) & ~2047; + option_rom_setup_reset(start, size); + return size; +} + +int cpu_is_bsp(CPUState *env) +{ + return env->cpuid_apic_id == 0; +} + +static struct goldfish_device event0_device = { + .name = "goldfish_events", + .id = 0, + .size = 0x1000, + .irq_count = 1 +}; + +static struct goldfish_device nand_device = { + .name = "goldfish_nand", + .id = 0, + .size = 0x1000 +}; + +void goldfish_memlog_init(uint32_t base); + +/* PC hardware initialisation */ +static void pc_init1(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, + int pci_enabled, const char *cpu_model) +{ + char *filename; + int ret, linux_boot, i; + ram_addr_t ram_addr, bios_offset, option_rom_offset; + ram_addr_t below_4g_mem_size, above_4g_mem_size = 0; + int bios_size, isa_bios_size, oprom_area_size; + PCIBus *pci_bus; + int piix3_devfn = -1; + CPUState *env; + qemu_irq *cpu_irq; + qemu_irq *i8259; +#ifndef CONFIG_ANDROID + int index; +#endif + BlockDriverState *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; +#ifndef CONFIG_ANDROID + BlockDriverState *fd[MAX_FD]; +#endif + int using_vga = cirrus_vga_enabled || std_vga_enabled || vmsvga_enabled; + + if (ram_size >= 0xe0000000 ) { + above_4g_mem_size = ram_size - 0xe0000000; + below_4g_mem_size = 0xe0000000; + } else { + below_4g_mem_size = ram_size; + } + + linux_boot = (kernel_filename != NULL); + + /* init CPUs */ + if (cpu_model == NULL) { +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif + } + + for(i = 0; i < smp_cpus; i++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find x86 CPU definition\n"); + exit(1); + } + if ((env->cpuid_features & CPUID_APIC) || smp_cpus > 1) { + env->cpuid_apic_id = env->cpu_index; + apic_init(env); + } + qemu_register_reset(main_cpu_reset, 0, env); + } +#ifndef CONFIG_ANDROID + vmport_init(); +#endif + /* allocate RAM */ + ram_addr = qemu_ram_alloc(0xa0000); + cpu_register_physical_memory(0, 0xa0000, ram_addr); + + /* Allocate, even though we won't register, so we don't break the + * phys_ram_base + PA assumption. This range includes vga (0xa0000 - 0xc0000), + * and some bios areas, which will be registered later + */ + ram_addr = qemu_ram_alloc(0x100000 - 0xa0000); + ram_addr = qemu_ram_alloc(below_4g_mem_size - 0x100000); + cpu_register_physical_memory(0x100000, + below_4g_mem_size - 0x100000, + ram_addr); + + /* above 4giga memory allocation */ + if (above_4g_mem_size > 0) { +#if TARGET_PHYS_ADDR_BITS == 32 + hw_error("To much RAM for 32-bit physical address"); +#else + ram_addr = qemu_ram_alloc(above_4g_mem_size); + cpu_register_physical_memory(0x100000000ULL, + above_4g_mem_size, + ram_addr); +#endif + } + + + /* BIOS load */ + if (bios_name == NULL) + bios_name = BIOS_FILENAME; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (filename) { + bios_size = get_image_size(filename); + } else { + bios_size = -1; + } + if (bios_size <= 0 || + (bios_size % 65536) != 0) { + goto bios_error; + } + bios_offset = qemu_ram_alloc(bios_size); + ret = load_image(filename, qemu_get_ram_ptr(bios_offset)); + if (ret != bios_size) { + bios_error: + fprintf(stderr, "qemu: could not load PC BIOS '%s'\n", bios_name); + exit(1); + } + if (filename) { + qemu_free(filename); + } + /* map the last 128KB of the BIOS in ISA space */ + isa_bios_size = bios_size; + if (isa_bios_size > (128 * 1024)) + isa_bios_size = 128 * 1024; + cpu_register_physical_memory(0x100000 - isa_bios_size, + isa_bios_size, + (bios_offset + bios_size - isa_bios_size) | IO_MEM_ROM); + + + + option_rom_offset = qemu_ram_alloc(0x20000); + oprom_area_size = 0; + cpu_register_physical_memory(0xc0000, 0x20000, option_rom_offset); + + if (using_vga) { + const char *vgabios_filename; + /* VGA BIOS load */ + if (cirrus_vga_enabled) { + vgabios_filename = VGABIOS_CIRRUS_FILENAME; + } else { + vgabios_filename = VGABIOS_FILENAME; + } + oprom_area_size = load_option_rom(vgabios_filename, 0xc0000, 0xe0000); + } + /* Although video roms can grow larger than 0x8000, the area between + * 0xc0000 - 0xc8000 is reserved for them. It means we won't be looking + * for any other kind of option rom inside this area */ + if (oprom_area_size < 0x8000) + oprom_area_size = 0x8000; + + if (linux_boot) { + load_linux(0xc0000 + oprom_area_size, + kernel_filename, initrd_filename, kernel_cmdline, below_4g_mem_size); + oprom_area_size += 2048; + } + + for (i = 0; i < nb_option_roms; i++) { + oprom_area_size += load_option_rom(option_rom[i], + 0xc0000 + oprom_area_size, 0xe0000); + } + + /* map all the bios at the top of memory */ + cpu_register_physical_memory((uint32_t)(-bios_size), + bios_size, bios_offset | IO_MEM_ROM); + + bochs_bios_init(); + + cpu_irq = qemu_allocate_irqs(pic_irq_request, NULL, 1); + i8259 = i8259_init(cpu_irq[0]); + ferr_irq = i8259[13]; + +#define IRQ_PDEV_BUS 4 + goldfish_device_init(i8259, 0xff010000, 0x7f0000, 5, 5); + goldfish_device_bus_init(0xff001000, IRQ_PDEV_BUS); + + if (android_hw->hw_battery) + goldfish_battery_init(); + + goldfish_memlog_init(0); + +#ifdef CONFIG_NAND + goldfish_add_device_no_io(&nand_device); + nand_dev_init(nand_device.base); +#endif + + { + DriveInfo* info = drive_get( IF_IDE, 0, 0 ); + if (info != NULL) { + goldfish_mmc_init(0xff005000, 0, info->bdrv); + } + } + + if (pci_enabled) { + pci_bus = i440fx_init(&i440fx_state, i8259); + piix3_devfn = piix3_init(pci_bus, -1); + } else { + pci_bus = NULL; + } + + /* init basic PC hardware */ + register_ioport_write(0x80, 1, 1, ioport80_write, NULL); + + register_ioport_write(0xf0, 1, 1, ioportF0_write, NULL); + +#ifndef CONFIG_ANDROID + if (cirrus_vga_enabled) { + if (pci_enabled) { + pci_cirrus_vga_init(pci_bus); + } else { + isa_cirrus_vga_init(); + } + } else if (vmsvga_enabled) { + if (pci_enabled) + pci_vmsvga_init(pci_bus); + else + fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__); + } else if (std_vga_enabled) { + if (pci_enabled) { + pci_vga_init(pci_bus, 0, 0); + } else { + isa_vga_init(); + } + } +#endif + + rtc_state = rtc_init(0x70, i8259[8], 2000); + + qemu_register_boot_set(pc_boot_set, rtc_state); + + register_ioport_read(0x92, 1, 1, ioport92_read, NULL); + register_ioport_write(0x92, 1, 1, ioport92_write, NULL); + + if (pci_enabled) { + ioapic = ioapic_init(); + } + pit = pit_init(0x40, i8259[0]); + +#ifndef CONFIG_ANDROID + pcspk_init(pit); + + if (!no_hpet) { + hpet_init(i8259); + } +#endif + + if (pci_enabled) { + pic_set_alt_irq_func(isa_pic, ioapic_set_irq, ioapic); + } + + goldfish_tty_add(serial_hds[0], 0, 0, 0); + for(i = 1; i < MAX_SERIAL_PORTS; i++) { + if(serial_hds[i]) { + goldfish_tty_add(serial_hds[i], i, 0, 0); + } + } + +#ifndef CONFIG_ANDROID + for(i = 0; i < MAX_SERIAL_PORTS; i++) { + if (serial_hds[i]) { + serial_init(serial_io[i], i8259[serial_irq[i]], 115200, + serial_hds[i]); + } + } + + for(i = 0; i < MAX_PARALLEL_PORTS; i++) { + if (parallel_hds[i]) { + parallel_init(parallel_io[i], i8259[parallel_irq[i]], + parallel_hds[i]); + } + } +#endif + + watchdog_pc_init(pci_bus); + + for(i = 0; i < nb_nics; i++) { + NICInfo *nd = &nd_table[i]; + + if (!pci_enabled || (nd->model && strcmp(nd->model, "ne2k_isa") == 0)) + pc_init_ne2k_isa(nd, i8259); + else + pci_nic_init(pci_bus, nd, -1, "ne2k_pci"); + } + +#ifdef CONFIG_ANDROID + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) + hd[i] = NULL; +#else + qemu_system_hot_add_init(); + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + index = drive_get_index(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + if (index != -1) + hd[i] = drives_table[index].bdrv; + else + hd[i] = NULL; + } + + if (pci_enabled) { + pci_piix3_ide_init(pci_bus, hd, piix3_devfn + 1, i8259); + } else { + for(i = 0; i < MAX_IDE_BUS; i++) { + isa_ide_init(ide_iobase[i], ide_iobase2[i], i8259[ide_irq[i]], + hd[MAX_IDE_DEVS * i], hd[MAX_IDE_DEVS * i + 1]); + } + } +#endif + + i8042_init(i8259[1], i8259[12], 0x60); + DMA_init(0); + + goldfish_fb_init(0); + + goldfish_add_device_no_io(&event0_device); + events_dev_init(event0_device.base, i8259[event0_device.irq]); + +#ifdef HAS_AUDIO + audio_init(pci_enabled ? pci_bus : NULL, i8259); +#endif + +#ifndef CONFIG_ANDROID + for(i = 0; i < MAX_FD; i++) { + index = drive_get_index(IF_FLOPPY, 0, i); + if (index != -1) + fd[i] = drives_table[index].bdrv; + else + fd[i] = NULL; + } + + floppy_controller = fdctrl_init(i8259[6], 2, 0, 0x3f0, fd); +#endif + + cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, hd); + +#ifndef CONFIG_ANDROID + if (pci_enabled && usb_enabled) { + usb_uhci_piix3_init(pci_bus, piix3_devfn + 2); + } + + if (pci_enabled && acpi_enabled) { + uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */ + i2c_bus *smbus; + + /* TODO: Populate SPD eeprom data. */ + smbus = piix4_pm_init(pci_bus, piix3_devfn + 3, 0xb100, i8259[9]); + for (i = 0; i < 8; i++) { + DeviceState *eeprom; + eeprom = qdev_create((BusState *)smbus, "smbus-eeprom"); + qdev_set_prop_int(eeprom, "address", 0x50 + i); + qdev_set_prop_ptr(eeprom, "data", eeprom_buf + (i * 256)); + qdev_init(eeprom); + } + } +#endif + + if (i440fx_state) { + i440fx_init_memory_mappings(i440fx_state); + } + + if (pci_enabled) { + int max_bus; + int bus; + + max_bus = drive_get_max_bus(IF_SCSI); + for (bus = 0; bus <= max_bus; bus++) { + pci_create_simple(pci_bus, -1, "lsi53c895a"); + } + } +#ifndef CONFIG_ANDROID + /* Add virtio block devices */ + if (pci_enabled) { + int index; + int unit_id = 0; + + while ((index = drive_get_index(IF_VIRTIO, 0, unit_id)) != -1) { + pci_create_simple(pci_bus, -1, "virtio-blk-pci"); + unit_id++; + } + } + + /* Add virtio balloon device */ + if (pci_enabled && !no_virtio_balloon) { + pci_create_simple(pci_bus, -1, "virtio-balloon-pci"); + } + + /* Add virtio console devices */ + if (pci_enabled) { + for(i = 0; i < MAX_VIRTIO_CONSOLES; i++) { + if (virtcon_hds[i]) { + pci_create_simple(pci_bus, -1, "virtio-console-pci"); + } + } + } +#endif +} + +static void pc_init_pci(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + pc_init1(ram_size, boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, 1, cpu_model); +} + +static void pc_init_isa(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + pc_init1(ram_size, boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, 0, cpu_model); +} + +/* set CMOS shutdown status register (index 0xF) as S3_resume(0xFE) + BIOS will read it and start S3 resume at POST Entry */ +void cmos_set_s3_resume(void) +{ + if (rtc_state) + rtc_set_memory(rtc_state, 0xF, 0xFE); +} + +static QEMUMachine pc_machine = { + .name = "pc", + .desc = "Standard PC", + .init = pc_init_pci, + .max_cpus = 255, + .is_default = 1, +}; + +static QEMUMachine isapc_machine = { + .name = "isapc", + .desc = "ISA-only PC", + .init = pc_init_isa, + .max_cpus = 1, +}; + +static void pc_machine_init(void) +{ + qemu_register_machine(&pc_machine); + qemu_register_machine(&isapc_machine); +} + +machine_init(pc_machine_init); diff --git a/hw/pckbd.c b/hw/pckbd.c new file mode 100644 index 0000000..e1c6d40 --- /dev/null +++ b/hw/pckbd.c @@ -0,0 +1,446 @@ +/* + * QEMU PC keyboard emulation + * + * Copyright (c) 2003 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 "hw.h" +#include "isa.h" +#include "pc.h" +#include "ps2.h" +#include "sysemu.h" + +/* debug PC keyboard */ +//#define DEBUG_KBD + +/* Keyboard Controller Commands */ +#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */ +#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */ +#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */ +#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */ +#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */ +#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */ +#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */ +#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */ +#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */ +#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */ +#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */ +#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */ +#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */ +#define KBD_CCMD_WRITE_OBUF 0xD2 +#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if + initiated by the auxiliary device */ +#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */ +#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */ +#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */ +#define KBD_CCMD_RESET 0xFE + +/* Keyboard Commands */ +#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */ +#define KBD_CMD_ECHO 0xEE +#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */ +#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */ +#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */ +#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */ +#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */ +#define KBD_CMD_RESET 0xFF /* Reset */ + +/* Keyboard Replies */ +#define KBD_REPLY_POR 0xAA /* Power on reset */ +#define KBD_REPLY_ACK 0xFA /* Command ACK */ +#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */ + +/* Status Register Bits */ +#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */ +#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */ +#define KBD_STAT_SELFTEST 0x04 /* Self test successful */ +#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */ +#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */ +#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */ +#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */ +#define KBD_STAT_PERR 0x80 /* Parity error */ + +/* Controller Mode Register Bits */ +#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */ +#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */ +#define KBD_MODE_SYS 0x04 /* The system flag (?) */ +#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */ +#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */ +#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */ +#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */ +#define KBD_MODE_RFU 0x80 + +/* Mouse Commands */ +#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */ +#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */ +#define AUX_SET_RES 0xE8 /* Set resolution */ +#define AUX_GET_SCALE 0xE9 /* Get scaling factor */ +#define AUX_SET_STREAM 0xEA /* Set stream mode */ +#define AUX_POLL 0xEB /* Poll */ +#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */ +#define AUX_SET_WRAP 0xEE /* Set wrap mode */ +#define AUX_SET_REMOTE 0xF0 /* Set remote mode */ +#define AUX_GET_TYPE 0xF2 /* Get type */ +#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */ +#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */ +#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */ +#define AUX_SET_DEFAULT 0xF6 +#define AUX_RESET 0xFF /* Reset aux device */ +#define AUX_ACK 0xFA /* Command byte ACK. */ + +#define MOUSE_STATUS_REMOTE 0x40 +#define MOUSE_STATUS_ENABLED 0x20 +#define MOUSE_STATUS_SCALE21 0x10 + +#define KBD_PENDING_KBD 1 +#define KBD_PENDING_AUX 2 + +typedef struct KBDState { + uint8_t write_cmd; /* if non zero, write data to port 60 is expected */ + uint8_t status; + uint8_t mode; + /* Bitmask of devices with data available. */ + uint8_t pending; + void *kbd; + void *mouse; + + qemu_irq irq_kbd; + qemu_irq irq_mouse; + target_phys_addr_t mask; +} KBDState; + +static KBDState kbd_state; + +/* update irq and KBD_STAT_[MOUSE_]OBF */ +/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be + incorrect, but it avoids having to simulate exact delays */ +static void kbd_update_irq(KBDState *s) +{ + int irq_kbd_level, irq_mouse_level; + + irq_kbd_level = 0; + irq_mouse_level = 0; + s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF); + if (s->pending) { + s->status |= KBD_STAT_OBF; + /* kbd data takes priority over aux data. */ + if (s->pending == KBD_PENDING_AUX) { + s->status |= KBD_STAT_MOUSE_OBF; + if (s->mode & KBD_MODE_MOUSE_INT) + irq_mouse_level = 1; + } else { + if ((s->mode & KBD_MODE_KBD_INT) && + !(s->mode & KBD_MODE_DISABLE_KBD)) + irq_kbd_level = 1; + } + } + qemu_set_irq(s->irq_kbd, irq_kbd_level); + qemu_set_irq(s->irq_mouse, irq_mouse_level); +} + +static void kbd_update_kbd_irq(void *opaque, int level) +{ + KBDState *s = (KBDState *)opaque; + + if (level) + s->pending |= KBD_PENDING_KBD; + else + s->pending &= ~KBD_PENDING_KBD; + kbd_update_irq(s); +} + +static void kbd_update_aux_irq(void *opaque, int level) +{ + KBDState *s = (KBDState *)opaque; + + if (level) + s->pending |= KBD_PENDING_AUX; + else + s->pending &= ~KBD_PENDING_AUX; + kbd_update_irq(s); +} + +static uint32_t kbd_read_status(void *opaque, uint32_t addr) +{ + KBDState *s = opaque; + int val; + val = s->status; +#if defined(DEBUG_KBD) + printf("kbd: read status=0x%02x\n", val); +#endif + return val; +} + +static void kbd_queue(KBDState *s, int b, int aux) +{ + if (aux) + ps2_queue(s->mouse, b); + else + ps2_queue(s->kbd, b); +} + +static void kbd_write_command(void *opaque, uint32_t addr, uint32_t val) +{ + KBDState *s = opaque; + +#ifdef DEBUG_KBD + printf("kbd: write cmd=0x%02x\n", val); +#endif + switch(val) { + case KBD_CCMD_READ_MODE: + kbd_queue(s, s->mode, 0); + break; + case KBD_CCMD_WRITE_MODE: + case KBD_CCMD_WRITE_OBUF: + case KBD_CCMD_WRITE_AUX_OBUF: + case KBD_CCMD_WRITE_MOUSE: + case KBD_CCMD_WRITE_OUTPORT: + s->write_cmd = val; + break; + case KBD_CCMD_MOUSE_DISABLE: + s->mode |= KBD_MODE_DISABLE_MOUSE; + break; + case KBD_CCMD_MOUSE_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_MOUSE; + break; + case KBD_CCMD_TEST_MOUSE: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_SELF_TEST: + s->status |= KBD_STAT_SELFTEST; + kbd_queue(s, 0x55, 0); + break; + case KBD_CCMD_KBD_TEST: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_KBD_DISABLE: + s->mode |= KBD_MODE_DISABLE_KBD; + kbd_update_irq(s); + break; + case KBD_CCMD_KBD_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_KBD; + kbd_update_irq(s); + break; + case KBD_CCMD_READ_INPORT: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_READ_OUTPORT: + /* XXX: check that */ +#ifdef TARGET_I386 + val = 0x01 | (ioport_get_a20() << 1); +#else + val = 0x01; +#endif + if (s->status & KBD_STAT_OBF) + val |= 0x10; + if (s->status & KBD_STAT_MOUSE_OBF) + val |= 0x20; + kbd_queue(s, val, 0); + break; +#ifdef TARGET_I386 + case KBD_CCMD_ENABLE_A20: + ioport_set_a20(1); + break; + case KBD_CCMD_DISABLE_A20: + ioport_set_a20(0); + break; +#endif + case KBD_CCMD_RESET: + qemu_system_reset_request(); + break; + case 0xff: + /* ignore that - I don't know what is its use */ + break; + default: + fprintf(stderr, "qemu: unsupported keyboard cmd=0x%02x\n", val); + break; + } +} + +static uint32_t kbd_read_data(void *opaque, uint32_t addr) +{ + KBDState *s = opaque; + uint32_t val; + + if (s->pending == KBD_PENDING_AUX) + val = ps2_read_data(s->mouse); + else + val = ps2_read_data(s->kbd); + +#if defined(DEBUG_KBD) + printf("kbd: read data=0x%02x\n", val); +#endif + return val; +} + +static void kbd_write_data(void *opaque, uint32_t addr, uint32_t val) +{ + KBDState *s = opaque; + +#ifdef DEBUG_KBD + printf("kbd: write data=0x%02x\n", val); +#endif + + switch(s->write_cmd) { + case 0: + ps2_write_keyboard(s->kbd, val); + break; + case KBD_CCMD_WRITE_MODE: + s->mode = val; + ps2_keyboard_set_translation(s->kbd, (s->mode & KBD_MODE_KCC) != 0); + /* ??? */ + kbd_update_irq(s); + break; + case KBD_CCMD_WRITE_OBUF: + kbd_queue(s, val, 0); + break; + case KBD_CCMD_WRITE_AUX_OBUF: + kbd_queue(s, val, 1); + break; + case KBD_CCMD_WRITE_OUTPORT: +#ifdef TARGET_I386 + ioport_set_a20((val >> 1) & 1); +#endif + if (!(val & 1)) { + qemu_system_reset_request(); + } + break; + case KBD_CCMD_WRITE_MOUSE: + ps2_write_mouse(s->mouse, val); + break; + default: + break; + } + s->write_cmd = 0; +} + +static void kbd_reset(void *opaque) +{ + KBDState *s = opaque; + + s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT; + s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED; +} + +static void kbd_save(QEMUFile* f, void* opaque) +{ + KBDState *s = (KBDState*)opaque; + + qemu_put_8s(f, &s->write_cmd); + qemu_put_8s(f, &s->status); + qemu_put_8s(f, &s->mode); + qemu_put_8s(f, &s->pending); +} + +static int kbd_load(QEMUFile* f, void* opaque, int version_id) +{ + KBDState *s = (KBDState*)opaque; + + if (version_id != 3) + return -EINVAL; + qemu_get_8s(f, &s->write_cmd); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->mode); + qemu_get_8s(f, &s->pending); + return 0; +} + +void i8042_init(qemu_irq kbd_irq, qemu_irq mouse_irq, uint32_t io_base) +{ + KBDState *s = &kbd_state; + + s->irq_kbd = kbd_irq; + s->irq_mouse = mouse_irq; + + kbd_reset(s); + register_savevm("pckbd", 0, 3, kbd_save, kbd_load, s); + register_ioport_read(io_base, 1, 1, kbd_read_data, s); + register_ioport_write(io_base, 1, 1, kbd_write_data, s); + register_ioport_read(io_base + 4, 1, 1, kbd_read_status, s); + register_ioport_write(io_base + 4, 1, 1, kbd_write_command, s); + + s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s); + s->mouse = ps2_mouse_init(kbd_update_aux_irq, s); +#ifndef CONFIG_ANDROID +#ifdef TARGET_I386 + vmmouse_init(s->mouse); +#endif +#endif + qemu_register_reset(kbd_reset, 0, s); +} + +/* Memory mapped interface */ +static uint32_t kbd_mm_readb (void *opaque, target_phys_addr_t addr) +{ + KBDState *s = opaque; + + if (addr & s->mask) + return kbd_read_status(s, 0) & 0xff; + else + return kbd_read_data(s, 0) & 0xff; +} + +static void kbd_mm_writeb (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + KBDState *s = opaque; + + if (addr & s->mask) + kbd_write_command(s, 0, value & 0xff); + else + kbd_write_data(s, 0, value & 0xff); +} + +static CPUReadMemoryFunc *kbd_mm_read[] = { + &kbd_mm_readb, + &kbd_mm_readb, + &kbd_mm_readb, +}; + +static CPUWriteMemoryFunc *kbd_mm_write[] = { + &kbd_mm_writeb, + &kbd_mm_writeb, + &kbd_mm_writeb, +}; + +void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq, + target_phys_addr_t base, ram_addr_t size, + target_phys_addr_t mask) +{ + KBDState *s = &kbd_state; + int s_io_memory; + + s->irq_kbd = kbd_irq; + s->irq_mouse = mouse_irq; + s->mask = mask; + + kbd_reset(s); + register_savevm("pckbd", 0, 3, kbd_save, kbd_load, s); + s_io_memory = cpu_register_io_memory(kbd_mm_read, kbd_mm_write, s); + cpu_register_physical_memory(base, size, s_io_memory); + + s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s); + s->mouse = ps2_mouse_init(kbd_update_aux_irq, s); +#ifndef CONFIG_ANDROID +#ifdef TARGET_I386 + vmmouse_init(s->mouse); +#endif +#endif + qemu_register_reset(kbd_reset, 0, s); +} diff --git a/hw/piix_pci.c b/hw/piix_pci.c new file mode 100644 index 0000000..67e6309 --- /dev/null +++ b/hw/piix_pci.c @@ -0,0 +1,374 @@ +/* + * QEMU i440FX/PIIX3 PCI Bridge Emulation + * + * Copyright (c) 2006 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 "hw.h" +#include "pc.h" +#include "pci.h" + +typedef uint32_t pci_addr_t; +#include "pci_host.h" + +typedef PCIHostState I440FXState; + +static void i440fx_addr_writel(void* opaque, uint32_t addr, uint32_t val) +{ + I440FXState *s = opaque; + s->config_reg = val; +} + +static uint32_t i440fx_addr_readl(void* opaque, uint32_t addr) +{ + I440FXState *s = opaque; + return s->config_reg; +} + +static void piix3_set_irq(qemu_irq *pic, int irq_num, int level); + +/* return the global irq number corresponding to a given device irq + pin. We could also use the bus number to have a more precise + mapping. */ +static int pci_slot_get_pirq(PCIDevice *pci_dev, int irq_num) +{ + int slot_addend; + slot_addend = (pci_dev->devfn >> 3) - 1; + return (irq_num + slot_addend) & 3; +} + +static target_phys_addr_t isa_page_descs[384 / 4]; +static uint8_t smm_enabled; +static int pci_irq_levels[4]; + +static void update_pam(PCIDevice *d, uint32_t start, uint32_t end, int r) +{ + uint32_t addr; + + // printf("ISA mapping %08x-0x%08x: %d\n", start, end, r); + switch(r) { + case 3: + /* RAM */ + cpu_register_physical_memory(start, end - start, + start); + break; + case 1: + /* ROM (XXX: not quite correct) */ + cpu_register_physical_memory(start, end - start, + start | IO_MEM_ROM); + break; + case 2: + case 0: + /* XXX: should distinguish read/write cases */ + for(addr = start; addr < end; addr += 4096) { + cpu_register_physical_memory(addr, 4096, + isa_page_descs[(addr - 0xa0000) >> 12]); + } + break; + } +} + +static void i440fx_update_memory_mappings(PCIDevice *d) +{ + int i, r; + uint32_t smram, addr; + + update_pam(d, 0xf0000, 0x100000, (d->config[0x59] >> 4) & 3); + for(i = 0; i < 12; i++) { + r = (d->config[(i >> 1) + 0x5a] >> ((i & 1) * 4)) & 3; + update_pam(d, 0xc0000 + 0x4000 * i, 0xc0000 + 0x4000 * (i + 1), r); + } + smram = d->config[0x72]; + if ((smm_enabled && (smram & 0x08)) || (smram & 0x40)) { + cpu_register_physical_memory(0xa0000, 0x20000, 0xa0000); + } else { + for(addr = 0xa0000; addr < 0xc0000; addr += 4096) { + cpu_register_physical_memory(addr, 4096, + isa_page_descs[(addr - 0xa0000) >> 12]); + } + } +} + +void i440fx_set_smm(PCIDevice *d, int val) +{ + val = (val != 0); + if (smm_enabled != val) { + smm_enabled = val; + i440fx_update_memory_mappings(d); + } +} + + +/* XXX: suppress when better memory API. We make the assumption that + no device (in particular the VGA) changes the memory mappings in + the 0xa0000-0x100000 range */ +void i440fx_init_memory_mappings(PCIDevice *d) +{ + int i; + for(i = 0; i < 96; i++) { + isa_page_descs[i] = cpu_get_physical_page_desc(0xa0000 + i * 0x1000); + } +} + +static void i440fx_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + /* XXX: implement SMRAM.D_LOCK */ + pci_default_write_config(d, address, val, len); + if ((address >= 0x59 && address <= 0x5f) || address == 0x72) + i440fx_update_memory_mappings(d); +} + +static void i440fx_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + int i; + + pci_device_save(d, f); + qemu_put_8s(f, &smm_enabled); + + for (i = 0; i < 4; i++) + qemu_put_be32(f, pci_irq_levels[i]); +} + +static int i440fx_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + int ret, i; + + if (version_id > 2) + return -EINVAL; + ret = pci_device_load(d, f); + if (ret < 0) + return ret; + i440fx_update_memory_mappings(d); + qemu_get_8s(f, &smm_enabled); + + if (version_id >= 2) + for (i = 0; i < 4; i++) + pci_irq_levels[i] = qemu_get_be32(f); + + return 0; +} + +PCIBus *i440fx_init(PCIDevice **pi440fx_state, qemu_irq *pic) +{ + PCIBus *b; + PCIDevice *d; + I440FXState *s; + + s = qemu_mallocz(sizeof(I440FXState)); + b = pci_register_bus(NULL, "pci", + piix3_set_irq, pci_slot_get_pirq, pic, 0, 4); + s->bus = b; + + register_ioport_write(0xcf8, 4, 4, i440fx_addr_writel, s); + register_ioport_read(0xcf8, 4, 4, i440fx_addr_readl, s); + + register_ioport_write(0xcfc, 4, 1, pci_host_data_writeb, s); + register_ioport_write(0xcfc, 4, 2, pci_host_data_writew, s); + register_ioport_write(0xcfc, 4, 4, pci_host_data_writel, s); + register_ioport_read(0xcfc, 4, 1, pci_host_data_readb, s); + register_ioport_read(0xcfc, 4, 2, pci_host_data_readw, s); + register_ioport_read(0xcfc, 4, 4, pci_host_data_readl, s); + + d = pci_register_device(b, "i440FX", sizeof(PCIDevice), 0, + NULL, i440fx_write_config); + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_INTEL_82441); + d->config[0x08] = 0x02; // revision + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; // header_type + + d->config[0x72] = 0x02; /* SMRAM */ + + register_savevm("I440FX", 0, 2, i440fx_save, i440fx_load, d); + *pi440fx_state = d; + return b; +} + +/* PIIX3 PCI to ISA bridge */ + +static PCIDevice *piix3_dev; +PCIDevice *piix4_dev; + +static void piix3_set_irq(qemu_irq *pic, int irq_num, int level) +{ + int i, pic_irq, pic_level; + + pci_irq_levels[irq_num] = level; + + /* now we change the pic irq level according to the piix irq mappings */ + /* XXX: optimize */ + pic_irq = piix3_dev->config[0x60 + irq_num]; + if (pic_irq < 16) { + /* The pic level is the logical OR of all the PCI irqs mapped + to it */ + pic_level = 0; + for (i = 0; i < 4; i++) { + if (pic_irq == piix3_dev->config[0x60 + i]) + pic_level |= pci_irq_levels[i]; + } + qemu_set_irq(pic[pic_irq], pic_level); + } +} + +static void piix3_reset(void *opaque) +{ + PCIDevice *d = opaque; + uint8_t *pci_conf = d->config; + + pci_conf[0x04] = 0x07; // master, memory and I/O + pci_conf[0x05] = 0x00; + pci_conf[0x06] = 0x00; + pci_conf[0x07] = 0x02; // PCI_status_devsel_medium + pci_conf[0x4c] = 0x4d; + pci_conf[0x4e] = 0x03; + pci_conf[0x4f] = 0x00; + pci_conf[0x60] = 0x80; + pci_conf[0x61] = 0x80; + pci_conf[0x62] = 0x80; + pci_conf[0x63] = 0x80; + pci_conf[0x69] = 0x02; + pci_conf[0x70] = 0x80; + pci_conf[0x76] = 0x0c; + pci_conf[0x77] = 0x0c; + pci_conf[0x78] = 0x02; + pci_conf[0x79] = 0x00; + pci_conf[0x80] = 0x00; + pci_conf[0x82] = 0x00; + pci_conf[0xa0] = 0x08; + pci_conf[0xa2] = 0x00; + pci_conf[0xa3] = 0x00; + pci_conf[0xa4] = 0x00; + pci_conf[0xa5] = 0x00; + pci_conf[0xa6] = 0x00; + pci_conf[0xa7] = 0x00; + pci_conf[0xa8] = 0x0f; + pci_conf[0xaa] = 0x00; + pci_conf[0xab] = 0x00; + pci_conf[0xac] = 0x00; + pci_conf[0xae] = 0x00; + + memset(pci_irq_levels, 0, sizeof(pci_irq_levels)); +} + +static void piix4_reset(void *opaque) +{ + PCIDevice *d = opaque; + uint8_t *pci_conf = d->config; + + pci_conf[0x04] = 0x07; // master, memory and I/O + pci_conf[0x05] = 0x00; + pci_conf[0x06] = 0x00; + pci_conf[0x07] = 0x02; // PCI_status_devsel_medium + pci_conf[0x4c] = 0x4d; + pci_conf[0x4e] = 0x03; + pci_conf[0x4f] = 0x00; + pci_conf[0x60] = 0x0a; // PCI A -> IRQ 10 + pci_conf[0x61] = 0x0a; // PCI B -> IRQ 10 + pci_conf[0x62] = 0x0b; // PCI C -> IRQ 11 + pci_conf[0x63] = 0x0b; // PCI D -> IRQ 11 + pci_conf[0x69] = 0x02; + pci_conf[0x70] = 0x80; + pci_conf[0x76] = 0x0c; + pci_conf[0x77] = 0x0c; + pci_conf[0x78] = 0x02; + pci_conf[0x79] = 0x00; + pci_conf[0x80] = 0x00; + pci_conf[0x82] = 0x00; + pci_conf[0xa0] = 0x08; + pci_conf[0xa2] = 0x00; + pci_conf[0xa3] = 0x00; + pci_conf[0xa4] = 0x00; + pci_conf[0xa5] = 0x00; + pci_conf[0xa6] = 0x00; + pci_conf[0xa7] = 0x00; + pci_conf[0xa8] = 0x0f; + pci_conf[0xaa] = 0x00; + pci_conf[0xab] = 0x00; + pci_conf[0xac] = 0x00; + pci_conf[0xae] = 0x00; + + memset(pci_irq_levels, 0, sizeof(pci_irq_levels)); +} + +static void piix_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + pci_device_save(d, f); +} + +static int piix_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + if (version_id != 2) + return -EINVAL; + return pci_device_load(d, f); +} + +int piix3_init(PCIBus *bus, int devfn) +{ + PCIDevice *d; + uint8_t *pci_conf; + + d = pci_register_device(bus, "PIIX3", sizeof(PCIDevice), + devfn, NULL, NULL); + register_savevm("PIIX3", 0, 2, piix_save, piix_load, d); + + piix3_dev = d; + pci_conf = d->config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371SB_0); // 82371SB PIIX3 PCI-to-ISA bridge (Step A1) + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_ISA); + pci_conf[PCI_HEADER_TYPE] = + PCI_HEADER_TYPE_NORMAL | PCI_HEADER_TYPE_MULTI_FUNCTION; // header_type = PCI_multifunction, generic + + piix3_reset(d); + qemu_register_reset(piix3_reset, 0, d); + return d->devfn; +} + +int piix4_init(PCIBus *bus, int devfn) +{ + PCIDevice *d; + uint8_t *pci_conf; + + d = pci_register_device(bus, "PIIX4", sizeof(PCIDevice), + devfn, NULL, NULL); + register_savevm("PIIX4", 0, 2, piix_save, piix_load, d); + + piix4_dev = d; + pci_conf = d->config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_0); // 82371AB/EB/MB PIIX4 PCI-to-ISA bridge + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_ISA); + pci_conf[PCI_HEADER_TYPE] = + PCI_HEADER_TYPE_NORMAL | PCI_HEADER_TYPE_MULTI_FUNCTION; // header_type = PCI_multifunction, generic + + + piix4_reset(d); + qemu_register_reset(piix4_reset, 0, d); + return d->devfn; +} diff --git a/hw/ps2.c b/hw/ps2.c new file mode 100644 index 0000000..9149598 --- /dev/null +++ b/hw/ps2.c @@ -0,0 +1,611 @@ +/* + * QEMU PS/2 keyboard/mouse emulation + * + * Copyright (c) 2003 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 "hw.h" +#include "ps2.h" +#include "console.h" + +/* debug PC keyboard */ +//#define DEBUG_KBD + +/* debug PC keyboard : only mouse */ +//#define DEBUG_MOUSE + +/* Keyboard Commands */ +#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */ +#define KBD_CMD_ECHO 0xEE +#define KBD_CMD_SCANCODE 0xF0 /* Get/set scancode set */ +#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */ +#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */ +#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */ +#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */ +#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */ +#define KBD_CMD_RESET 0xFF /* Reset */ + +/* Keyboard Replies */ +#define KBD_REPLY_POR 0xAA /* Power on reset */ +#define KBD_REPLY_ID 0xAB /* Keyboard ID */ +#define KBD_REPLY_ACK 0xFA /* Command ACK */ +#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */ + +/* Mouse Commands */ +#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */ +#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */ +#define AUX_SET_RES 0xE8 /* Set resolution */ +#define AUX_GET_SCALE 0xE9 /* Get scaling factor */ +#define AUX_SET_STREAM 0xEA /* Set stream mode */ +#define AUX_POLL 0xEB /* Poll */ +#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */ +#define AUX_SET_WRAP 0xEE /* Set wrap mode */ +#define AUX_SET_REMOTE 0xF0 /* Set remote mode */ +#define AUX_GET_TYPE 0xF2 /* Get type */ +#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */ +#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */ +#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */ +#define AUX_SET_DEFAULT 0xF6 +#define AUX_RESET 0xFF /* Reset aux device */ +#define AUX_ACK 0xFA /* Command byte ACK. */ + +#define MOUSE_STATUS_REMOTE 0x40 +#define MOUSE_STATUS_ENABLED 0x20 +#define MOUSE_STATUS_SCALE21 0x10 + +#define PS2_QUEUE_SIZE 256 + +typedef struct { + uint8_t data[PS2_QUEUE_SIZE]; + int rptr, wptr, count; +} PS2Queue; + +typedef struct { + PS2Queue queue; + int32_t write_cmd; + void (*update_irq)(void *, int); + void *update_arg; +} PS2State; + +typedef struct { + PS2State common; + int scan_enabled; + /* Qemu uses translated PC scancodes internally. To avoid multiple + conversions we do the translation (if any) in the PS/2 emulation + not the keyboard controller. */ + int translate; + int scancode_set; /* 1=XT, 2=AT, 3=PS/2 */ +} PS2KbdState; + +typedef struct { + PS2State common; + uint8_t mouse_status; + uint8_t mouse_resolution; + uint8_t mouse_sample_rate; + uint8_t mouse_wrap; + uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */ + uint8_t mouse_detect_state; + int mouse_dx; /* current values, needed for 'poll' mode */ + int mouse_dy; + int mouse_dz; + uint8_t mouse_buttons; +} PS2MouseState; + +/* Table to convert from PC scancodes to raw scancodes. */ +static const unsigned char ps2_raw_keycode[128] = { + 0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13, + 21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27, + 35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42, + 50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88, 5, 6, 4, 12, 3, + 11, 2, 10, 1, 9,119,126,108,117,125,123,107,115,116,121,105, + 114,122,112,113,127, 96, 97,120, 7, 15, 23, 31, 39, 47, 55, 63, + 71, 79, 86, 94, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111, + 19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110 +}; + +void ps2_queue(void *opaque, int b) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q = &s->queue; + + if (q->count >= PS2_QUEUE_SIZE) + return; + q->data[q->wptr] = b; + if (++q->wptr == PS2_QUEUE_SIZE) + q->wptr = 0; + q->count++; + s->update_irq(s->update_arg, 1); +} + +/* + keycode is expressed as follow: + bit 7 - 0 key pressed, 1 = key released + bits 6-0 - translated scancode set 2 + */ +static void ps2_put_keycode(void *opaque, int keycode) +{ + PS2KbdState *s = opaque; + + /* XXX: add support for scancode sets 1 and 3 */ + if (!s->translate && keycode < 0xe0 && s->scancode_set == 2) + { + if (keycode & 0x80) + ps2_queue(&s->common, 0xf0); + keycode = ps2_raw_keycode[keycode & 0x7f]; + } + ps2_queue(&s->common, keycode); +} + +uint32_t ps2_read_data(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + int val, index; + + q = &s->queue; + if (q->count == 0) { + /* NOTE: if no data left, we return the last keyboard one + (needed for EMM386) */ + /* XXX: need a timer to do things correctly */ + index = q->rptr - 1; + if (index < 0) + index = PS2_QUEUE_SIZE - 1; + val = q->data[index]; + } else { + val = q->data[q->rptr]; + if (++q->rptr == PS2_QUEUE_SIZE) + q->rptr = 0; + q->count--; + /* reading deasserts IRQ */ + s->update_irq(s->update_arg, 0); + /* reassert IRQs if data left */ + s->update_irq(s->update_arg, q->count != 0); + } + return val; +} + +static void ps2_reset_keyboard(PS2KbdState *s) +{ + s->scan_enabled = 1; + s->scancode_set = 2; +} + +void ps2_write_keyboard(void *opaque, int val) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + + switch(s->common.write_cmd) { + default: + case -1: + switch(val) { + case 0x00: + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case 0x05: + ps2_queue(&s->common, KBD_REPLY_RESEND); + break; + case KBD_CMD_GET_ID: + ps2_queue(&s->common, KBD_REPLY_ACK); + /* We emulate a MF2 AT keyboard here */ + ps2_queue(&s->common, KBD_REPLY_ID); + if (s->translate) + ps2_queue(&s->common, 0x41); + else + ps2_queue(&s->common, 0x83); + break; + case KBD_CMD_ECHO: + ps2_queue(&s->common, KBD_CMD_ECHO); + break; + case KBD_CMD_ENABLE: + s->scan_enabled = 1; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_SCANCODE: + case KBD_CMD_SET_LEDS: + case KBD_CMD_SET_RATE: + s->common.write_cmd = val; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_DISABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 0; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_ENABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 1; + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET: + ps2_reset_keyboard(s); + ps2_queue(&s->common, KBD_REPLY_ACK); + ps2_queue(&s->common, KBD_REPLY_POR); + break; + default: + ps2_queue(&s->common, KBD_REPLY_ACK); + break; + } + break; + case KBD_CMD_SCANCODE: + if (val == 0) { + if (s->scancode_set == 1) + ps2_put_keycode(s, 0x43); + else if (s->scancode_set == 2) + ps2_put_keycode(s, 0x41); + else if (s->scancode_set == 3) + ps2_put_keycode(s, 0x3f); + } else { + if (val >= 1 && val <= 3) + s->scancode_set = val; + ps2_queue(&s->common, KBD_REPLY_ACK); + } + s->common.write_cmd = -1; + break; + case KBD_CMD_SET_LEDS: + ps2_queue(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + case KBD_CMD_SET_RATE: + ps2_queue(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + } +} + +/* Set the scancode translation mode. + 0 = raw scancodes. + 1 = translated scancodes (used by qemu internally). */ + +void ps2_keyboard_set_translation(void *opaque, int mode) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + s->translate = mode; +} + +static void ps2_mouse_send_packet(PS2MouseState *s) +{ + unsigned int b; + int dx1, dy1, dz1; + + dx1 = s->mouse_dx; + dy1 = s->mouse_dy; + dz1 = s->mouse_dz; + /* XXX: increase range to 8 bits ? */ + if (dx1 > 127) + dx1 = 127; + else if (dx1 < -127) + dx1 = -127; + if (dy1 > 127) + dy1 = 127; + else if (dy1 < -127) + dy1 = -127; + b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07); + ps2_queue(&s->common, b); + ps2_queue(&s->common, dx1 & 0xff); + ps2_queue(&s->common, dy1 & 0xff); + /* extra byte for IMPS/2 or IMEX */ + switch(s->mouse_type) { + default: + break; + case 3: + if (dz1 > 127) + dz1 = 127; + else if (dz1 < -127) + dz1 = -127; + ps2_queue(&s->common, dz1 & 0xff); + break; + case 4: + if (dz1 > 7) + dz1 = 7; + else if (dz1 < -7) + dz1 = -7; + b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1); + ps2_queue(&s->common, b); + break; + } + + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; +} + +static void ps2_mouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + PS2MouseState *s = opaque; + + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + s->mouse_dx += dx; + s->mouse_dy -= dy; + s->mouse_dz += dz; + /* XXX: SDL sometimes generates nul events: we delete them */ + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && + s->mouse_buttons == buttons_state) + return; + s->mouse_buttons = buttons_state; + + if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && + (s->common.queue.count < (PS2_QUEUE_SIZE - 16))) { + for(;;) { + /* if not remote, send event. Multiple events are sent if + too big deltas */ + ps2_mouse_send_packet(s); + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0) + break; + } + } +} + +void ps2_mouse_fake_event(void *opaque) +{ + ps2_mouse_event(opaque, 1, 0, 0, 0); +} + +void ps2_write_mouse(void *opaque, int val) +{ + PS2MouseState *s = (PS2MouseState *)opaque; +#ifdef DEBUG_MOUSE + printf("kbd: write mouse 0x%02x\n", val); +#endif + switch(s->common.write_cmd) { + default: + case -1: + /* mouse command */ + if (s->mouse_wrap) { + if (val == AUX_RESET_WRAP) { + s->mouse_wrap = 0; + ps2_queue(&s->common, AUX_ACK); + return; + } else if (val != AUX_RESET) { + ps2_queue(&s->common, val); + return; + } + } + switch(val) { + case AUX_SET_SCALE11: + s->mouse_status &= ~MOUSE_STATUS_SCALE21; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_SCALE21: + s->mouse_status |= MOUSE_STATUS_SCALE21; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_STREAM: + s->mouse_status &= ~MOUSE_STATUS_REMOTE; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_WRAP: + s->mouse_wrap = 1; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_REMOTE: + s->mouse_status |= MOUSE_STATUS_REMOTE; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_GET_TYPE: + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, s->mouse_type); + break; + case AUX_SET_RES: + case AUX_SET_SAMPLE: + s->common.write_cmd = val; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_GET_SCALE: + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, s->mouse_status); + ps2_queue(&s->common, s->mouse_resolution); + ps2_queue(&s->common, s->mouse_sample_rate); + break; + case AUX_POLL: + ps2_queue(&s->common, AUX_ACK); + ps2_mouse_send_packet(s); + break; + case AUX_ENABLE_DEV: + s->mouse_status |= MOUSE_STATUS_ENABLED; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_DISABLE_DEV: + s->mouse_status &= ~MOUSE_STATUS_ENABLED; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_SET_DEFAULT: + s->mouse_sample_rate = 100; + s->mouse_resolution = 2; + s->mouse_status = 0; + ps2_queue(&s->common, AUX_ACK); + break; + case AUX_RESET: + s->mouse_sample_rate = 100; + s->mouse_resolution = 2; + s->mouse_status = 0; + s->mouse_type = 0; + ps2_queue(&s->common, AUX_ACK); + ps2_queue(&s->common, 0xaa); + ps2_queue(&s->common, s->mouse_type); + break; + default: + break; + } + break; + case AUX_SET_SAMPLE: + s->mouse_sample_rate = val; + /* detect IMPS/2 or IMEX */ + switch(s->mouse_detect_state) { + default: + case 0: + if (val == 200) + s->mouse_detect_state = 1; + break; + case 1: + if (val == 100) + s->mouse_detect_state = 2; + else if (val == 200) + s->mouse_detect_state = 3; + else + s->mouse_detect_state = 0; + break; + case 2: + if (val == 80) + s->mouse_type = 3; /* IMPS/2 */ + s->mouse_detect_state = 0; + break; + case 3: + if (val == 80) + s->mouse_type = 4; /* IMEX */ + s->mouse_detect_state = 0; + break; + } + ps2_queue(&s->common, AUX_ACK); + s->common.write_cmd = -1; + break; + case AUX_SET_RES: + s->mouse_resolution = val; + ps2_queue(&s->common, AUX_ACK); + s->common.write_cmd = -1; + break; + } +} + +static void ps2_reset(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + s->write_cmd = -1; + q = &s->queue; + q->rptr = 0; + q->wptr = 0; + q->count = 0; + s->update_irq(s->update_arg, 0); +} + +static void ps2_common_save (QEMUFile *f, PS2State *s) +{ + qemu_put_be32 (f, s->write_cmd); + qemu_put_be32 (f, s->queue.rptr); + qemu_put_be32 (f, s->queue.wptr); + qemu_put_be32 (f, s->queue.count); + qemu_put_buffer (f, s->queue.data, sizeof (s->queue.data)); +} + +static void ps2_common_load (QEMUFile *f, PS2State *s) +{ + s->write_cmd=qemu_get_be32 (f); + s->queue.rptr=qemu_get_be32 (f); + s->queue.wptr=qemu_get_be32 (f); + s->queue.count=qemu_get_be32 (f); + qemu_get_buffer (f, s->queue.data, sizeof (s->queue.data)); +} + +static void ps2_kbd_save(QEMUFile* f, void* opaque) +{ + PS2KbdState *s = (PS2KbdState*)opaque; + + ps2_common_save (f, &s->common); + qemu_put_be32(f, s->scan_enabled); + qemu_put_be32(f, s->translate); + qemu_put_be32(f, s->scancode_set); +} + +static void ps2_mouse_save(QEMUFile* f, void* opaque) +{ + PS2MouseState *s = (PS2MouseState*)opaque; + + ps2_common_save (f, &s->common); + qemu_put_8s(f, &s->mouse_status); + qemu_put_8s(f, &s->mouse_resolution); + qemu_put_8s(f, &s->mouse_sample_rate); + qemu_put_8s(f, &s->mouse_wrap); + qemu_put_8s(f, &s->mouse_type); + qemu_put_8s(f, &s->mouse_detect_state); + qemu_put_be32(f, s->mouse_dx); + qemu_put_be32(f, s->mouse_dy); + qemu_put_be32(f, s->mouse_dz); + qemu_put_8s(f, &s->mouse_buttons); +} + +static int ps2_kbd_load(QEMUFile* f, void* opaque, int version_id) +{ + PS2KbdState *s = (PS2KbdState*)opaque; + + if (version_id != 2 && version_id != 3) + return -EINVAL; + + ps2_common_load (f, &s->common); + s->scan_enabled=qemu_get_be32(f); + s->translate=qemu_get_be32(f); + if (version_id == 3) + s->scancode_set=qemu_get_be32(f); + else + s->scancode_set=2; + return 0; +} + +static int ps2_mouse_load(QEMUFile* f, void* opaque, int version_id) +{ + PS2MouseState *s = (PS2MouseState*)opaque; + + if (version_id != 2) + return -EINVAL; + + ps2_common_load (f, &s->common); + qemu_get_8s(f, &s->mouse_status); + qemu_get_8s(f, &s->mouse_resolution); + qemu_get_8s(f, &s->mouse_sample_rate); + qemu_get_8s(f, &s->mouse_wrap); + qemu_get_8s(f, &s->mouse_type); + qemu_get_8s(f, &s->mouse_detect_state); + s->mouse_dx=qemu_get_be32(f); + s->mouse_dy=qemu_get_be32(f); + s->mouse_dz=qemu_get_be32(f); + qemu_get_8s(f, &s->mouse_buttons); + return 0; +} + +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2KbdState *s = (PS2KbdState *)qemu_mallocz(sizeof(PS2KbdState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + s->scancode_set = 2; + ps2_reset(&s->common); + register_savevm("ps2kbd", 0, 3, ps2_kbd_save, ps2_kbd_load, s); + //qemu_add_kbd_event_handler(ps2_put_keycode, s); + qemu_register_reset(ps2_reset, 0, &s->common); + return s; +} + +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2MouseState *s = (PS2MouseState *)qemu_mallocz(sizeof(PS2MouseState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + ps2_reset(&s->common); + register_savevm("ps2mouse", 0, 2, ps2_mouse_save, ps2_mouse_load, s); + //qemu_add_mouse_event_handler(ps2_mouse_event, s, 0, "QEMU PS/2 Mouse"); + qemu_register_reset(ps2_reset, 0, &s->common); + return s; +} diff --git a/hw/ps2.h b/hw/ps2.h new file mode 100644 index 0000000..32a4231 --- /dev/null +++ b/hw/ps2.h @@ -0,0 +1,9 @@ +/* ps2.c */ +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg); +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg); +void ps2_write_mouse(void *, int val); +void ps2_write_keyboard(void *, int val); +uint32_t ps2_read_data(void *); +void ps2_queue(void *, int b); +void ps2_keyboard_set_translation(void *opaque, int mode); +void ps2_mouse_fake_event(void *opaque); diff --git a/hw/smbios.c b/hw/smbios.c new file mode 100644 index 0000000..ced90ce --- /dev/null +++ b/hw/smbios.c @@ -0,0 +1,224 @@ +/* + * SMBIOS Support + * + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * + * Authors: + * Alex Williamson + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "sysemu.h" +#include "smbios.h" + +/* + * Structures shared with the BIOS + */ +struct smbios_header { + uint16_t length; + uint8_t type; +} __attribute__((__packed__)); + +struct smbios_field { + struct smbios_header header; + uint8_t type; + uint16_t offset; + uint8_t data[]; +} __attribute__((__packed__)); + +struct smbios_table { + struct smbios_header header; + uint8_t data[]; +} __attribute__((__packed__)); + +#define SMBIOS_FIELD_ENTRY 0 +#define SMBIOS_TABLE_ENTRY 1 + + +static uint8_t *smbios_entries; +static size_t smbios_entries_len; + +uint8_t *smbios_get_table(size_t *length) +{ + *length = smbios_entries_len; + return smbios_entries; +} + +/* + * To avoid unresolvable overlaps in data, don't allow both + * tables and fields for the same smbios type. + */ +static void smbios_check_collision(int type, int entry) +{ + uint16_t *num_entries = (uint16_t *)smbios_entries; + struct smbios_header *header; + char *p; + int i; + + if (!num_entries) + return; + + p = (char *)(num_entries + 1); + + for (i = 0; i < *num_entries; i++) { + header = (struct smbios_header *)p; + if (entry == SMBIOS_TABLE_ENTRY && header->type == SMBIOS_FIELD_ENTRY) { + struct smbios_field *field = (void *)header; + if (type == field->type) { + fprintf(stderr, "SMBIOS type %d field already defined, " + "cannot add table\n", type); + exit(1); + } + } else if (entry == SMBIOS_FIELD_ENTRY && + header->type == SMBIOS_TABLE_ENTRY) { + struct smbios_structure_header *table = (void *)(header + 1); + if (type == table->type) { + fprintf(stderr, "SMBIOS type %d table already defined, " + "cannot add field\n", type); + exit(1); + } + } + p += le16_to_cpu(header->length); + } +} + +void smbios_add_field(int type, int offset, int len, void *data) +{ + struct smbios_field *field; + + smbios_check_collision(type, SMBIOS_FIELD_ENTRY); + + if (!smbios_entries) { + smbios_entries_len = sizeof(uint16_t); + smbios_entries = qemu_mallocz(smbios_entries_len); + } + smbios_entries = qemu_realloc(smbios_entries, smbios_entries_len + + sizeof(*field) + len); + field = (struct smbios_field *)(smbios_entries + smbios_entries_len); + field->header.type = SMBIOS_FIELD_ENTRY; + field->header.length = cpu_to_le16(sizeof(*field) + len); + + field->type = type; + field->offset = cpu_to_le16(offset); + memcpy(field->data, data, len); + + smbios_entries_len += sizeof(*field) + len; + (*(uint16_t *)smbios_entries) = + cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); +} + +static void smbios_build_type_0_fields(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "vendor", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, vendor_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "version", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, bios_version_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "date", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, + bios_release_date_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "release", t)) { + int major, minor; + sscanf(buf, "%d.%d", &major, &minor); + smbios_add_field(0, offsetof(struct smbios_type_0, + system_bios_major_release), 1, &major); + smbios_add_field(0, offsetof(struct smbios_type_0, + system_bios_minor_release), 1, &minor); + } +} + +static void smbios_build_type_1_fields(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "manufacturer", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, manufacturer_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "product", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, product_name_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "version", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, version_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "serial", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, serial_number_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "uuid", t)) { + if (qemu_uuid_parse(buf, qemu_uuid) != 0) { + fprintf(stderr, "Invalid SMBIOS UUID string\n"); + exit(1); + } + } + if (get_param_value(buf, sizeof(buf), "sku", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, sku_number_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "family", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, family_str), + strlen(buf) + 1, buf); +} + +int smbios_entry_add(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "file", t)) { + struct smbios_structure_header *header; + struct smbios_table *table; + int size = get_image_size(buf); + + if (size < sizeof(struct smbios_structure_header)) { + fprintf(stderr, "Cannot read smbios file %s", buf); + exit(1); + } + + if (!smbios_entries) { + smbios_entries_len = sizeof(uint16_t); + smbios_entries = qemu_mallocz(smbios_entries_len); + } + + smbios_entries = qemu_realloc(smbios_entries, smbios_entries_len + + sizeof(*table) + size); + table = (struct smbios_table *)(smbios_entries + smbios_entries_len); + table->header.type = SMBIOS_TABLE_ENTRY; + table->header.length = cpu_to_le16(sizeof(*table) + size); + + if (load_image(buf, table->data) != size) { + fprintf(stderr, "Failed to load smbios file %s", buf); + exit(1); + } + + header = (struct smbios_structure_header *)(table->data); + smbios_check_collision(header->type, SMBIOS_TABLE_ENTRY); + + smbios_entries_len += sizeof(*table) + size; + (*(uint16_t *)smbios_entries) = + cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); + return 0; + } + + if (get_param_value(buf, sizeof(buf), "type", t)) { + unsigned long type = strtoul(buf, NULL, 0); + switch (type) { + case 0: + smbios_build_type_0_fields(t); + return 0; + case 1: + smbios_build_type_1_fields(t); + return 0; + default: + fprintf(stderr, "Don't know how to build fields for SMBIOS type " + "%ld\n", type); + exit(1); + } + } + + fprintf(stderr, "smbios: must specify type= or file=\n"); + return -1; +} -- cgit v1.1