From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1MswIp-0004rl-1i for qemu-devel@nongnu.org; Wed, 30 Sep 2009 06:20:55 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1MswIO-0004ZU-Mg for qemu-devel@nongnu.org; Wed, 30 Sep 2009 06:20:49 -0400 Received: from [199.232.76.173] (port=43896 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1MswIN-0004ZJ-B4 for qemu-devel@nongnu.org; Wed, 30 Sep 2009 06:20:27 -0400 Received: from mail.valinux.co.jp ([210.128.90.3]:55871) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1MswIL-0005q1-1O for qemu-devel@nongnu.org; Wed, 30 Sep 2009 06:20:26 -0400 From: Isaku Yamahata Date: Wed, 30 Sep 2009 19:18:29 +0900 Message-Id: <1254305917-14784-54-git-send-email-yamahata@valinux.co.jp> In-Reply-To: <1254305917-14784-1-git-send-email-yamahata@valinux.co.jp> References: <1254305917-14784-1-git-send-email-yamahata@valinux.co.jp> Subject: [Qemu-devel] [PATCH 53/61] pc q35 based chipset emulator List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org, anthony@codemonkey.ws Cc: yamahata@valinux.co.jp pc q35 based chipset emulator Signed-off-by: Isaku Yamahata --- Makefile.target | 1 + hw/acpi_ich9.c | 565 ++++++++++++++++++++++++++++++++++++++++++ hw/acpi_ich9.h | 57 +++++ hw/pc_q35.c | 220 +++++++++++++++++ hw/pci_ids.h | 13 + hw/q35.c | 731 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/q35.h | 228 +++++++++++++++++ hw/q35_smbus.c | 150 ++++++++++++ 8 files changed, 1965 insertions(+), 0 deletions(-) create mode 100644 hw/acpi_ich9.c create mode 100644 hw/acpi_ich9.h create mode 100644 hw/pc_q35.c create mode 100644 hw/q35.c create mode 100644 hw/q35.h create mode 100644 hw/q35_smbus.c diff --git a/Makefile.target b/Makefile.target index ab5c098..9990942 100644 --- a/Makefile.target +++ b/Makefile.target @@ -188,6 +188,7 @@ obj-i386-y += usb-uhci.o vmmouse.o vmport.o vmware_vga.o hpet.o obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += ne2000-isa.o obj-i386-y += pc_smbus.o pc_apm.o acpi_piix4.o pc_piix.o +obj-i386-y += pc_q35.o q35.o q35_smbus.o acpi_ich9.o # shared objects obj-ppc-y = ppc.o ide/core.o ide/qdev.o ide/isa.o ide/pci.o ide/macio.o diff --git a/hw/acpi_ich9.c b/hw/acpi_ich9.c new file mode 100644 index 0000000..82e4d5c --- /dev/null +++ b/hw/acpi_ich9.c @@ -0,0 +1,565 @@ +/* + * ACPI implementation + * + * Copyright (c) 2006 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 version 2 as published by the Free Software Foundation. + * + * 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 + */ +/* + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This is based on acpi.c. + */ +#include "hw.h" +#include "pc.h" +#include "pci.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "acpi.h" + +#include "q35.h" + +//#define DEBUG + +#ifdef DEBUG +#define ICH9_DEBUG(fmt, ...) do { printf("%s "fmt, __func__, ## __VA_ARGS__); } while (0) +#else +#define ICH9_DEBUG(fmt, ...) do { } while (0) +#endif + +/* XXX where does the constant, ACPI_DBG_IO_ADDR, come from */ +/* 0xb044 is used on ich9. 0xb080 or something. */ +//#define ACPI_DBG_IO_ADDR 0xb044 +// 0xb044 is already used. +#define ACPI_DBG_IO_ADDR 0xb080 +static void ich9_acpi_dbg_writel(void *opaqe, uint32_t addr, uint32_t val) +{ + ICH9_DEBUG("0x%08x\n", val); +} + +void ich9_acpi_dbg_init(void); +void ich9_acpi_dbg_init(void) +{ + register_ioport_write(ACPI_DBG_IO_ADDR, 4, 4, ich9_acpi_dbg_writel, NULL); +} + + +static void pm_ioport_write_fallback(void *opaque, uint32_t addr, int len, + uint32_t val); +static uint32_t pm_ioport_read_fallback(void *opaque, uint32_t addr, int len); +static void gpe_ioport_writeb(struct ich9_lpc_pm_regs *pm, + uint32_t addr, uint32_t val); +static uint32_t gpe_ioport_readb(struct ich9_lpc_pm_regs *pm, + uint32_t addr); + +static uint32_t get_pmtmr(struct ich9_lpc_pm_regs *pm) +{ + uint32_t d; + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, + get_ticks_per_sec()); + return d & 0xffffff; +} + +static int get_pm1_sts(struct ich9_lpc_pm_regs *pm) +{ + int64_t d; + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, + get_ticks_per_sec()); + if (d >= pm->tmr_overflow_time) + pm->pm1_sts |= ACPI_BITMASK_TIMER_STATUS; + return pm->pm1_sts; +} + +static void pm_update_sci(struct ich9_lpc_pm_regs *pm) +{ + int sci_level, pm1_sts; + int64_t expire_time; + + pm1_sts = get_pm1_sts(pm); + sci_level = (((pm1_sts & pm->pm1_en) & + (ACPI_BITMASK_RT_CLOCK_ENABLE | + ACPI_BITMASK_POWER_BUTTON_ENABLE | + ACPI_BITMASK_GLOBAL_LOCK_ENABLE | + ACPI_BITMASK_TIMER_ENABLE)) != 0); + qemu_set_irq(pm->irq, sci_level); + /* schedule a timer interruption if needed */ + if ((pm->pm1_en & ACPI_BITMASK_TIMER_ENABLE) && + !(pm1_sts & ACPI_BITMASK_TIMER_STATUS)) { + expire_time = muldiv64(pm->tmr_overflow_time, get_ticks_per_sec(), + PM_TIMER_FREQUENCY); + qemu_mod_timer(pm->tmr_timer, expire_time); + } else { + qemu_del_timer(pm->tmr_timer); + } +} + +static void pm_tmr_timer(void *opaque) +{ + struct ich9_lpc_pm_regs *pm = opaque; + pm_update_sci(pm); +} + +static void pm_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + struct ich9_lpc_pm_regs *pm = opaque; + + switch (addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + 7): + case ICH9_PMIO_GPE0_EN ... (ICH9_PMIO_GPE0_EN + 7): + gpe_ioport_writeb(pm, addr, val); + break; + default: + break; + } + + ICH9_DEBUG("port=0x%04x val=0x%04x\n", addr, val); +} + +static uint32_t pm_ioport_readb(void *opaque, uint32_t addr) +{ + struct ich9_lpc_pm_regs *pm = opaque; + uint32_t val = 0; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + 7): + case ICH9_PMIO_GPE0_EN ... (ICH9_PMIO_GPE0_EN + 7): + val = gpe_ioport_readb(pm, addr); + break; + default: + val = 0; + break; + } + ICH9_DEBUG("port=0x%04x val=0x%04x\n", addr, val); + return val; +} + +static void pm_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + struct ich9_lpc_pm_regs *pm = opaque; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_STS: + { + int64_t d; + int pm1_sts; + pm1_sts = get_pm1_sts(pm); + if (pm1_sts & val & ACPI_BITMASK_TIMER_STATUS) { + /* if TMRSTS is reset, then compute the new overflow time */ + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, + get_ticks_per_sec()); + pm->tmr_overflow_time = (d + 0x800000LL) & ~0x7fffffLL; + } + pm->pm1_sts &= ~val; + pm_update_sci(pm); + } + break; + case ICH9_PMIO_PM1_EN: + pm->pm1_en = val; + pm_update_sci(pm); + break; + case ICH9_PMIO_PM1_CNT: + { + int sus_typ; + pm->pm1_cnt = val & ~(ACPI_BITMASK_SLEEP_ENABLE); + if (val & ACPI_BITMASK_SLEEP_ENABLE) { + /* change suspend type */ + sus_typ = (val >> 10) & 7; + switch(sus_typ) { + case 0: /* soft power off */ + qemu_system_shutdown_request(); + break; + case 1: + /* ACPI_BITMASK_WAKE_STATUS should be set on resume. + Pretend that resume was caused by power button */ + pm->pm1_sts |= (ACPI_BITMASK_WAKE_STATUS | + ACPI_BITMASK_POWER_BUTTON_STATUS); + qemu_system_reset_request(); +#if defined(TARGET_I386) + cmos_set_s3_resume(); +#endif + default: + break; + } + } + } + break; + default: + pm_ioport_write_fallback(opaque, addr, 2, val); + break; + } + ICH9_DEBUG("port=0x%04x val=0x%04x\n", addr, val); +} + +static uint32_t pm_ioport_readw(void *opaque, uint32_t addr) +{ + struct ich9_lpc_pm_regs *pm = opaque; + uint32_t val; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_STS: + val = get_pm1_sts(pm); + break; + case ICH9_PMIO_PM1_EN: + val = pm->pm1_en; + break; + case ICH9_PMIO_PM1_CNT: + val = pm->pm1_cnt; + break; + default: + val = pm_ioport_read_fallback(opaque, addr, 2); + break; + } + ICH9_DEBUG("port=0x%04x val=0x%04x\n", addr, val); + return val; +} + +static void pm_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + struct ich9_lpc_pm_regs *pm = opaque; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_SMI_EN: + pm->smi_en = val; + break; + default: + pm_ioport_write_fallback(opaque, addr, 4, val); + break; + } + ICH9_DEBUG("port=0x%04x val=0x%08x\n", addr, val); +} + +static uint32_t pm_ioport_readl(void *opaque, uint32_t addr) +{ + struct ich9_lpc_pm_regs *pm = opaque; + uint32_t val; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_TMR: + val = get_pmtmr(pm); + break; + case ICH9_PMIO_SMI_EN: + val = pm->smi_en; + break; + + default: + val = pm_ioport_read_fallback(opaque, addr, 4); + break; + } + ICH9_DEBUG("port=0x%04x val=0x%08x\n", addr, val); + return val; +} + +static void pm_ioport_write_fallback(void *opaque, uint32_t addr, int len, + uint32_t val) +{ + int subsize = (len == 4)? 2: 1; + IOPortWriteFunc *ioport_write = + (subsize == 2)? pm_ioport_writew: pm_ioport_writeb; + + int i; + + for (i = 0; i < len; i += subsize) { + ioport_write(opaque, addr, val); + val >>= 8 * subsize; + } +} + +static uint32_t pm_ioport_read_fallback(void *opaque, uint32_t addr, int len) +{ + int subsize = (len == 4)? 2: 1; + IOPortReadFunc *ioport_read = + (subsize == 2)? pm_ioport_readw: pm_ioport_readb; + + uint32_t val; + int i; + + val = 0; + for (i = 0; i < len; i += subsize) { + val <<= 8 * subsize; + val |= ioport_read(opaque, addr); + } + + return val; +} + +void ich9_pm_iospace_update(struct ich9_lpc_pm_regs *pm, uint32_t pm_io_base) +{ + ICH9_DEBUG("to 0x%x\n", pm_io_base); + + assert((pm_io_base & ICH9_PMIO_MASK) == 0); + + if (pm->pm_io_base != 0) + isa_unassign_ioport(pm->pm_io_base, ICH9_PMIO_SIZE); + + /* XXX: tmp hack */ + if (pm_io_base == 0) + return; + + register_ioport_write(pm_io_base, ICH9_PMIO_SIZE, 1, pm_ioport_writeb, pm); + register_ioport_read(pm_io_base, ICH9_PMIO_SIZE, 1, pm_ioport_readb, pm); + register_ioport_write(pm_io_base, ICH9_PMIO_SIZE, 2, pm_ioport_writew, pm); + register_ioport_read(pm_io_base, ICH9_PMIO_SIZE, 2, pm_ioport_readw, pm); + register_ioport_write(pm_io_base, ICH9_PMIO_SIZE, 4, pm_ioport_writel, pm); + register_ioport_read(pm_io_base, ICH9_PMIO_SIZE, 4, pm_ioport_readl, pm); + + pm->pm_io_base = pm_io_base; +} + +#define PCI_BUS_MAX 256 +struct ich9_pci_status { + uint32_t up[PCI_BUS_MAX]; + uint32_t down[PCI_BUS_MAX]; + uint8_t bus; +}; + +const VMStateDescription vmstate_ich9_pci_status = { + .name = "ich9_pci_status", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(up, struct ich9_pci_status, PCI_BUS_MAX), + VMSTATE_UINT32_ARRAY(down, struct ich9_pci_status, PCI_BUS_MAX), + VMSTATE_UINT8(bus, struct ich9_pci_status), + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_ich9_pm = { + .name = "ich9_pm", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(pm1_sts, struct ich9_lpc_pm_regs), + VMSTATE_UINT16(pm1_en, struct ich9_lpc_pm_regs), + VMSTATE_UINT16(pm1_cnt, struct ich9_lpc_pm_regs), + VMSTATE_TIMER(tmr_timer, struct ich9_lpc_pm_regs), + VMSTATE_INT64(tmr_overflow_time, struct ich9_lpc_pm_regs), + VMSTATE_UINT64(gpe0_sts, struct ich9_lpc_pm_regs), + VMSTATE_UINT64(gpe0_en, struct ich9_lpc_pm_regs), + VMSTATE_UINT32(smi_en, struct ich9_lpc_pm_regs), + VMSTATE_UINT32(smi_sts, struct ich9_lpc_pm_regs), + VMSTATE_STRUCT_POINTER(pci_status, struct ich9_lpc_pm_regs, 0, + vmstate_ich9_pci_status, + struct ich9_pci_status*), + VMSTATE_END_OF_LIST() + } +}; + +static void pm_reset(void *opaque) +{ + //struct ich9_lpc_pm_regs *pm = opaque; + /* XXX:TODO */ +} + +static void pm_powerdown(void *opaque, int irq, int power_failing) +{ +#if defined(TARGET_I386) + struct ich9_lpc_pm_regs *pm = (struct ich9_lpc_pm_regs*) opaque; + + if (!pm) { + qemu_system_shutdown_request(); + } else if (pm->pm1_en & ACPI_BITMASK_POWER_BUTTON_ENABLE) { + pm->pm1_sts |= ACPI_BITMASK_POWER_BUTTON_STATUS; + pm_update_sci(pm); + } +#endif +} + +void ich9_pm_init(struct ich9_lpc_pm_regs *pm, qemu_irq sci_irq) +{ + pm->tmr_timer = qemu_new_timer(vm_clock, pm_tmr_timer, pm); + + pm->irq = sci_irq; + qemu_register_reset(pm_reset, pm); + qemu_system_powerdown = *qemu_allocate_irqs(pm_powerdown, pm, 1); +} + +/* GPE */ +#define GPE_ADDR_MASK 0xff + +static uint8_t *gpe_ioport_get_ptr(struct ich9_lpc_pm_regs *pm, uint32_t addr) +{ + uint8_t *cur = NULL; + + addr &= GPE_ADDR_MASK; + switch (addr) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + 7): + cur = (uint8_t*)&pm->gpe0_sts; + cur += addr - ICH9_PMIO_GPE0_STS; + break; + case ICH9_PMIO_GPE0_EN ... (ICH9_PMIO_GPE0_EN + 7): + cur = (uint8_t*)&pm->gpe0_en; + cur += addr - ICH9_PMIO_GPE0_EN; + break; + default: + abort(); + break; + } + return cur; +} + +static void gpe_ioport_writeb(struct ich9_lpc_pm_regs *pm, + uint32_t addr, uint32_t val) +{ + uint8_t *cur = gpe_ioport_get_ptr(pm, addr); + + switch (addr & GPE_ADDR_MASK) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + 7): + *cur = (*cur) & ~val; + break; + case ICH9_PMIO_GPE0_EN ... (ICH9_PMIO_GPE0_EN + 7): + *cur = val; + break; + default: + abort(); + break; + } + ICH9_DEBUG("%x <== %d\n", addr, val); +} + +static uint32_t gpe_ioport_readb(struct ich9_lpc_pm_regs *pm, + uint32_t addr) +{ + uint8_t *cur = gpe_ioport_get_ptr(pm, addr); + uint32_t val = 0; + + if (cur != NULL) + val = *cur; + + ICH9_DEBUG("%x == %x\n", addr, val); + return val; +} + +#define PCI_HP_BASE 0xae00 +#define PCI_EJ_BASE 0xae08 +#define PCI_HP_BUS 0xae0c + +static uint32_t pcihotplug_read(void *opaque, uint32_t addr) +{ + uint32_t val = 0; + struct ich9_pci_status *p = opaque; + switch (addr) { + case PCI_HP_BASE: + val = p->up[p->bus]; + break; + case PCI_HP_BASE + 4: + val = p->down[p->bus]; + break; + default: + break; + } + + ICH9_DEBUG("%x == %x\n", addr, val); + return val; +} + +static void pcihotplug_write(void *opaque, uint32_t addr, uint32_t val) +{ + struct ich9_pci_status *p = opaque; + switch (addr) { + case PCI_HP_BASE: + p->up[p->bus] = val; + break; + case PCI_HP_BASE + 4: + p->down[p->bus] = val; + break; + } + + ICH9_DEBUG("%x <== %d\n", addr, val); +} + +static uint32_t pciej_read(void *opaque, uint32_t addr) +{ + ICH9_DEBUG("%x\n", addr); + return 0; +} + +static void pciej_write(void *opaque, uint32_t addr, uint32_t val) +{ +#if defined (TARGET_I386) + struct ich9_pci_status *p = opaque; + int slot = ffs(val) - 1; + + pci_device_hot_remove_success(p->bus, slot); +#endif + + ICH9_DEBUG("%x <== %d\n", addr, val); +} + +static uint32_t pci_hp_bus_read(void *opaque, uint32_t addr) +{ + struct ich9_pci_status *p = opaque; + ICH9_DEBUG("%x <== %d\n", addr, val); + return p->bus; +} + +static void pci_hp_bus_write(void *opaque, uint32_t addr, uint32_t val) +{ + struct ich9_pci_status *p = opaque; + p->bus = val; + ICH9_DEBUG("%x <== %d\n", addr, val); +} + +static void enable_device(struct ich9_pci_status *p, int bus, int slot) +{ + p->up[bus] |= (1 << slot); +} + +static void disable_device(struct ich9_pci_status *p, int bus, int slot) +{ + p->down[bus] |= (1 << slot); +} + +#define ACPI_SCI_L1 0x2 + +static void ich9_device_hot_add(int bus, int slot, int state, void *opaque) +{ + struct ich9_lpc_pm_regs *pm = opaque; + struct ich9_pci_status *pci_status = pm->pci_status; + + pci_status->up[bus] = 0; + pci_status->down[bus] = 0; + + if (state) + enable_device(pci_status, bus, slot); + else + disable_device(pci_status, bus, slot); + + pm->gpe0_sts |= ACPI_SCI_L1; + if (pm->gpe0_en & ACPI_SCI_L1) { + qemu_set_irq(pm->irq, 1); + qemu_set_irq(pm->irq, 0); + } +} + +void ich9_hot_add_init(struct ich9_lpc_pm_regs *pm) +{ + struct ich9_pci_status *pci_status; + pci_status = qemu_mallocz(sizeof(*pci_status)); + + register_ioport_write(PCI_HP_BASE, 8, 4, pcihotplug_write, pci_status); + register_ioport_read(PCI_HP_BASE, 8, 4, pcihotplug_read, pci_status); + + register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, pci_status); + register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, pci_status); + + register_ioport_write(PCI_HP_BUS, 1, 1, pci_hp_bus_write, pci_status); + register_ioport_read(PCI_HP_BUS, 1, 1, pci_hp_bus_read, pci_status); + + pm->pci_status = pci_status; + qemu_system_device_hot_add_register(ich9_device_hot_add, pm); +} diff --git a/hw/acpi_ich9.h b/hw/acpi_ich9.h new file mode 100644 index 0000000..7ebb710 --- /dev/null +++ b/hw/acpi_ich9.h @@ -0,0 +1,57 @@ +/* + * QEMU GMCH/ICH9 LPC PM Emulation + * + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 + */ + +#ifndef HW_ACPI_ICH9_H +#define HW_ACPI_ICH9_H + +#include + +struct ich9_pci_status; + +struct ich9_lpc_pm_regs { + uint16_t pm1_sts; + uint16_t pm1_en; + + /* + * In ich9 spec says that pm1_cnt register is 32bit width and + * that the upper 16bits are reserved and unused. + * PM1a_CNT_BLK = 2 in FADT so it is defined as uint16_t. + */ + uint16_t pm1_cnt; + + /* uint32_t pm1_tmr; */ + QEMUTimer *tmr_timer; + int64_t tmr_overflow_time; + + uint64_t gpe0_sts; + uint64_t gpe0_en; + + uint32_t smi_en; + uint32_t smi_sts; + + qemu_irq irq; /* SCI */ + + uint32_t pm_io_base; + + struct ich9_pci_status *pci_status; +}; + +#endif /* HW_ACPI_ICH9_H */ diff --git a/hw/pc_q35.c b/hw/pc_q35.c new file mode 100644 index 0000000..9980a39 --- /dev/null +++ b/hw/pc_q35.c @@ -0,0 +1,220 @@ +/* + * Q35 chipset based pc system emulator + * + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This is based on pc.c, but heavily modified. + * + * 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 + */ +/* + * 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 "pci_bridge.h" +#include "block.h" +#include "sysemu.h" +#include "audio/audio.h" +#include "net.h" +#include "smbus.h" +#include "boards.h" +#include "monitor.h" +#include "fw_cfg.h" +#include "hpet_emul.h" +#include "watchdog.h" +#include "smbios.h" +#include "ide.h" + +#include "q35.h" + +#define MAX_IDE_BUS 2 + +static const int ide_iobase[MAX_IDE_BUS] = { 0x1f0, 0x170 }; +static const int ide_iobase2[MAX_IDE_BUS] = { 0x3f6, 0x376 }; +static const int ide_irq[MAX_IDE_BUS] = { 14, 15 }; + +static void pc_q35_bridge_init(PCIBus *host_bus, PCIBus *pci_bus) +{ + int dev; + + /* PCI to PCI bridge b6:d[29 - 31]:f0, 6:[1d - 1f].0 with subordinate bus + of 7 - 9 on b0:d30:f0, 0.1e.0 = pci_bus */ +#define Q35_P2P_BRDIGE_DEV_BASE 29 +#define Q35_P2P_BRDIGE_DEV_MAX 32 +#define Q35_P2P_BRDIGE_SUBBUS_BASE 7 + for (dev = Q35_P2P_BRDIGE_DEV_BASE; dev < Q35_P2P_BRDIGE_DEV_MAX; dev++) { + int sec_bus = + Q35_P2P_BRDIGE_SUBBUS_BASE + dev - Q35_P2P_BRDIGE_DEV_BASE; + int sub_bus = sec_bus; + + i82801ba11_init(pci_bus, PCI_DEVFN(dev, 0), sec_bus, sub_bus); + } + + /* PCIe root port b0:d1:f0, 0:01.0 with subordinate bus of 63 */ + + /* PCIe root port b0:d23:f[0-5], 0.17.[0-5] with subordinate bus of 64 */ + + /* PCIe upstream port b0:d24:f0, 0.18.0 with subordinate bus of 126 */ + + /* PCIe downstream port b126:d[0-15]:f[0-7], 7e:[0-1f].[0-7] with + subordinate bus of 127 - 127 + 128 on bus 126 */ +} + +/* PC hardware initialisation */ +static void pc_q35_init(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) +{ + int i; + ram_addr_t below_4g_mem_size, above_4g_mem_size; + PCIBus *host_bus; + PCIBus *pci_bus; + ISADevice *isa_dev; + static PCIDevice *gmch_state; + int ich9_lpc_devfn = -1; + qemu_irq *cpu_irq; + qemu_irq *isa_irq; + qemu_irq *i8259; + IsaIrqState *isa_irq_state; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + fdctrl_t *floppy_controller; + RTCState *rtc_state; + + pc_cpus_init(cpu_model); + + vmport_init(); + + /* allocate ram and load rom/bios */ + pc_memory_init(ram_size, kernel_filename, kernel_cmdline, initrd_filename, + &below_4g_mem_size, &above_4g_mem_size); + + + cpu_irq = pc_allocate_cpu_irq(); + i8259 = i8259_init(cpu_irq[0]); + isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state)); + isa_irq_state->i8259 = i8259; + isa_irq_state->ioapic = ioapic_init(); + isa_irq = qemu_allocate_irqs(isa_irq_handler, isa_irq_state, 24); + + host_bus = gmch_init(&gmch_state, isa_irq, &ich9_lpc_devfn); + + isa_bus_irqs(isa_irq); + pc_register_ferr_irq(isa_reserve_irq(13)); + + pci_bus = ich9_d2pbr_init(host_bus, PCI_DEVFN(ICH9_D2P_BRIDGE_DEV, + ICH9_D2P_BRIDGE_FUNC), + ICH9_D2P_SECONDARY_DEFAULT); + pci_set_default_bus(pci_bus); + + pc_vga_init(host_bus); + + /* init basic PC hardware */ + pc_basic_device_init(isa_irq, &isa_dev, &floppy_controller, &rtc_state); + + pc_q35_bridge_init(host_bus, pci_bus); + + for(i = 0; i < nb_nics; i++) { + NICInfo *nd = &nd_table[i]; + + pci_nic_init(nd, "e1000", NULL); + } + + 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++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + /* XXX ich9 supports only SATA */ + pci_piix3_ide_init(pci_bus, hd, -1); + +#ifdef HAS_AUDIO + pc_audio_init(pci_bus, isa_irq); +#endif + + pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, hd, + floppy_controller, rtc_state); + + if (usb_enabled) { + /* XXX: make ich9 specific add usb_uchi_ich9_init() */ + usb_uhci_piix4_init(pci_bus, -1); + } + + if (acpi_enabled) { + uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */ + i2c_bus *smbus; + + /* TODO: Populate SPD eeprom data. */ + /* XXX determine proper smb_io_base */ + smbus = ich9_smb_init(host_bus, ich9_lpc_devfn + 3, 0xb100); + for (i = 0; i < 8; i++) { + DeviceState *eeprom; + eeprom = qdev_create((BusState *)smbus, "smbus-eeprom"); + qdev_prop_set_uint32(eeprom, "address", 0x50 + i); + qdev_prop_set_ptr(eeprom, "data", eeprom_buf + (i * 256)); + qdev_init(eeprom); + } + } + + if (gmch_state) { + gmch_init_memory_mappings(gmch_state); + } + + pc_pci_device_init(pci_bus); +} + +static QEMUMachine pc_q35_machine = { + .name = "pc_q35", + .desc = "Q35 chipset PC", + .init = pc_q35_init, + .max_cpus = 255, +}; + +static void pc_q35_machine_init(void) +{ + qemu_register_machine(&pc_q35_machine); +} + +machine_init(pc_q35_machine_init); diff --git a/hw/pci_ids.h b/hw/pci_ids.h index 7eef2cd..4eb6d07 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -33,6 +33,7 @@ #define PCI_CLASS_BRIDGE_HOST 0x0600 #define PCI_CLASS_BRIDGE_ISA 0x0601 #define PCI_CLASS_BRIDGE_PCI 0x0604 +#define PCI_CLASS_BRDIGE_PCI_INF_SUB 0x01 #define PCI_CLASS_BRIDGE_OTHER 0x0680 #define PCI_CLASS_COMMUNICATION_OTHER 0x0780 @@ -109,4 +110,16 @@ #define PCI_DEVICE_ID_INTEL_82371AB_2 0x7112 #define PCI_DEVICE_ID_INTEL_82371AB_3 0x7113 +#define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910 +#define PCI_DEVICE_ID_INTEL_ICH9_1 0x2917 +#define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912 +#define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913 +#define PCI_DEVICE_ID_INTEL_ICH9_4 0x2914 +#define PCI_DEVICE_ID_INTEL_ICH9_5 0x2919 +#define PCI_DEVICE_ID_INTEL_ICH9_6 0x2930 +#define PCI_DEVICE_ID_INTEL_ICH9_7 0x2916 +#define PCI_DEVICE_ID_INTEL_ICH9_8 0x2918 + +#define PCI_DEVICE_ID_INTEL_Q35_MCH 0x29c0 + #define PCI_VENDOR_ID_INVALID 0xffff diff --git a/hw/q35.c b/hw/q35.c new file mode 100644 index 0000000..8e07048 --- /dev/null +++ b/hw/q35.c @@ -0,0 +1,731 @@ +/* + * QEMU GMCH/ICH9 PCI Bridge Emulation + * + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This is based on piix_pci.c, but heavily modified. + * + * 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 + */ +/* + * 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 "isa.h" +#include "sysbus.h" +#include "pc.h" +#include "pc_apm.h" +#include "pci.h" +#include "pci_bridge.h" +#include "q35.h" +#include "acpi.h" + +typedef uint32_t pci_addr_t; +#include "pci_host_c.h" + +struct ICH9_LPCState { + /* ICH9 LPC PCI to ISA bridge */ + PCIDevice d; + + int pci_irq_levels[ICH9_LPC_NB_PIRQS]; + + APMState apm; + struct ich9_lpc_pm_regs pm; +}; + +struct ICH9_LPCIrqState { + struct ICH9_LPCState *lpc; + qemu_irq *pic; +}; + +typedef struct GMCHState { + PCIExpressHost host; + + struct PCIDevice *dev; +} GMCHState; + +struct GMCH_PCIState { + PCIDevice d; + //GMCHState *gmch; + // PCIDevice -> qdev -> parent_bus -upcast-> GMCHState + + target_phys_addr_t isa_page_descs[384 / 4]; + uint8_t smm_enabled; + + struct ICH9_LPCIrqState *irq_state; +}; + +static GMCHState *gmchstate_from_sysbus(SysBusDevice *dev) +{ + PCIHostState *pci = FROM_SYSBUS(PCIHostState, dev); + PCIExpressHost *pcie = DO_UPCAST(PCIExpressHost, pci, pci); + return DO_UPCAST(GMCHState, host, pcie); +} + +/* pci */ +static void gmch_addr_writel(void* opaque, uint32_t addr, uint32_t val) +{ + GMCHState *s = opaque; + s->host.pci.config_reg = val; +} + +static uint32_t gmch_addr_readl(void* opaque, uint32_t addr) +{ + GMCHState *s = opaque; + return s->host.pci.config_reg; +} + +/* PCIE MMCFG */ +static void gmch_update_pcixbar(struct GMCH_PCIState *gs) +{ + PCIDevice* pci_dev = &gs->d; + BusState *bus = qdev_get_parent_bus(&pci_dev->qdev); + DeviceState *qdev = bus->parent; + GMCHState *s = gmchstate_from_sysbus(sysbus_from_qdev(qdev)); + + uint64_t pciexbar; + int enable; + uint64_t addr; + uint64_t addr_mask; + uint32_t length; + + pciexbar = pci_config_get_quad(pci_dev, Q35_HOST_BRIDGE_PCIEXBAR); + enable = pciexbar & Q35_HOST_BRIDGE_PCIEXBAREN; + + addr_mask = Q35_HOST_BRIDGE_PCIEXBAR_ADMSK; + switch (pciexbar & Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_MASK) { + case Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_256M: + length = 256 * 1024 * 1024; + break; + case Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_128M: + length = 128 * 1024 * 1024; + addr_mask |= Q35_HOST_BRIDGE_PCIEXBAR_128ADMSK | + Q35_HOST_BRIDGE_PCIEXBAR_64ADMSK; + break; + case Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_64M: + length = 64 * 1024 * 1024; + addr_mask |= Q35_HOST_BRIDGE_PCIEXBAR_64ADMSK; + break; + case Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_RVD: + default: + enable = 0; + length = 0; + abort(); + break; + } + addr = pciexbar & addr_mask; + + pcie_host_mmcfg_update(&s->host, enable, addr, length); +} + +/* ich9 irq */ +static struct ICH9_LPCState *ich9_lpc_init(PCIBus *bus, int devfn, struct ICH9_LPCIrqState *irq_state); +static void ich9_lpc_set_irq(void *opaque, 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) +{ + return pci_swizzle_map_irq_fn(pci_dev, irq_num); +} + +/* PAM */ + +/* 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 = + [Q35_HOST_BRIDGE_SMARM_C_BASE, Q35_HOST_BRIDGE_UPPER_SYSTEM_BIOS_END) + range */ +void gmch_init_memory_mappings(PCIDevice *d) +{ + struct GMCH_PCIState *gs = DO_UPCAST(struct GMCH_PCIState, d, d); + int i; + for(i = 0; i < 96; i++) { + gs->isa_page_descs[i] = cpu_get_physical_page_desc( + Q35_HOST_BRIDGE_SMARM_C_BASE + i * 0x1000); + } +} + +static target_phys_addr_t isa_page_descs_get(struct GMCH_PCIState *gs, + uint32_t addr) +{ + return gs->isa_page_descs[ + (addr - Q35_HOST_BRIDGE_SMARM_C_BASE) >> TARGET_PAGE_BITS]; +} + +static void update_pam(struct GMCH_PCIState *gs, + uint32_t start, uint32_t size, uint8_t r) +{ + uint32_t addr; + +#if 0 + printf("ISA mapping %08"PRIx32"-0x%08"PRIx32": %"PRId32"\n", + start, start + size, r); +#endif + switch(r) { + case Q35_HOST_BRIDGE_PAM_WE | Q35_HOST_BRIDGE_PAM_RE: + /* RAM */ + cpu_register_physical_memory(start, size, start); + break; + + case Q35_HOST_BRIDGE_PAM_RE: + /* ROM (XXX: not quite correct) */ + cpu_register_physical_memory(start, size, start | IO_MEM_ROM); + break; + + case Q35_HOST_BRIDGE_PAM_WE: + case 0: + /* XXX: should distinguish read/write cases */ + for(addr = start; addr < start + size; addr += TARGET_PAGE_SIZE) { + cpu_register_physical_memory(addr, TARGET_PAGE_SIZE, + isa_page_descs_get(gs, addr)); + } + break; + + default: + abort(); + break; + } +} + +static uint8_t gmch_get_pam_attr(PCIDevice *d, uint32_t addr, int hi) +{ + return (d->config[addr] >> ((!!hi) * 4)) & Q35_HOST_BRIDGE_PAM_MASK; +} + +static void gmch_update_pam(struct GMCH_PCIState *gs, int pam) +{ + PCIDevice *d = &gs->d; + uint32_t conf_addr = Q35_HOST_BRIDGE_PAM0 + pam; + uint32_t phys_addr; + + assert(0 <= pam && pam <= 6); + + if (pam == 0) { + update_pam(gs, Q35_HOST_BRIDGE_PAM_BIOS_AREA, + Q35_HOST_BRIDGE_PAM_AREA_SIZE, + gmch_get_pam_attr(d, conf_addr, 1)); + return; + } + + phys_addr = Q35_HOST_BRIDGE_PAM_EXPAN_AREA + + Q35_HOST_BRIDGE_PAM_EXPAN_SIZE * (pam - 1) * 2; + update_pam(gs, phys_addr, Q35_HOST_BRIDGE_PAM_EXPAN_SIZE, + gmch_get_pam_attr(d, conf_addr, 0)); + + phys_addr += Q35_HOST_BRIDGE_PAM_AREA_SIZE; + update_pam(gs, phys_addr, Q35_HOST_BRIDGE_PAM_EXPAN_SIZE, + gmch_get_pam_attr(d, conf_addr, 1)); +} + +static void gmch_update_memory_mappings(struct GMCH_PCIState *gs) +{ + int i; + + for (i = 0; i < Q35_HOST_BRIDGE_PAM_NB; i++) + gmch_update_pam(gs, i); +} + +/* SMRAM */ +static void gmch_update_smram(struct GMCH_PCIState *gs) +{ + uint32_t smram; + uint32_t addr; + + smram = gs->d.config[Q35_HOST_BRDIGE_SMRAM]; + if ((gs->smm_enabled && (smram & Q35_HOST_BRIDGE_SMRAM_G_SMRAME)) || + (smram & Q35_HOST_BRIDGE_SMRAM_D_OPEN)) { + cpu_register_physical_memory(Q35_HOST_BRIDGE_SMARM_C_BASE, + Q35_HOST_BRIDGE_SMRAM_C_SIZE, + Q35_HOST_BRIDGE_SMARM_C_BASE); + } else { + for(addr = Q35_HOST_BRIDGE_SMARM_C_BASE; + addr < Q35_HOST_BRIDGE_SMRAM_C_END; + addr += TARGET_PAGE_SIZE) { + cpu_register_physical_memory(addr, TARGET_PAGE_SIZE, + isa_page_descs_get(gs, addr)); + } + } +} + +static void gmch_set_smm(int smm, void *arg) +{ + uint8_t val = (smm != 0); + struct GMCH_PCIState *gs = (struct GMCH_PCIState*)arg; + + if (gs->smm_enabled != val) { + gs->smm_enabled = val; + gmch_update_smram(gs); + } +} + +static void gmch_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + struct GMCH_PCIState *gs = DO_UPCAST(struct GMCH_PCIState, d, d); + + /* XXX: implement SMRAM.D_LOCK */ + pci_default_write_config(d, address, val, len); + + if (pci_config_changed(address, len, + Q35_HOST_BRIDGE_PAM0, Q35_HOST_BRIDGE_PAM6)) + gmch_update_memory_mappings(gs); + + if (pci_config_changed_with_size(address, len, Q35_HOST_BRIDGE_PCIEXBAR, + Q35_HOST_BRIDGE_PCIEXBAR_SIZE)) + gmch_update_pcixbar(gs); + + if (pci_config_changed_with_size(address, len, Q35_HOST_BRDIGE_SMRAM, + Q35_HOST_BRDIGE_SMRAM_SIZE)) + gmch_update_smram(gs); +} + +static int gmch_post_load(void *opaque) +{ + struct GMCH_PCIState *gs = opaque; + gmch_update_memory_mappings(gs); + gmch_update_smram(gs); + return 0; +} + +static const VMStateDescription vmstate_gmch = { + .name = "gmch", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = gmch_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(d, struct GMCH_PCIState), + VMSTATE_UINT8(smm_enabled, struct GMCH_PCIState), + VMSTATE_END_OF_LIST() + } +}; + +static int gmch_pcihost_initfn(SysBusDevice *dev) +{ + GMCHState *s = gmchstate_from_sysbus(dev); + + register_ioport_write(Q35_HOST_BRIDGE_CONFIG_ADDR, + 4, 4, gmch_addr_writel, s); + register_ioport_read(Q35_HOST_BRIDGE_CONFIG_ADDR, + 4, 4, gmch_addr_readl, s); + + register_ioport_write(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 1, pci_host_data_writeb, s); + register_ioport_write(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 2, pci_host_data_writew, s); + register_ioport_write(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 4, pci_host_data_writel, s); + register_ioport_read(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 1, pci_host_data_readb, s); + register_ioport_read(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 2, pci_host_data_readw, s); + register_ioport_read(Q35_HOST_BRIDGE_CONFIG_DATA, + 4, 4, pci_host_data_readl, s); + + if (pcie_host_init(&s->host, NULL, NULL) < 0) + abort(); + + return 0; +} + +static int gmch_initfn(PCIDevice *d) +{ + struct GMCH_PCIState *gs = DO_UPCAST(struct GMCH_PCIState, d, d); + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_INTEL_Q35_MCH); + d->config[PCI_REVISION_ID] = Q35_HOST_BRIDGE_REVISION_DEFUALT; + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; + + pci_config_set_quad(d, Q35_HOST_BRIDGE_PCIEXBAR, + Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT); + gmch_update_pcixbar(gs); + + d->config[Q35_HOST_BRDIGE_SMRAM] = Q35_HOST_BRIDGE_SMRAM_DEFAULT; + + vmstate_register(0, &vmstate_gmch, gs); + cpu_smm_register(&gmch_set_smm, gs); + + return 0; +} + +/* host bridge */ +PCIBus *gmch_init(PCIDevice **pgmch_state, qemu_irq *pic, int *ich9_lpc_devfn) +{ + DeviceState *dev; + GMCHState *s; + PCIBus *b; + PCIDevice *d; + struct GMCH_PCIState *gs; + struct ICH9_LPCIrqState *irq_state = qemu_malloc(sizeof(*irq_state)); + + irq_state->pic = pic; + dev = qdev_create(NULL, "gmch-pcihost"); + s = gmchstate_from_sysbus(sysbus_from_qdev(dev)); + b = pci_register_bus(dev, "pci.0", + ich9_lpc_set_irq, pci_slot_get_pirq, irq_state, 0, + 24 /* 24 pin IO APIC */); + s->host.pci.bus = b; + qdev_init(dev); + + d = pci_create_simple(b, 0, "gmch"); + s->dev = d; + gs = DO_UPCAST(struct GMCH_PCIState, d, d); + gs->irq_state = irq_state; + *pgmch_state = d; + + irq_state->lpc = ich9_lpc_init(b, PCI_DEVFN(ICH9_LPC_DEV, ICH9_LPC_FUNC), + irq_state); + *ich9_lpc_devfn = irq_state->lpc->d.devfn; + return b; +} + +static SysBusDeviceInfo gmch_pcihost_info = { + .init = gmch_pcihost_initfn, + .qdev.name = "gmch-pcihost", + .qdev.size = sizeof(GMCHState), + .qdev.no_user = 1, + .qdev.props = (Property[]) { + { + .name = "MCFG", + .info = &qdev_prop_uint64, + .offset = offsetof(GMCHState, host.base_addr), + .defval = (uint64_t[]){ Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT }, + }, + {/* end of list */} + }, +}; + +static PCIDeviceInfo gmch_info = { + .qdev.name = "gmch", + .qdev.desc = "Host bridge", + .qdev.size = sizeof(struct GMCH_PCIState), + .qdev.no_user = 1, + .init = gmch_initfn, + .config_write = gmch_write_config, +}; + +static void gmch_register(void) +{ + sysbus_register_withprop(&gmch_pcihost_info); + pci_qdev_register(&gmch_info); +} +device_init(gmch_register); + +/*****************************************************************************/ +/* ICH9 DMI-to-PCI bridge */ +static void ich9_d2pbr_reset(PCIDevice *d) +{ + pci_config_set_word(d, PCI_COMMAND, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER); + pci_config_set_word(d, PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM); +} + +PCIBus *ich9_d2pbr_init(PCIBus *bus, int devfn, int secondary_bus_num) +{ + PCIBus *b; + PCIDevice *d; + + b = pci_bridge_create_simple(bus, devfn, + PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_82801BA_11, + secondary_bus_num, + secondary_bus_num, + pci_swizzle_map_irq_fn, + "Intel ich9 dmi to pci bridge", + PCI_BRIDGE_INTEL_82801BA_11); + if (b == NULL) + return NULL; + + d = pci_bus_to_dev(b); + vmstate_register(0, &vmstate_pci_device, d); /* current s2pbr doesn't have + specfic bridge status */ + + + /* some command bits are hotwired to 0 for pcie */ + pci_conf_initw(d, PCI_COMMAND, + PCI_COMMAND_IO | + PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER | + PCI_COMMAND_PARITY | + PCI_COMMAND_SERR); + + pci_config_set_byte(d, PCI_REVISION_ID, ICH9_D2P_A2_REVISION); + + // XXX + pci_config_set_byte(d, PCI_CLASS_PROG, PCI_CLASS_BRDIGE_PCI_INF_SUB); + pci_config_set_byte(d, PCI_HEADER_TYPE, PCI_HEADER_TYPE_BRIDGE); + + ich9_d2pbr_reset(d); + return b; +} + + +/*****************************************************************************/ +/* ICH9 LPC PCI to ISA bridge */ + +static struct ICH9_LPCState *ich9_pci_to_lpc(PCIDevice *d) +{ + return DO_UPCAST(struct ICH9_LPCState, d, d); +} + +static void ich9_lpc_reset(struct ICH9_LPCState *lpc) +{ + PCIDevice *d = &lpc->d; + int i; + + pci_config_set_word(d, PCI_COMMAND, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER); + + /* XXX capability isn't supported yet */ + pci_config_set_word(d, PCI_STATUS, + PCI_STATUS_DEVSEL_MEDIUM /* | PCI_STATUS_CAP_LIST */); + + for (i = 0; i < 4; i++) { + pci_config_set_byte(d, ICH9_LPC_PIRQA_ROUT + i, + ICH9_LPC_PIRQ_ROUT_DEFAULT); + } + for (i = 0; i < 4; i++) { + pci_config_set_byte(d, ICH9_LPC_PIRQE_ROUT + i, + ICH9_LPC_PIRQ_ROUT_DEFAULT); + } + pci_config_set_long(d, ICH9_LPC_PMBASE, ICH9_LPC_PMBASE_DEFAULT); + pci_config_set_long(d, ICH9_LPC_RCBA, ICH9_LPC_RCBA_DEFAULT); +} + +static void ich9_lpc_rout(uint8_t pirq_rout, int *pic_irq, int *pic_dis) +{ + *pic_irq = pirq_rout & ICH9_LPC_PIRQ_ROUT_MASK; + *pic_dis = pirq_rout & ICH9_LPC_PIRQ_ROUT_IRQEN; +} + +static void ich9_lpc_pic_irq(struct ICH9_LPCState *lpc, int irq_num, + int *pic_irq, int *pic_dis) +{ + switch (irq_num) { + case 0 ... 3: /* A-D */ + ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQA_ROUT + irq_num], + pic_irq, pic_dis); + return; + case 4 ... 7: /* E-H */ + ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQE_ROUT + (irq_num - 4)], + pic_irq, pic_dis); + return; + default: + break; + } + abort(); +} + +static void ich9_lpc_set_irq(void *opaque, int irq_num, int level) +{ + struct ICH9_LPCIrqState *irq_state = opaque; + struct ICH9_LPCState *ich9_lpc = irq_state->lpc; + qemu_irq *pic = irq_state->pic; + int i, pic_level; + int pic_irq; + int pic_dis; + + assert(0 <= irq_num && irq_num < 8); + ich9_lpc->pci_irq_levels[irq_num] = level; + + /* now we change the pic irq level according to + the ich9 lpc irq mappings */ + ich9_lpc_pic_irq(ich9_lpc, irq_num, &pic_irq, &pic_dis); + if (pic_dis) { + return; + } + + if (pic_irq > 24) { + fprintf(stderr, "%s: error irq_num %d pic_irq %d\n", + __func__, irq_num, pic_irq); + return; + } + + /* The pic level is the logical OR of all the PCI irqs mapped to it */ + pic_level = 0; + for (i = 0; i < ICH9_LPC_NB_PIRQS; i++) { + int tmp_irq; + int tmp_dis; + ich9_lpc_pic_irq(ich9_lpc, i, &tmp_irq, &tmp_dis); + if (!tmp_dis && pic_irq == tmp_irq) { + pic_level |= ich9_lpc->pci_irq_levels[i]; + } + } + qemu_set_irq(pic[pic_irq], pic_level); +} + +/* APM */ +static void ich9_apm_ctrl_changed(uint32_t val, void *arg) +{ + struct ICH9_LPCState *lpc = arg; + + /* ACPI specs 3.0, 4.7.2.5 */ + if (val == ICH9_APM_ACPI_ENABLE) { + lpc->pm.pm1_cnt |= ACPI_BITMASK_SCI_ENABLE; + } else if (val == ICH9_APM_ACPI_DISABLE) { + lpc->pm.pm1_cnt &= ~ACPI_BITMASK_SCI_ENABLE; + } + + /* SMI_EN = PMBASE + 30. SMI control and enable register */ + if (lpc->pm.smi_en & ICH9_PMIO_SMI_EN_APMC_EN) { + cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI); + } +} + +/* config:PMBASE */ +static void +ich9_lpc_pmbase_update(PCIDevice *d) +{ + uint32_t pm_io_base = pci_config_get_long(d, ICH9_LPC_PMBASE); + pm_io_base &= ICH9_LPC_PMBASE_BASE_ADDRESS_MASK; + + ich9_pm_iospace_update(&ich9_pci_to_lpc(d)->pm, pm_io_base); +} + +static int ich9_lpc_post_load(void *opaque) +{ + struct ICH9_LPCState *lpc = opaque; + + ich9_lpc_pmbase_update(&lpc->d); + return 0; +} + +static const VMStateDescription vmstate_ich9_lpc = { + .name = "ICH9LPC", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = ich9_lpc_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(d, struct ICH9_LPCState), + VMSTATE_INT32_ARRAY(pci_irq_levels, struct ICH9_LPCState, + ICH9_LPC_NB_PIRQS), + VMSTATE_STRUCT(apm, struct ICH9_LPCState, 0, + vmstate_pc_apm, APMState), + VMSTATE_STRUCT(pm, struct ICH9_LPCState, 0, + vmstate_ich9_pm, struct ich9_lpc_pm_regs), + VMSTATE_END_OF_LIST() + } +}; + +static void ich9_set_sci(void *opaque, int irq_num, int level) +{ + /* opaque = pic */ + assert(irq_num == 0); + ich9_lpc_set_irq(opaque, 1/* SCI = PIRQB# */, level); +} + +static qemu_irq *ich9_lpc_allocate_sci_irq(struct ICH9_LPCIrqState *irq_state) +{ + return qemu_allocate_irqs(ich9_set_sci, irq_state, 1); +} + +static void ich9_lpc_config_write( PCIDevice *d, + uint32_t addr, uint32_t val, int len) +{ + pci_default_write_config(d, addr, val, len); + if (pci_config_changed_with_size(addr, len, ICH9_LPC_PMBASE, 4)) { + ich9_lpc_pmbase_update(d); + } +} + +static int ich9_lpc_initfn(PCIDevice *d) +{ + struct ICH9_LPCState *lpc = ich9_pci_to_lpc(d); + //ich9_lpc_set_irq_register(lpc); /* for ich9_lpc_set_irq XXX */ + + isa_bus_new(&d->qdev); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_INTEL_ICH9_8); /* ICH9 LPC */ + pci_config_set_byte(d, PCI_REVISION_ID, ICH9_A2_LPC_REVISION); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_ISA); + + /* header_type = PCI_multifunction, generic */ + pci_config_set_byte(d, PCI_HEADER_TYPE, + PCI_HEADER_TYPE_NORMAL | + PCI_HEADER_TYPE_MULTI_FUNCTION); + + apm_init(&lpc->apm, ich9_apm_ctrl_changed, lpc); + + + pci_conf_initl(d, ICH9_LPC_PMBASE, ICH9_LPC_PMBASE_BASE_ADDRESS_MASK); + pci_config_set_long(d, ICH9_LPC_PMBASE, ICH9_LPC_PMBASE_DEFAULT); + + vmstate_register(0, &vmstate_ich9_lpc, lpc); + + return 0; +} + +static struct ICH9_LPCState * +ich9_lpc_init(PCIBus *bus, int devfn, struct ICH9_LPCIrqState *irq_state) +{ + PCIDevice *d; + struct ICH9_LPCState *lpc; + qemu_irq *sci_irq; + + assert(devfn == PCI_DEVFN(ICH9_LPC_DEV, ICH9_LPC_FUNC)); + d = pci_create_simple(bus, devfn, "ICH9 LPC"); + lpc = ich9_pci_to_lpc(d); + + sci_irq = ich9_lpc_allocate_sci_irq(irq_state); + ich9_pm_init(&lpc->pm, sci_irq[0]); + ich9_lpc_reset(lpc); + ich9_hot_add_init(&lpc->pm); + + return lpc; +} + +static PCIDeviceInfo ich9_lpc_info = { + .qdev.name = "ICH9 LPC", + .qdev.desc = "ICH9 LPC bridge", + .qdev.size = sizeof(struct ICH9_LPCState), + .qdev.no_user = 1, + .init = ich9_lpc_initfn, + .config_write = ich9_lpc_config_write, +}; + + +static void ich9_lpc_register(void) +{ + pci_qdev_register(&ich9_lpc_info); +} + +device_init(ich9_lpc_register); diff --git a/hw/q35.h b/hw/q35.h new file mode 100644 index 0000000..a3a97b5 --- /dev/null +++ b/hw/q35.h @@ -0,0 +1,228 @@ +/* + * q35.h + * + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef HW_Q35_H +#define HW_Q35_H + +#include "acpi_ich9.h" + +void gmch_init_memory_mappings(PCIDevice *d); +PCIBus *gmch_init(PCIDevice **pgmch_state, qemu_irq *pic, int *ich9_lpc_devfn); + +PCIBus *ich9_d2pbr_init(PCIBus *bus, int devfn, int secondary_bus_num); + +i2c_bus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base); + +void ich9_pm_init(struct ich9_lpc_pm_regs *pm, qemu_irq sci_irq); +void ich9_pm_iospace_update(struct ich9_lpc_pm_regs *pm, uint32_t pm_io_base); +extern const VMStateDescription vmstate_ich9_pm; + +void ich9_hot_add_init(struct ich9_lpc_pm_regs *pm); + +#define Q35_MASK(bit, ms_bit, ls_bit) ((uint##bit##_t)(((1ULL << ((ms_bit) + 1)) - 1) & ~((1ULL << ls_bit) - 1))) + +/* + * gmch part + */ + +/* PCI configuration */ +#define Q35_HOST_BRIDGE "GMCH" + +#define Q35_HOST_BRIDGE_CONFIG_ADDR 0xcf8 +#define Q35_HOST_BRIDGE_CONFIG_DATA 0xcfc + +/* D0:F0 configuration space */ +#define Q35_HOST_BRIDGE_REVISION_DEFUALT 0x0 + +#define Q35_HOST_BRIDGE_PCIEXBAR 0x60 /* 64bit register */ +#define Q35_HOST_BRIDGE_PCIEXBAR_SIZE 8 /* 64bit register */ +#define Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT 0xe0000000 +//#define Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT (0xe0000000 | Q35_HOST_BRIDGE_PCIEXBAREN) +//#define Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT (0xf0000000 | Q35_HOST_BRIDGE_PCIEXBAREN) +//#define Q35_HOST_BRIDGE_PCIEXBAR_DEFAULT (0xf4000000 | Q35_HOST_BRIDGE_PCIEXBAREN) +#define Q35_HOST_BRIDGE_PCIEXBAR_ADMSK Q35_MASK(64, 35, 25) /* bit 35:28 */ +#define Q35_HOST_BRIDGE_PCIEXBAR_128ADMSK ((uint64_t)(1 << 26)) +#define Q35_HOST_BRIDGE_PCIEXBAR_64ADMSK ((uint64_t)(1 << 25)) +#define Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_MASK ((uint64_t)(0x3 << 1)) +#define Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_256M ((uint64_t)(0x0 << 1)) +#define Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_128M ((uint64_t)(0x1 << 1)) +#define Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_64M ((uint64_t)(0x2 << 1)) +#define Q35_HOST_BRIDGE_PCIEXBAR_LENGTH_RVD ((uint64_t)(0x3 << 1)) +#define Q35_HOST_BRIDGE_PCIEXBAREN ((uint64_t)1) + +#define Q35_HOST_BRIDGE_PAM_NB 7 +#define Q35_HOST_BRIDGE_PAM0 0x90 +#define Q35_HOST_BRIDGE_PAM_BIOS_AREA 0xf0000 +#define Q35_HOST_BRIDGE_PAM_AREA_SIZE 0x10000 /* 16KB */ +#define Q35_HOST_BRIDGE_PAM1 0x91 +#define Q35_HOST_BRIDGE_PAM_EXPAN_AREA 0xc0000 +#define Q35_HOST_BRIDGE_PAM_EXPAN_SIZE 0x04000 +#define Q35_HOST_BRIDGE_PAM2 0x92 +#define Q35_HOST_BRIDGE_PAM3 0x93 +#define Q35_HOST_BRIDGE_PAM4 0x94 +#define Q35_HOST_BRIDGE_PAM_EXBIOS_AREA 0xe0000 +#define Q35_HOST_BRIDGE_PAM_EXBIOS_SIZE 0x04000 +#define Q35_HOST_BRIDGE_PAM5 0x95 +#define Q35_HOST_BRIDGE_PAM6 0x96 +#define Q35_HOST_BRIDGE_PAM_WE_HI ((uint8_t)(0x2 << 4)) +#define Q35_HOST_BRIDGE_PAM_RE_HI ((uint8_t)(0x1 << 4)) +#define Q35_HOST_BRIDGE_PAM_HI_MASK ((uint8_t)(0x3 << 4)) +#define Q35_HOST_BRIDGE_PAM_WE_LO ((uint8_t)0x2) +#define Q35_HOST_BRIDGE_PAM_RE_LO ((uint8_t)0x1) +#define Q35_HOST_BRIDGE_PAM_LO_MASK ((uint8_t)0x3) +#define Q35_HOST_BRIDGE_PAM_WE ((uint8_t)0x2) +#define Q35_HOST_BRIDGE_PAM_RE ((uint8_t)0x1) +#define Q35_HOST_BRIDGE_PAM_MASK ((uint8_t)0x3) + + +#define Q35_HOST_BRDIGE_SMRAM 0x9d +#define Q35_HOST_BRDIGE_SMRAM_SIZE 1 +#define Q35_HOST_BRIDGE_SMRAM_DEFAULT ((uint8_t)0x2) +#define Q35_HOST_BRIDGE_SMRAM_D_OPEN ((uint8_t)(1 << 6)) +#define Q35_HOST_BRIDGE_SMRAM_D_CLS ((uint8_t)(1 << 5)) +#define Q35_HOST_BRIDGE_SMRAM_D_LCK ((uint8_t)(1 << 4)) +#define Q35_HOST_BRIDGE_SMRAM_G_SMRAME ((uint8_t)(1 << 3)) +#define Q35_HOST_BRIDGE_SMRAM_C_BASE_SEG_MASK ((uint8_t)0x7) +#define Q35_HOST_BRIDGE_SMRAM_C_BASE_SEG ((uint8_t)0x2) /* hardwired to b010 */ +#define Q35_HOST_BRIDGE_SMARM_C_BASE 0xa0000 +#define Q35_HOST_BRIDGE_SMRAM_C_END 0xc0000 +#define Q35_HOST_BRIDGE_SMRAM_C_SIZE 0x20000 +#define Q35_HOST_BRIDGE_UPPER_SYSTEM_BIOS_END 0x100000 + +#define Q35_HOST_BRIDGE_ESMRAMC 0x9e +#define Q35_HOST_BRDIGE_ESMRAMC_H_SMRAME ((uint8_t)(1 << 6)) +#define Q35_HOST_BRDIGE_ESMRAMC_E_SMERR ((uint8_t)(1 << 5)) +#define Q35_HOST_BRDIGE_ESMRAMC_SM_CACHE ((uint8_t)(1 << 4)) +#define Q35_HOST_BRDIGE_ESMRAMC_SM_L1 ((uint8_t)(1 << 3)) +#define Q35_HOST_BRDIGE_ESMRAMC_SM_L2 ((uint8_t)(1 << 2)) +#define Q35_HOST_BRDIGE_ESMRAMC_TSEG_SZ_MASK ((uint8_t)(0x3 << 1)) +#define Q35_HOST_BRDIGE_ESMRAMC_TSEG_SZ_1MB ((uint8_t)(0x0 << 1)) +#define Q35_HOST_BRDIGE_ESMRAMC_TSEG_SZ_2MB ((uint8_t)(0x1 << 1)) +#define Q35_HOST_BRDIGE_ESMRAMC_TSEG_SZ_8MB ((uint8_t)(0x2 << 1)) +#define Q35_HOST_BRDIGE_ESMRAMC_T_EN ((uint8_t)1) + + +/* + * ich9 part + */ + +/* D30:F0 DMI-to-PCI brdige */ +#define ICH9_D2P_BRIDGE "ICH9 D2P BRIDGE" +#define ICH9_D2P_BRIDGE_SAVEVM_VERSION 0 + +#define ICH9_D2P_BRIDGE_DEV 30 +#define ICH9_D2P_BRIDGE_FUNC 0 + +/* 1 pcie port on gmch and 6 pcie port on ich9, + the next bus # is 7 */ +#define ICH9_D2P_SECONDARY_DEFAULT 6 + +#define ICH9_D2P_A2_REVISION 0x92 + + +/* D30:F1 LPC controller */ +#define ICH9_A2_LPC "ICH9 A2 LPC" +#define ICH9_A2_LPC_SAVEVM_VERSION 0 + +#define ICH9_LPC_DEV 31 +#define ICH9_LPC_FUNC 0 + +#define ICH9_A2_LPC_REVISION 0x2 +#define ICH9_LPC_NB_PIRQS 8 /* PCI A-H */ + +#define ICH9_LPC_PMBASE 0x40 +#define ICH9_LPC_PMBASE_BASE_ADDRESS_MASK Q35_MASK(32, 15, 7) +#define ICH9_LPC_PMBASE_RTE 0x1 +#define ICH9_LPC_PMBASE_DEFAULT 0x1 +#define ICH9_LPC_ACPI_CTRL 0x44 +#define ICH9_LPC_ACPI_CTRL_ACPI_EN 0x80 +#define ICH9_LPC_ACPI_CTRL_SCI_IRQ_SEL_MASK Q35_MASK(8, 2, 0) +#define ICH9_LPC_ACPI_CTRL_DEFAULT 0x0 + +#define ICH9_LPC_PIRQA_ROUT 0x60 +#define ICH9_LPC_PIRQB_ROUT 0x61 +#define ICH9_LPC_PIRQC_ROUT 0x62 +#define ICH9_LPC_PIRQD_ROUT 0x63 + +#define ICH9_LPC_PIRQE_ROUT 0x68 +#define ICH9_LPC_PIRQF_ROUT 0x69 +#define ICH9_LPC_PIRQG_ROUT 0x6a +#define ICH9_LPC_PIRQH_ROUT 0x6b + +#define ICH9_LPC_PIRQ_ROUT_IRQEN 0x80 +#define ICH9_LPC_PIRQ_ROUT_MASK Q35_MASK(8, 3, 0) +#define ICH9_LPC_PIRQ_ROUT_DEFAULT 0x80 + +#define ICH9_LPC_RCBA 0xf0 +#define ICH9_LPC_RCBA_BA_MASK Q35_MASK(32, 31, 14) +#define ICH9_LPC_RCBA_EN 0x1 +#define ICH9_LPC_RCBA_DEFAULT 0x0 + +/* D30:F1 power management I/O registers + offset from the address ICH9_LPC_PMBASE */ + +/* ICH9 LPC PM I/O registers are 128 ports and 128-aligned */ +#define ICH9_PMIO_SIZE 128 +#define ICH9_PMIO_MASK (ICH9_PMIO_SIZE - 1) + +#define ICH9_PMIO_PM1_STS 0x00 +#define ICH9_PMIO_PM1_EN 0x02 +#define ICH9_PMIO_PM1_CNT 0x04 +#define ICH9_PMIO_PM1_TMR 0x08 +#define ICH9_PMIO_GPE0_STS 0x20 +#define ICH9_PMIO_GPE0_EN 0x28 +#define ICH9_PMIO_SMI_EN 0x30 +#define ICH9_PMIO_SMI_EN_APMC_EN (1 << 5) +#define ICH9_PMIO_SMI_STS 0x34 + +/* FADT ACPI_ENABLE/ACPI_DISABLE */ +#define ICH9_APM_ACPI_ENABLE 0x2 +#define ICH9_APM_ACPI_DISABLE 0x3 + + +/* D30:F3 SMBus controller */ +#define ICH9_A2_SMB_REVISION 0x02 +#define ICH9_SMB_PI 0x00 + +#define ICH9_SMB_SMBMBAR0 0x10 +#define ICH9_SMB_SMBMBAR1 0x14 +#define ICH9_SMB_SMBM_BAR 0 +#define ICH9_SMB_SMBM_SIZE (1 << 8) +#define ICH9_SMB_SMB_BASE 0x20 +#define ICH9_SMB_SMB_BASE_BAR 4 +#define ICH9_SMB_SMB_BASE_SIZE (1 << 5) +#define ICH9_SMB_HOSTC 0x40 +#define ICH9_SMB_HOSTC_SSRESET ((uint8_t)(1 << 3)) +#define ICH9_SMB_HOSTC_I2C_EN ((uint8_t)(1 << 2)) +#define ICH9_SMB_HOSTC_SMB_SMI_EN ((uint8_t)(1 << 1)) +#define ICH9_SMB_HOSTC_HST_EN ((uint8_t)(1 << 0)) + +/* D30:F3 SMBus I/O and memory mapped I/O registers */ +#define ICH9_SMB_HST_STS 0x00 +#define ICH9_SMB_HST_CNT 0x02 +#define ICH9_SMB_HST_CMD 0x03 +#define ICH9_SMB_XMIT_SLVA 0x04 +#define ICH9_SMB_HST_D0 0x05 +#define ICH9_SMB_HST_D1 0x06 +#define ICH9_SMB_HOST_BLOCK_DB 0x07 + + +#endif /* HW_Q35_H */ diff --git a/hw/q35_smbus.c b/hw/q35_smbus.c new file mode 100644 index 0000000..63ad8d3 --- /dev/null +++ b/hw/q35_smbus.c @@ -0,0 +1,150 @@ +/* + * ACPI implementation + * + * Copyright (c) 2006 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 version 2 as published by the Free Software Foundation. + * + * 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 + */ +/* + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This is based on acpi.c. + */ +#include "hw.h" +#include "pc.h" +#include "pc_smbus.h" +#include "pci.h" +#include "sysemu.h" +#include "i2c.h" +#include "smbus.h" + +#include "q35.h" + +typedef struct ICH9_SMBState { + PCIDevice dev; + + PCSMBus smb; +} ICH9_SMBState; + +static ICH9_SMBState *ich9_pci_to_smb(PCIDevice* pci_dev) +{ + return DO_UPCAST(ICH9_SMBState, dev, pci_dev); +} + +static const VMStateDescription vmstate_ich9_smbus = { + .name = "ich9_smb", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, struct ICH9_SMBState), + VMSTATE_END_OF_LIST() + } +}; + +static void ich9_smb_reset(void *opaque) +{ + //ICH9_SMBState *s = opaque; + //uint8_t *pci_conf = s->dev.config; + + /* XXX not yet */ +} + +static void ich9_smb_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + ICH9_SMBState *s = opaque; + uint8_t hostc = s->dev.config[ICH9_SMB_HOSTC]; + + if (hostc & ICH9_SMB_HOSTC_HST_EN && !(hostc & ICH9_SMB_HOSTC_I2C_EN)) { + uint64_t offset = addr - s->dev.io_regions[ICH9_SMB_SMB_BASE_BAR].addr; + smb_ioport_writeb(&s->smb, offset, val); + } +} + +static uint32_t ich9_smb_ioport_readb(void *opaque, uint32_t addr) +{ + ICH9_SMBState *s = opaque; + uint8_t hostc = s->dev.config[ICH9_SMB_HOSTC]; + + if (hostc & ICH9_SMB_HOSTC_HST_EN && !(hostc & ICH9_SMB_HOSTC_I2C_EN)) { + uint64_t offset = addr - s->dev.io_regions[ICH9_SMB_SMB_BASE_BAR].addr; + return smb_ioport_readb(&s->smb, offset); + } + + return 0xff; +} + +static void ich9_smb_map_ioport(PCIDevice *dev, int region_num, + uint64_t addr, uint64_t size, int type) +{ + ICH9_SMBState *s = ich9_pci_to_smb(dev); + + assert(size == ICH9_SMB_SMB_BASE_SIZE); + assert(type == PCI_ADDRESS_SPACE_IO); + + register_ioport_write(addr, 64, 1, ich9_smb_ioport_writeb, s); + register_ioport_read(addr, 64, 1, ich9_smb_ioport_readb, s); +} + +i2c_bus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base) +{ + ICH9_SMBState *s; + PCIDevice *d; + + s = (ICH9_SMBState *)pci_register_device(bus, + "ICH9 SMB", sizeof(ICH9_SMBState), + devfn, NULL, NULL); + d = &s->dev; + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_INTEL_ICH9_6); + + pci_config_set_word(d, PCI_STATUS, + PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM); + pci_conf_initw(d, PCI_STATUS, + PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY); + + pci_config_set_byte(d, PCI_REVISION_ID, ICH9_A2_SMB_REVISION); + pci_config_set_byte(d, PCI_CLASS_PROG, ICH9_SMB_PI); + + pci_config_set_class(d->config, PCI_CLASS_SERIAL_SMBUS); + pci_config_set_byte(d, PCI_HEADER_TYPE, PCI_HEADER_TYPE_NORMAL); // header_type + + /* XXX: D31IP.SMIP in chipset configuration space */ + pci_config_set_byte(d, PCI_INTERRUPT_PIN, 0x01); // interrupt pin 1 + + pci_config_set_byte(d, ICH9_SMB_HOSTC, 0); + + + /* XXX: + * paralell_hds[0] + * serial_hds[0] + * serial_hds[0] + * fdc + */ + + // XXX smb_io_base + pci_config_set_byte(d, ICH9_SMB_HOSTC, 0); + /* XXX bar0, bar1: 64bit BAR support*/ + pci_register_bar(d, ICH9_SMB_SMB_BASE_BAR, + ICH9_SMB_SMB_BASE_SIZE, PCI_ADDRESS_SPACE_IO, + &ich9_smb_map_ioport); + + vmstate_register(0, &vmstate_ich9_smbus, s); + + pc_smbus_init(&s->smb); + qemu_register_reset(ich9_smb_reset, s); + + return s->smb.smbus; +} -- 1.6.0.2