From patchwork Wed Mar 16 09:29:37 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaku Yamahata X-Patchwork-Id: 87221 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by ozlabs.org (Postfix) with ESMTP id C6E87B7008 for ; Wed, 16 Mar 2011 21:31:32 +1100 (EST) Received: from localhost ([127.0.0.1]:44691 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Pznva-00034q-Th for incoming@patchwork.ozlabs.org; Wed, 16 Mar 2011 06:26:06 -0400 Received: from [140.186.70.92] (port=34480 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Pzn36-0006GW-M9 for qemu-devel@nongnu.org; Wed, 16 Mar 2011 05:29:53 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Pzn30-0006E1-T9 for qemu-devel@nongnu.org; Wed, 16 Mar 2011 05:29:48 -0400 Received: from mail.valinux.co.jp ([210.128.90.3]:37357) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Pzn2z-0006Bc-Es for qemu-devel@nongnu.org; Wed, 16 Mar 2011 05:29:42 -0400 Received: from ps.local.valinux.co.jp (vagw.valinux.co.jp [210.128.90.14]) by mail.valinux.co.jp (Postfix) with SMTP id 99F9B1892F; Wed, 16 Mar 2011 18:29:39 +0900 (JST) Received: (nullmailer pid 18387 invoked by uid 1000); Wed, 16 Mar 2011 09:29:37 -0000 From: Isaku Yamahata To: qemu-devel@nongnu.org Date: Wed, 16 Mar 2011 18:29:37 +0900 Message-Id: X-Mailer: git-send-email 1.7.1.1 In-Reply-To: References: In-Reply-To: References: X-Virus-Scanned: clamav-milter 0.95.2 at va-mail.local.valinux.co.jp X-Virus-Status: Clean X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 210.128.90.3 Cc: yamahata@valinux.co.jp Subject: [Qemu-devel] [PATCH 26/26] pc q35 based chipset emulator X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org pc q35 based chipset emulator to support pci express natively. Signed-off-by: Isaku Yamahata --- Makefile.target | 1 + hw/acpi_ich9.c | 314 ++++++++++++++++++++ hw/acpi_ich9.h | 53 ++++ hw/pc_q35.c | 359 +++++++++++++++++++++++ hw/pci_ids.h | 13 + hw/q35.c | 877 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/q35.h | 271 +++++++++++++++++ hw/q35_smbus.c | 154 ++++++++++ 8 files changed, 2042 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 d57d250..28da59f 100644 --- a/Makefile.target +++ b/Makefile.target @@ -219,6 +219,7 @@ obj-i386-y += vmport.o obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o kvmclock.o +obj-i386-y += pc_q35.o q35.o q35_smbus.o acpi_ich9.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o # shared objects diff --git a/hw/acpi_ich9.c b/hw/acpi_ich9.c new file mode 100644 index 0000000..28db57b --- /dev/null +++ b/hw/acpi_ich9.c @@ -0,0 +1,314 @@ +/* + * 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, see + */ +/* + * 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 + +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 pm_update_sci(ICH9_LPCPmRegs *pm) +{ + int sci_level, pm1a_sts; + + pm1a_sts = acpi_pm1_evt_get_sts(&pm->pm1a, pm->tmr.overflow_time); + + sci_level = (((pm1a_sts & pm->pm1a.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 */ + acpi_pm_tmr_update(&pm->tmr, + (pm->pm1a.en & ACPI_BITMASK_TIMER_ENABLE) && + !(pm1a_sts & ACPI_BITMASK_TIMER_STATUS)); +} + +static void ich9_pm_update_sci_fn(ACPIPMTimer *tmr) +{ + ICH9_LPCPmRegs *pm = container_of(tmr, ICH9_LPCPmRegs, tmr); + pm_update_sci(pm); +} + +static void pm_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + ICH9_LPCPmRegs *pm = opaque; + + switch (addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + ICH9_PMIO_GPE0_LEN - 1): + acpi_gpe_ioport_writeb(&pm->gpe0, 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) +{ + ICH9_LPCPmRegs *pm = opaque; + uint32_t val = 0; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_GPE0_STS ... (ICH9_PMIO_GPE0_STS + ICH9_PMIO_GPE0_LEN - 1): + val = acpi_gpe_ioport_readb(&pm->gpe0, 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) +{ + ICH9_LPCPmRegs *pm = opaque; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_STS: + acpi_pm1_evt_write_sts(&pm->pm1a, &pm->tmr, val); + pm_update_sci(pm); + break; + case ICH9_PMIO_PM1_EN: + pm->pm1a.en = val; + pm_update_sci(pm); + break; + case ICH9_PMIO_PM1_CNT: + acpi_pm1_cnt_write(&pm->pm1a, &pm->pm1_cnt, val); + 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) +{ + ICH9_LPCPmRegs *pm = opaque; + uint32_t val; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_STS: + val = acpi_pm1_evt_get_sts(&pm->pm1a, pm->tmr.overflow_time); + break; + case ICH9_PMIO_PM1_EN: + val = pm->pm1a.en; + break; + case ICH9_PMIO_PM1_CNT: + val = pm->pm1_cnt.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) +{ + ICH9_LPCPmRegs *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) +{ + ICH9_LPCPmRegs *pm = opaque; + uint32_t val; + + switch(addr & ICH9_PMIO_MASK) { + case ICH9_PMIO_PM1_TMR: + val = acpi_pm_tmr_get(&pm->tmr); + 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(ICH9_LPCPmRegs *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); + } + + /* don't map at 0 */ + 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; + acpi_gpe_blk(&pm->gpe0, pm_io_base + ICH9_PMIO_GPE0_STS); +} + +static int ich9_pm_post_load(void *opaque, int version_id) +{ + ICH9_LPCPmRegs *pm = opaque; + uint32_t pm_io_base = pm->pm_io_base; + pm->pm_io_base = 0; + ich9_pm_iospace_update(pm, pm_io_base); + return 0; +} + +#define VMSTATE_GPE_ARRAY(_field, _state) \ + { \ + .name = (stringify(_field)), \ + .version_id = 0, \ + .num = ICH9_PMIO_GPE0_LEN, \ + .info = &vmstate_info_uint8, \ + .size = sizeof(uint8_t), \ + .flags = VMS_ARRAY | VMS_POINTER, \ + .offset = vmstate_offset_pointer(_state, _field, uint8_t), \ + } + +const VMStateDescription vmstate_ich9_pm = { + .name = "ich9_pm", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = ich9_pm_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16(pm1a.sts, ICH9_LPCPmRegs), + VMSTATE_UINT16(pm1a.en, ICH9_LPCPmRegs), + VMSTATE_UINT16(pm1_cnt.cnt, ICH9_LPCPmRegs), + VMSTATE_TIMER(tmr.timer, ICH9_LPCPmRegs), + VMSTATE_INT64(tmr.overflow_time, ICH9_LPCPmRegs), + VMSTATE_GPE_ARRAY(gpe0.sts, ICH9_LPCPmRegs), + VMSTATE_GPE_ARRAY(gpe0.en, ICH9_LPCPmRegs), + VMSTATE_UINT32(smi_en, ICH9_LPCPmRegs), + VMSTATE_UINT32(smi_sts, ICH9_LPCPmRegs), + VMSTATE_END_OF_LIST() + } +}; + +static void pm_reset(void *opaque) +{ + ICH9_LPCPmRegs *pm = opaque; + ich9_pm_iospace_update(pm, 0); + + acpi_pm1_evt_reset(&pm->pm1a); + acpi_pm1_cnt_reset(&pm->pm1_cnt); + acpi_pm_tmr_reset(&pm->tmr); + acpi_gpe_reset(&pm->gpe0); + + pm_update_sci(pm); +} + +static void pm_powerdown(void *opaque, int irq, int power_failing) +{ + ICH9_LPCPmRegs *pm = opaque; + ACPIPM1EVT *pm1a = pm? &pm->pm1a: NULL; + ACPIPMTimer *tmr = pm? &pm->tmr: NULL; + + acpi_pm1_evt_power_down(pm1a, tmr); +} + +void ich9_pm_init(ICH9_LPCPmRegs *pm, qemu_irq sci_irq, qemu_irq cmos_s3) +{ + acpi_pm_tmr_init(&pm->tmr, ich9_pm_update_sci_fn); + acpi_pm1_cnt_init(&pm->pm1_cnt, cmos_s3); + acpi_gpe_init(&pm->gpe0, ICH9_PMIO_GPE0_LEN); + + pm->irq = sci_irq; + qemu_register_reset(pm_reset, pm); + qemu_system_powerdown = *qemu_allocate_irqs(pm_powerdown, pm, 1); +} diff --git a/hw/acpi_ich9.h b/hw/acpi_ich9.h new file mode 100644 index 0000000..f55c0e9 --- /dev/null +++ b/hw/acpi_ich9.h @@ -0,0 +1,53 @@ +/* + * 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, see + */ + +#ifndef HW_ACPI_ICH9_H +#define HW_ACPI_ICH9_H + +#include "acpi.h" + +typedef struct ICH9_LPCPmRegs { + ACPIPM1EVT pm1a; + + /* + * 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. + */ + ACPIPM1CNT pm1_cnt; + + ACPIPMTimer tmr; + + ACPIGPE gpe0; + + uint32_t smi_en; + uint32_t smi_sts; + + qemu_irq irq; /* SCI */ + + uint32_t pm_io_base; +} ICH9_LPCPmRegs; + +void ich9_pm_init(ICH9_LPCPmRegs *pm, + qemu_irq sci_irq, qemu_irq cmos_s3_resume); +void ich9_pm_iospace_update(ICH9_LPCPmRegs *pm, uint32_t pm_io_base); +extern const VMStateDescription vmstate_ich9_pm; + +#endif /* HW_ACPI_ICH9_H */ diff --git a/hw/pc_q35.c b/hw/pc_q35.c new file mode 100644 index 0000000..84dc221 --- /dev/null +++ b/hw/pc_q35.c @@ -0,0 +1,359 @@ +/* + * 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. + */ +/* + * Q35 chipset based pc system emulator + * + * Copyright (c) 2009, 2010 + * 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, see + */ +#include "hw.h" +#include "arch_init.h" +#include "pc.h" +#include "fdc.h" +#include "pci.h" +#include "pci_bridge.h" +#include "pci_p2pbr.h" +#include "ioh3420.h" +#include "xio3130_upstream.h" +#include "xio3130_downstream.h" +#include "block.h" +#include "blockdev.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 "usb-uhci.h" + +#include "q35.h" + +/* ICH9 AHCI has 6 ports */ +#define MAX_SATA_PORTS 6 + +#define I21154_REV 0x05 +#define I21154_PI 0x00 + +static PCIBridge *i21154_init(PCIBus *bus, int devfn, const char *bus_name, + bool multifunction) +{ + const PCIP2PBridgeInit init = { + .bus = bus, + .devfn = devfn, + .multifunction = multifunction, + + .bus_name = bus_name, + .map_irq = pci_swizzle_map_irq_fn, + }; + const PCIP2PBridgeProp prop = { + .vendor_id = PCI_VENDOR_ID_DEC, + .device_id = PCI_DEVICE_ID_DEC_21154, + .revision_id = I21154_REV, + .prog_interface = I21154_PI, + }; + return pci_p2pbr_create_simple(&init, &prop); +} + +static void pc_q35_bridge_init(PCIBus *host_bus, PCIBus *pci_bus) +{ + uint8_t dev; + uint8_t sec_bus; + uint8_t port = 0; + uint8_t chassis = 0; + uint16_t slot = 0; + uint8_t upstream_port; + PCIESlot *s; + uint8_t fn; + PCIESlot *root_port; + PCIBus *root_port_bus; + char buf[16]; + + /* PCI to PCI bridge b6:d[29 - 31]:f0, 6:[1c - 1f].0 with subordinate bus + of 7 - 9 on b0:d30:f0, 0.1e.0 = bus */ +#define Q35_P2P_BRDIGE_DEV_BASE 28 +#define Q35_P2P_BRDIGE_DEV_MAX 32 +#define Q35_P2P_BRDIGE_SUBBUS_BASE (ICH9_D2P_SECONDARY_DEFAULT + 1) + for (dev = Q35_P2P_BRDIGE_DEV_BASE; dev < Q35_P2P_BRDIGE_DEV_MAX; dev++) { + PCIBridge *br; + sec_bus = Q35_P2P_BRDIGE_SUBBUS_BASE + dev - Q35_P2P_BRDIGE_DEV_BASE; + + snprintf(buf, sizeof(buf), "pci.%d", sec_bus); + br = i21154_init(pci_bus, PCI_DEVFN(dev, 0), buf, true); + } + + /* PCIe root port b0:d1:f0 in GMCH. + * Actually it's vid/did = 0x8086:0x29c1, but we substitute ioh for it. + */ + sec_bus = 32; + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + s = ioh3420_init(host_bus, PCI_DEVFN(GMCH_PCIE_DEV, GMCH_PCIE_FUNC), true, + buf, pci_swizzle_map_irq_fn, port, chassis, slot); + + + /* more slots. ICH9 doesn't have those, but many slots are wanted. */ +//#define Q35_MANY_SLOTS +#undef Q35_MANY_SLOTS + +#ifdef Q35_MANY_SLOTS +#define Q35_NR_ROOTPORT 6 +#define Q35_NR_UPSTREAM 8 +#define Q35_NR_DOWNSTREAM 16 +#else +#define Q35_NR_ROOTPORT 1 +#define Q35_NR_UPSTREAM 1 +#define Q35_NR_DOWNSTREAM 1 +#endif + + /* PCIe root port b0:d23:f[0-5], 0.17.[0-5] */ + for (fn = 0; fn < Q35_NR_ROOTPORT; fn++) { + sec_bus++; + port++; + slot++; + + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + s = ioh3420_init(host_bus, PCI_DEVFN(23, fn), true, + buf, pci_swizzle_map_irq_fn, port, chassis, slot); + } + + /* PCIe root port b0:d24:f0 */ + sec_bus++; + port++; + slot++; + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + root_port = ioh3420_init(host_bus, PCI_DEVFN(24, 0), true, + buf, pci_swizzle_map_irq_fn, port, chassis, slot); + root_port_bus = pci_bridge_get_sec_bus(&root_port->port.br); + + /* 8 * 16 = 128 slots */ + upstream_port = 0; + for (fn = 0; fn < Q35_NR_UPSTREAM; fn++) { + PCIEPort *upstream; + PCIBus *upstream_bus; + uint16_t downstream_port; + + uint8_t ds_dev_max; + uint8_t ds_dev; + uint8_t ds_fn_max; + uint8_t ds_fn; + + /* PCIe upstream port d0:f[0-7] */ + sec_bus++; + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + upstream = xio3130_upstream_init(root_port_bus, PCI_DEVFN(0, fn), + true, buf, pci_swizzle_map_irq_fn, + upstream_port); + + upstream_bus = pci_bridge_get_sec_bus(&upstream->br); + upstream_port++; + + /* PCIe downstream port */ + downstream_port = 0; + ds_fn_max = MIN(Q35_NR_DOWNSTREAM / PCI_SLOT_MAX, PCI_FUNC_MAX); + ds_dev_max = MIN(Q35_NR_DOWNSTREAM / (ds_fn_max + 1), PCI_SLOT_MAX); + + for (ds_dev = 0; ds_dev <= ds_dev_max && + downstream_port < Q35_NR_DOWNSTREAM; ds_dev++) { + for (ds_fn = 0; ds_fn <= ds_fn_max && + downstream_port < Q35_NR_DOWNSTREAM; ds_fn++) { + sec_bus++; + slot++; + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + + xio3130_downstream_init(upstream_bus, PCI_DEVFN(ds_dev, ds_fn), + true, buf, pci_swizzle_map_irq_fn, + downstream_port, chassis, slot); + downstream_port++; + } + } + } + + /* PCIe root port b0:d28:f[0-6] in ICH9. + * Actually it's vid/did = 0x8086:0x294[02468A], but we substitute ioh + * for them. + */ + for (fn = 0; fn < ICH9_PCIE_FUNC_MAX; fn++) { + sec_bus++; + port++; + slot++; + + snprintf(buf, sizeof(buf), "pcie.%d", sec_bus); + s = ioh3420_init(host_bus, PCI_DEVFN(ICH9_PCIE_DEV, fn), true, + buf, pci_swizzle_map_irq_fn, + port, chassis, slot); + } +} + +static void pc_q35_init_early(qemu_irq *isa_irq, IsaIrqState *isa_irq_state, + DeviceState **gmch_host_p, + PCIBus **host_bus_p, PCIBus **pci_bus_p, + PCIDevice **lpc_p) +{ + DeviceState *gmch_host; + PCIBus *host_bus; + PCIBus *pci_bus; + + PCIDevice *gmch_state; + PCIDevice *lpc; + + /* create pci host bus */ + host_bus = gmch_host_init(&gmch_host, isa_irq, isa_irq_state->ioapic); + gmch_state = gmch_init(gmch_host, host_bus); + + /* create conventional pci bus: pcie2pci bridge */ + pci_bus = ich9_d2pbr_init(host_bus, PCI_DEVFN(ICH9_D2P_BRIDGE_DEV, + ICH9_D2P_BRIDGE_FUNC), + ICH9_D2P_SECONDARY_DEFAULT); + + /* create child pci/pcie buses */ + pc_q35_bridge_init(host_bus, pci_bus); + + /* create ISA bus */ + lpc = gmch_lpc_init(gmch_host, host_bus); + + *gmch_host_p = gmch_host; + *host_bus_p = host_bus; + *pci_bus_p = pci_bus; + *lpc_p = lpc; +} + +static void pc_q35_init_late(BusState **idebus, ISADevice *rtc_state, + DeviceState *gmch_host, + PCIBus *host_bus, PCIBus *pci_bus, + PCIDevice *lpc) +{ + qemu_irq *cmos_s3; + PCIDevice *ahci; + DriveInfo *hd[MAX_SATA_PORTS * MAX_IDE_DEVS]; + + /* connect pm stuff to lpc */ + cmos_s3 = qemu_allocate_irqs(pc_cmos_set_s3_resume, rtc_state, 1); + ich9_lpc_pm_init(gmch_host, lpc, *cmos_s3); + + /* ahci and SATA device */ + ide_drive_get(hd, MAX_SATA_PORTS); + ahci = pci_create_simple_multifunction(host_bus, + PCI_DEVFN(ICH9_SATA1_DEV, + ICH9_SATA1_FUNC), + true, "ich9-ahci"); + pci_ahci_ide_create_devs(ahci, hd); + idebus[0] = qdev_get_child_bus(&ahci->qdev, "ide.0"); + idebus[1] = qdev_get_child_bus(&ahci->qdev, "ide.1"); + + if (usb_enabled) { + /* Should we create 6 UHCI according to ich9 spec? */ + pci_create_simple_multifunction( + host_bus, PCI_DEVFN(ICH9_USB_UHCI1_DEV, ICH9_USB_UHCI1_FUNC), + true, "ich9-usb-uhci1"); + /* XXX: EHCI */ + } + + /* TODO: Populate SPD eeprom data. */ + smbus_eeprom_init(ich9_smb_init(host_bus, + PCI_DEVFN(ICH9_SMB_DEV, ICH9_SMB_FUNC), + 0xb100), + 8, NULL, 0); +} + +/* 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) +{ + ram_addr_t below_4g_mem_size, above_4g_mem_size; + DeviceState *gmch_host; + PCIBus *host_bus; + PCIBus *pci_bus; + PCIDevice *lpc; + qemu_irq *isa_irq; + IsaIrqState *isa_irq_state; + BusState *idebus[MAX_SATA_PORTS]; + ISADevice *rtc_state; + + pc_cpus_init(cpu_model); + + /* 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); + + /* irq lines */ + isa_irq = pc_isa_irq(&isa_irq_state); + ioapic_init(isa_irq_state); + + pc_q35_init_early(isa_irq, isa_irq_state, + &gmch_host, &host_bus, &pci_bus, &lpc); + isa_bus_irqs(isa_irq); + pc_register_ferr_irq(isa_get_irq(13)); + + /* init basic PC hardware */ + pc_basic_device_init(isa_irq, &rtc_state); + + pc_q35_init_late(idebus, rtc_state, gmch_host, host_bus, pci_bus, lpc); + + pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, + idebus[0], idebus[1], rtc_state); + + /* the rest devices to which pci devfn is automatically assigned */ + pc_vga_init(host_bus); + audio_init(isa_irq, pci_bus); + pc_nic_init(pci_bus); + 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 a597a7b..21cac58 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -35,6 +35,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 @@ -111,4 +112,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_ICH9_UCHI1 0x2934 + +#define PCI_DEVICE_ID_INTEL_Q35_MCH 0x29c0 diff --git a/hw/q35.c b/hw/q35.c new file mode 100644 index 0000000..0636aa4 --- /dev/null +++ b/hw/q35.c @@ -0,0 +1,877 @@ +/* + * 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. + */ +/* + * QEMU GMCH/ICH9 PCI Bridge Emulation + * + * Copyright (c) 2009, 2010, 2011 + * 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, see + */ + +#include "hw.h" +#include "range.h" +#include "isa.h" +#include "sysbus.h" +#include "pc.h" +#include "apm.h" +#include "apic.h" +#include "pci.h" +#include "pcie_host.h" +#include "pci_bridge.h" +#include "pci_p2pbr.h" +#include "q35.h" +#include "acpi.h" +#include "acpi_ich9.h" +#include "pam.h" + + +struct ICH9_LPCState; + +typedef struct ICH9_LPCIrqState { + struct ICH9_LPCState *lpc; + qemu_irq *pic; + qemu_irq *ioapic; +} ICH9_LPCIrqState; + +typedef struct GMCH_PCIHost { + PCIExpressHost host; + + PCIDevice *dev; + ICH9_LPCIrqState irq_state; +} GMCH_PCIHost; + +typedef struct GMCH_PCIState { + PCIDevice d; + /* + * GMCH_PCIHost *gmch_host; + * In order to get GMCH_PCIHost + * PCIDevice -> qdev -> parent_bus -> qdev -upcast-> GMCH_PCIHost + */ + + PAM pam; +} GMCH_PCIState; + +typedef struct ICH9_LPCState { + /* ICH9 LPC PCI to ISA bridge */ + PCIDevice d; + + /* (pci device, intx) -> pirq + * In real chipset case, the unused slots are never used + * as ICH9 supports only D25-D32 irq routing. + * On the other hand in qemu case, any slot/function can be populated + * via command line option. + * So fallback interrupt routing for any devices in any slots is necessary. + */ + uint8_t irr[PCI_SLOT_MAX][PCI_NUM_PINS]; + + APMState apm; + ICH9_LPCPmRegs pm; + uint32_t sci_level; /* track sci level */ + + /* 10.1 Chipset Configuration registers(Memory Space) + which is pointed by RCBA */ + uint8_t chip_config[ICH9_CC_SIZE]; + int rbca_index; +} ICH9_LPCState; + + +/**************************************************************************** + * GMCH PCI host + */ +/* ich9 irq */ +static int ich9_lpc_map_irq(void *opaque, PCIDevice *pci_dev, int intx); +static void ich9_lpc_set_irq(void *opaque, int irq_num, int level); +static int ich9_lpc_sci_irq(ICH9_LPCState *lpc); + +static GMCH_PCIHost *gmch_pcihost_from_qdev(DeviceState *gmch_host_qdev) +{ + SysBusDevice *sysdev = sysbus_from_qdev(gmch_host_qdev); + PCIHostState *pci = FROM_SYSBUS(PCIHostState, sysdev); + PCIExpressHost *pcie = DO_UPCAST(PCIExpressHost, pci, pci); + return DO_UPCAST(GMCH_PCIHost, host, pcie); +} + +static int gmch_pcihost_initfn(SysBusDevice *dev) +{ + GMCH_PCIHost *s = gmch_pcihost_from_qdev(&dev->qdev); + + pci_host_conf_register_ioport(GMCH_HOST_BRIDGE_CONFIG_ADDR, &s->host.pci); + pci_host_data_register_ioport(GMCH_HOST_BRIDGE_CONFIG_DATA, &s->host.pci); + + if (pcie_host_init(&s->host) < 0) { + abort(); + } + + return 0; +} + +static SysBusDeviceInfo gmch_pcihost_info = { + .init = gmch_pcihost_initfn, + .qdev.name = "gmch-pcihost", + .qdev.size = sizeof(GMCH_PCIHost), + .qdev.no_user = 1, + .qdev.props = (Property[]) { + { + .name = "MCFG", + .info = &qdev_prop_uint64, + .offset = offsetof(GMCH_PCIHost, host.base_addr), + .defval = (uint64_t[]){ GMCH_HOST_BRIDGE_PCIEXBAR_DEFAULT }, + }, + DEFINE_PROP_END_OF_LIST(), + }, +}; + +/* host bridge */ +PCIBus *gmch_host_init(DeviceState **gmch_hostp, + qemu_irq *pic, qemu_irq *ioapic) +{ + DeviceState *dev; + GMCH_PCIHost *s; + PCIBus *b; + + dev = qdev_create(NULL, "gmch-pcihost"); + s = gmch_pcihost_from_qdev(dev); + s->irq_state.pic = pic; + s->irq_state.ioapic = ioapic; + + b = pci_bus_new(dev, "pcie.0", 0); + pci_bus_irqs(b, ich9_lpc_set_irq, ich9_lpc_map_irq, &s->irq_state, + ICH9_LPC_NB_PIRQS); + s->host.pci.bus = b; + qdev_init_nofail(dev); + + *gmch_hostp = dev; + return b; +} + + +/**************************************************************************** + * GMCH + */ +static GMCH_PCIState *gmch_from_pci(PCIDevice *gmch_pci) +{ + return DO_UPCAST(GMCH_PCIState, d, gmch_pci); +} + +/* PCIE MMCFG */ +static void gmch_update_pciexbar(GMCH_PCIState *gs) +{ + PCIDevice* pci_dev = &gs->d; + BusState *bus = qdev_get_parent_bus(&pci_dev->qdev); + DeviceState *qdev = bus->parent; + GMCH_PCIHost *s = gmch_pcihost_from_qdev(qdev); + + uint64_t pciexbar; + int enable; + uint64_t addr; + uint64_t addr_mask; + uint32_t length; + + pciexbar = pci_get_quad(pci_dev->config + GMCH_HOST_BRIDGE_PCIEXBAR); + enable = pciexbar & GMCH_HOST_BRIDGE_PCIEXBAREN; + + addr_mask = GMCH_HOST_BRIDGE_PCIEXBAR_ADMSK; + switch (pciexbar & GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_MASK) { + case GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_256M: + length = 256 * 1024 * 1024; + break; + case GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_128M: + length = 128 * 1024 * 1024; + addr_mask |= GMCH_HOST_BRIDGE_PCIEXBAR_128ADMSK | + GMCH_HOST_BRIDGE_PCIEXBAR_64ADMSK; + break; + case GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_64M: + length = 64 * 1024 * 1024; + addr_mask |= GMCH_HOST_BRIDGE_PCIEXBAR_64ADMSK; + break; + case GMCH_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); +} + +/* PAM */ +static void gmch_update_pam(GMCH_PCIState *gs) +{ + int i; + for (i = 0; i <= PAM_IDX_MAX; i++) { + pam_update(&gs->pam, i, gs->d.config[GMCH_HOST_BRIDGE_PAM0 + i]); + } +} + +/* SMRAM */ +static void gmch_update_smram(GMCH_PCIState *gs) +{ + smram_update(&gs->pam, gs->d.config[GMCH_HOST_BRDIGE_SMRAM]); +} + +static void gmch_set_smm(int smm, void *arg) +{ + GMCH_PCIState *gs = arg; + smram_set_smm(&gs->pam, smm, gs->d.config[GMCH_HOST_BRDIGE_SMRAM]); +} + +static void gmch_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + GMCH_PCIState *gs = gmch_from_pci(d); + + /* XXX: implement SMRAM.D_LOCK */ + pci_default_write_config(d, address, val, len); + + if (ranges_overlap(address, len, GMCH_HOST_BRIDGE_PAM0, + GMCH_HOST_BRIDGE_PAM_SIZE)) { + gmch_update_pam(gs); + } + + if (ranges_overlap(address, len, GMCH_HOST_BRIDGE_PCIEXBAR, + GMCH_HOST_BRIDGE_PCIEXBAR_SIZE)) { + gmch_update_pciexbar(gs); + } + + if (ranges_overlap(address, len, GMCH_HOST_BRDIGE_SMRAM, + GMCH_HOST_BRDIGE_SMRAM_SIZE)) { + gmch_update_smram(gs); + } +} + +static void gmch_update(GMCH_PCIState *gs) +{ + gmch_update_pciexbar(gs); + gmch_update_pam(gs); + gmch_update_smram(gs); +} + +static int gmch_post_load(void *opaque, int version_id) +{ + GMCH_PCIState *gs = opaque; + gmch_update(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, GMCH_PCIState), + VMSTATE_UINT8(pam.smm_enabled, GMCH_PCIState), + VMSTATE_END_OF_LIST() + } +}; + +static void gmch_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + GMCH_PCIState *gs = gmch_from_pci(d); + + pci_set_quad(d->config + GMCH_HOST_BRIDGE_PCIEXBAR, + GMCH_HOST_BRIDGE_PCIEXBAR_DEFAULT); + + d->config[GMCH_HOST_BRDIGE_SMRAM] = GMCH_HOST_BRIDGE_SMRAM_DEFAULT; + + gmch_update(gs); +} + +static int gmch_initfn(PCIDevice *d) +{ + GMCH_PCIState *gs = gmch_from_pci(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); + pci_config_set_revision(d->config, GMCH_HOST_BRIDGE_REVISION_DEFUALT); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + + cpu_smm_register(&gmch_set_smm, gs); + pam_init_memory_mappings(&gs->pam); + + return 0; +} + +static PCIDeviceInfo gmch_info = { + .qdev.name = "gmch", + .qdev.desc = "Host bridge", + .qdev.size = sizeof(GMCH_PCIState), + .qdev.vmsd = &vmstate_gmch, + .qdev.no_user = 1, + .init = gmch_initfn, + .config_write = gmch_write_config, + .qdev.reset = gmch_reset, +}; + +/* host bridge */ +PCIDevice *gmch_init(DeviceState *gmch_host, PCIBus *b) +{ + GMCH_PCIHost *s = gmch_pcihost_from_qdev(gmch_host); + PCIDevice *d; + + d = pci_create_simple_multifunction(b, 0, false, "gmch"); + s->dev = d; + + return d; +} + +/*****************************************************************************/ +/* ICH9 DMI-to-PCI bridge */ +#define I82801ba_SSVID_OFFSET 0x50 +#define I82801ba_SSVID_SVID 0 +#define I82801ba_SSVID_SSID 0 + +static PCIBridge *i82801ba11_init(PCIBus *bus, int devfn, const char *bus_name, + bool multifunction) +{ + const PCIP2PBridgeInit init = { + .bus = bus, + .devfn = devfn, + .multifunction = multifunction, + + .bus_name = bus_name, + .map_irq = pci_swizzle_map_irq_fn, + }; + const PCIP2PBridgeProp prop = { + .vendor_id = PCI_VENDOR_ID_INTEL, + .device_id = PCI_DEVICE_ID_INTEL_82801BA_11, + .revision_id = ICH9_D2P_A2_REVISION, + .prog_interface = PCI_CLASS_BRDIGE_PCI_INF_SUB, + + .ssvid_cap = I82801ba_SSVID_OFFSET, + .svid = I82801ba_SSVID_SVID, + .ssid = I82801ba_SSVID_SSID, + }; + return pci_p2pbr_create_simple(&init, &prop); +} + +PCIBus *ich9_d2pbr_init(PCIBus *bus, int devfn, int sec_bus) +{ + PCIBridge *br; + char buf[16]; + + snprintf(buf, sizeof(buf), "pci.%d", sec_bus); + br = i82801ba11_init(bus, devfn, buf, true); + if (br == NULL) { + return NULL; + } + return pci_bridge_get_sec_bus(br); +} + + +/*****************************************************************************/ +/* ICH9 LPC PCI to ISA bridge */ + +static void ich9_lpc_reset(DeviceState *qdev); + +static ICH9_LPCState *ich9_lpc_from_pci(PCIDevice *lpc_pci) +{ + return DO_UPCAST(ICH9_LPCState, d, lpc_pci); +} + +/* chipset configuration register + * to access chipset configuration registers, pci_[sg]et_{byte, word, long} + * are used. + * Although it's not pci configuration space, it's little endian as Intel. + */ + +static void ich9_cc_update_ir(uint8_t irr[PCI_NUM_PINS], uint32_t ir) +{ + int intx; + for (intx = 0; intx < PCI_NUM_PINS; intx++) { + irr[intx] = (ir >> (intx * ICH9_CC_DIR_SHIFT)) & ICH9_CC_DIR_MASK; + } +} + +static void ich9_cc_update(ICH9_LPCState *lpc) +{ + int slot; + int reg_offset; + int intx; + + /* D{25 - 31}IR, but D30IR is read only to 0. */ + for (slot = 25, reg_offset = 0; slot < 32; slot++, reg_offset++) { + if (slot != 30) { + ich9_cc_update_ir(lpc->irr[slot], + lpc->chip_config[ICH9_CC_D31IR + reg_offset]); + } + } + + /* + * D30: DMI2PCI bridge + * It is arbitrarily decided how INTx lines of PCI devicesbehind the bridge + * are connected to pirq lines. Our choice is PIRQ[E-H]. + * INT[A-D] are connected to PIRQ[E-H] + */ + for (intx = 0; intx < PCI_NUM_PINS; intx++) { + lpc->irr[30][intx] = intx + 4; + } +} + +static void ich9_cc_init(ICH9_LPCState *lpc) +{ + int slot; + int intx; + + /* the default irq routing is arbitrary as long as it matches with + * acpi irq routing table. + * The one that is incompatible with piix_pci(= bochs) one is + * intentionally chosen to let the users know that the different + * board is used. + * + * int[A-D] -> pirq[E-F] + * avoid pirq A-D because they are used for pci express port + */ + for (slot = 0; slot < PCI_SLOT_MAX; slot++) { + for (intx = 0; intx < PCI_NUM_PINS; intx++) { + lpc->irr[slot][intx] = (slot + intx) % 4 + 4; + } + } + ich9_cc_update(lpc); +} + +static void ich9_cc_reset(ICH9_LPCState *lpc) +{ + uint8_t *c = lpc->chip_config; + + memset(lpc->chip_config, 0, sizeof(lpc->chip_config)); + + pci_set_long(c + ICH9_CC_D31IR, ICH9_CC_DIR_DEFAULT); + pci_set_long(c + ICH9_CC_D30IR, ICH9_CC_D30IR_DEFAULT); + pci_set_long(c + ICH9_CC_D29IR, ICH9_CC_DIR_DEFAULT); + pci_set_long(c + ICH9_CC_D28IR, ICH9_CC_DIR_DEFAULT); + pci_set_long(c + ICH9_CC_D27IR, ICH9_CC_DIR_DEFAULT); + pci_set_long(c + ICH9_CC_D26IR, ICH9_CC_DIR_DEFAULT); + pci_set_long(c + ICH9_CC_D25IR, ICH9_CC_DIR_DEFAULT); + + ich9_cc_update(lpc); +} + +static void ich9_cc_addr_len(uint32_t *addr, int *len) +{ + *addr &= ICH9_CC_ADDR_MASK; + if (*addr + *len >= ICH9_CC_SIZE) { + *len = ICH9_CC_SIZE - *addr; + } +} + +/* val: little endian */ +static void ich9_cc_write(ICH9_LPCState *lpc, uint32_t addr, + uint32_t val, int len) +{ + ich9_cc_addr_len(&addr, &len); + memcpy(lpc->chip_config + addr, &val, len); +} + +/* return value: little endian */ +static uint32_t ich9_cc_read(ICH9_LPCState *lpc, uint32_t addr, int len) +{ + uint32_t val = 0; + ich9_cc_addr_len(&addr, &len); + memcpy(&val, lpc->chip_config + addr, len); + return val; +} + +#define ICH9_CC_MMIO_WRITE(type, len) \ + static void ich9_cc_mmio_write ## type \ + (void *opaque, target_phys_addr_t addr, uint32_t val) \ + { \ + ich9_cc_write(opaque, addr, val, len); \ + } + +#define ICH9_CC_MMIO_READ(type, len) \ + static uint32_t ich9_cc_mmio_read ## type \ + (void *opaque, target_phys_addr_t addr) \ + { \ + return ich9_cc_read(opaque, addr, len); \ + } + +ICH9_CC_MMIO_WRITE(b, 1) +ICH9_CC_MMIO_WRITE(w, 2) +ICH9_CC_MMIO_WRITE(l, 4) + +ICH9_CC_MMIO_READ(b, 1) +ICH9_CC_MMIO_READ(w, 2) +ICH9_CC_MMIO_READ(l, 4) + +static CPUWriteMemoryFunc * const ich9_cc_mmio_write[] = { + ich9_cc_mmio_writeb, + ich9_cc_mmio_writew, + ich9_cc_mmio_writel, +}; + +static CPUReadMemoryFunc * const ich9_cc_mmio_read[] = { + ich9_cc_mmio_readb, + ich9_cc_mmio_readw, + ich9_cc_mmio_readl, +}; + +/* IRQ routing */ +/* */ +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(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(); +} + +/* pic_irq: i8254 irq 0-15 */ +static void ich9_lpc_update_pic(ICH9_LPCIrqState *irq_state, int pic_irq) +{ + GMCH_PCIHost *s = container_of(irq_state, GMCH_PCIHost, irq_state); + ICH9_LPCState *lpc = irq_state->lpc; + int i, pic_level; + + /* 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(lpc, i, &tmp_irq, &tmp_dis); + if (!tmp_dis && pic_irq == tmp_irq) { + pic_level |= pci_bus_get_irq_level(s->host.pci.bus, i); + } + } + if (pic_irq == ich9_lpc_sci_irq(lpc)) { + pic_level |= lpc->sci_level; + } + + qemu_set_irq(irq_state->pic[pic_irq], pic_level); +} + +/* pirq: pirq[A-H] 0-7*/ +static void ich9_lpc_update_by_pirq(ICH9_LPCIrqState *irq_state, int pirq) +{ + ICH9_LPCState *lpc = irq_state->lpc; + int pic_irq; + int pic_dis; + + ich9_lpc_pic_irq(lpc, pirq, &pic_irq, &pic_dis); + assert(pic_irq < ICH9_LPC_PIC_NUM_PINS); + if (pic_dis) { + return; + } + + ich9_lpc_update_pic(irq_state, pic_irq); +} + +/* APIC mode: GSIx: PIRQ[A-H] -> GSI 16, ... no pirq shares same APIC pins. */ +static int ich9_pirq_to_gsi(int pirq) +{ + return pirq + ICH9_LPC_PIC_NUM_PINS; +} + +static int ich9_gsi_to_pirq(int gsi) +{ + return gsi - ICH9_LPC_PIC_NUM_PINS; +} + +static void ich9_lpc_update_apic(ICH9_LPCIrqState *irq_state, int gsi) +{ + GMCH_PCIHost *s = container_of(irq_state, GMCH_PCIHost, irq_state); + ICH9_LPCState *lpc = irq_state->lpc; + int level; + + level = pci_bus_get_irq_level(s->host.pci.bus, ich9_gsi_to_pirq(gsi)); + if (gsi == ich9_lpc_sci_irq(lpc)) { + level |= lpc->sci_level; + } + + qemu_set_irq(irq_state->ioapic[gsi], level); +} + +/* return the pirq number (PIRQ[A-H]:0-7) corresponding to + a given device irq pin. */ +static int ich9_lpc_map_irq(void *opaque, PCIDevice *pci_dev, int intx) +{ + ICH9_LPCIrqState *irq_state = opaque; + return irq_state->lpc->irr[PCI_SLOT(pci_dev->devfn)][intx]; +} + +static void ich9_lpc_set_irq(void *opaque, int pirq, int level) +{ + ICH9_LPCIrqState *irq_state = opaque; + + assert(0 <= pirq); + assert(pirq < ICH9_LPC_NB_PIRQS); + + ich9_lpc_update_apic(irq_state, ich9_pirq_to_gsi(pirq)); + ich9_lpc_update_by_pirq(irq_state, pirq); +} + +static int ich9_lpc_sci_irq(ICH9_LPCState *lpc) +{ + switch (lpc->d.config[ICH9_LPC_ACPI_CTRL] & + ICH9_LPC_ACPI_CTRL_SCI_IRQ_SEL_MASK) { + case ICH9_LPC_ACPI_CTRL_9: + return 9; + case ICH9_LPC_ACPI_CTRL_10: + return 10; + case ICH9_LPC_ACPI_CTRL_11: + return 11; + case ICH9_LPC_ACPI_CTRL_20: + return 20; + case ICH9_LPC_ACPI_CTRL_21: + return 21; + default: + /* reserved */ + break; + } + return -1; +} + +static void ich9_set_sci(void *opaque, int irq_num, int level) +{ + ICH9_LPCIrqState *irq_state = opaque; + ICH9_LPCState *lpc = irq_state->lpc; + int irq; + + assert(irq_num == 0); + level = !!level; + if (level == lpc->sci_level) { + return; + } + lpc->sci_level = level; + + irq = ich9_lpc_sci_irq(lpc); + if (irq < 0) { + return; + } + + ich9_lpc_update_apic(irq_state, irq); + if (irq < ICH9_LPC_PIC_NUM_PINS) { + ich9_lpc_update_pic(irq_state, irq); + } +} + +void ich9_lpc_pm_init(DeviceState *gmch_host, PCIDevice *lpc_pci, + qemu_irq cmos_s3) +{ + GMCH_PCIHost *s = gmch_pcihost_from_qdev(gmch_host); + ICH9_LPCState *lpc = ich9_lpc_from_pci(lpc_pci); + qemu_irq *sci_irq; + + sci_irq = qemu_allocate_irqs(ich9_set_sci, &s->irq_state, 1); + ich9_pm_init(&lpc->pm, sci_irq[0], cmos_s3); + + ich9_lpc_reset(&lpc->d.qdev); +} + +/* APM */ +static void ich9_apm_ctrl_changed(uint32_t val, void *arg) +{ + ICH9_LPCState *lpc = arg; + + /* ACPI specs 3.0, 4.7.2.5 */ + acpi_pm1_cnt_update(&lpc->pm.pm1_cnt, + val == ICH9_APM_ACPI_ENABLE, + val == ICH9_APM_ACPI_DISABLE); + + /* 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(ICH9_LPCState *lpc) +{ + uint32_t pm_io_base = pci_get_long(lpc->d.config + ICH9_LPC_PMBASE); + pm_io_base &= ICH9_LPC_PMBASE_BASE_ADDRESS_MASK; + + ich9_pm_iospace_update(&lpc->pm, pm_io_base); +} + +/* config:RBCA */ +static void ich9_lpc_rcba_update(ICH9_LPCState *lpc, uint32_t rbca_old) +{ + uint32_t rbca = pci_get_long(lpc->d.config + ICH9_LPC_RCBA); + + if (rbca_old & ICH9_LPC_RCBA_EN) { + cpu_register_physical_memory(rbca_old & ICH9_LPC_RCBA_BA_MASK, + ICH9_CC_SIZE, IO_MEM_UNASSIGNED); + } + if (rbca & ICH9_LPC_RCBA_EN) { + cpu_register_physical_memory(rbca & ICH9_LPC_RCBA_BA_MASK, + ICH9_CC_SIZE, lpc->rbca_index); + } +} + +static int ich9_lpc_post_load(void *opaque, int version_id) +{ + ICH9_LPCState *lpc = opaque; + + ich9_lpc_pmbase_update(lpc); + ich9_lpc_rcba_update(lpc, 0 /* disabled ICH9_LPC_RBCA_EN */); + return 0; +} + +static void ich9_lpc_config_write(PCIDevice *d, + uint32_t addr, uint32_t val, int len) +{ + ICH9_LPCState *lpc = ich9_lpc_from_pci(d); + uint32_t rbca_old = pci_get_long(d->config + ICH9_LPC_RCBA); + + pci_default_write_config(d, addr, val, len); + if (ranges_overlap(addr, len, ICH9_LPC_PMBASE, 4)) { + ich9_lpc_pmbase_update(lpc); + } + if (ranges_overlap(addr, len, ICH9_LPC_RCBA, 4)) { + ich9_lpc_rcba_update(lpc, rbca_old); + } +} + +static void ich9_lpc_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + ICH9_LPCState *lpc = ich9_lpc_from_pci(d); + uint32_t rbca_old = pci_get_long(d->config + ICH9_LPC_RCBA); + int i; + + for (i = 0; i < 4; i++) { + pci_set_byte(d->config + ICH9_LPC_PIRQA_ROUT + i, + ICH9_LPC_PIRQ_ROUT_DEFAULT); + } + for (i = 0; i < 4; i++) { + pci_set_byte(d->config + ICH9_LPC_PIRQE_ROUT + i, + ICH9_LPC_PIRQ_ROUT_DEFAULT); + } + pci_set_byte(d->config + ICH9_LPC_ACPI_CTRL, ICH9_LPC_ACPI_CTRL_DEFAULT); + + pci_set_long(d->config + ICH9_LPC_PMBASE, ICH9_LPC_PMBASE_DEFAULT); + pci_set_long(d->config + ICH9_LPC_RCBA, ICH9_LPC_RCBA_DEFAULT); + + ich9_cc_reset(lpc); + + ich9_lpc_pmbase_update(lpc); + ich9_lpc_rcba_update(lpc, rbca_old); + + lpc->sci_level = 0; +} + +static int ich9_lpc_initfn(PCIDevice *d) +{ + ICH9_LPCState *lpc = ich9_lpc_from_pci(d); + + 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_revision(d->config, ICH9_A2_LPC_REVISION); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_ISA); + + pci_set_long(d->wmask + ICH9_LPC_PMBASE, + ICH9_LPC_PMBASE_BASE_ADDRESS_MASK); + + lpc->rbca_index = cpu_register_io_memory(ich9_cc_mmio_read, + ich9_cc_mmio_write, + lpc, DEVICE_LITTLE_ENDIAN); + + ich9_cc_init(lpc); + apm_init(&lpc->apm, ich9_apm_ctrl_changed, lpc); + 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, ICH9_LPCState), + VMSTATE_STRUCT(apm, ICH9_LPCState, 0, vmstate_apm, APMState), + VMSTATE_STRUCT(pm, ICH9_LPCState, 0, vmstate_ich9_pm, ICH9_LPCPmRegs), + VMSTATE_UINT8_ARRAY(chip_config, ICH9_LPCState, ICH9_CC_SIZE), + VMSTATE_UINT32(sci_level, ICH9_LPCState), + VMSTATE_END_OF_LIST() + } +}; + +PCIDevice *gmch_lpc_init(DeviceState *gmch_host, PCIBus *bus) +{ + GMCH_PCIHost *s = gmch_pcihost_from_qdev(gmch_host); + PCIDevice *d; + ICH9_LPCState *lpc; + + d = pci_create_simple_multifunction(bus, PCI_DEVFN(ICH9_LPC_DEV, + ICH9_LPC_FUNC), + true, "ICH9 LPC"); + lpc = ich9_lpc_from_pci(d); + s->irq_state.lpc = lpc; + return &lpc->d; +} + +static PCIDeviceInfo ich9_lpc_info = { + .qdev.name = "ICH9 LPC", + .qdev.desc = "ICH9 LPC bridge", + .qdev.size = sizeof(ICH9_LPCState), + .qdev.vmsd = &vmstate_ich9_lpc, + .qdev.no_user = 1, + .init = ich9_lpc_initfn, + .config_write = ich9_lpc_config_write, + .qdev.reset = ich9_lpc_reset, +}; + +static void q35_register(void) +{ + sysbus_register_withprop(&gmch_pcihost_info); + pci_qdev_register(&gmch_info); + pci_qdev_register(&ich9_lpc_info); +} +device_init(q35_register); diff --git a/hw/q35.h b/hw/q35.h new file mode 100644 index 0000000..c0f5213 --- /dev/null +++ b/hw/q35.h @@ -0,0 +1,271 @@ +/* + * 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 Lesser General Public + * License along with this library; if not, see + */ + +#ifndef HW_Q35_H +#define HW_Q35_H + +#include "sysbus.h" +#include "acpi_ich9.h" + +PCIBus *gmch_host_init(DeviceState **gmch_hostp, + qemu_irq *pic, qemu_irq *ioapic); + +PCIDevice *gmch_init(DeviceState *gmch_host, PCIBus *b); +PCIBus *ich9_d2pbr_init(PCIBus *bus, int devfn, int sec_bus); +PCIDevice *gmch_lpc_init(DeviceState *gmch_host, PCIBus *bus); +void ich9_lpc_pm_init(DeviceState *gmch_host, PCIDevice *pci_lpc, + qemu_irq cmos_s3); + +i2c_bus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base); + +#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 GMCH_HOST_BRIDGE "GMCH" + +#define GMCH_HOST_BRIDGE_CONFIG_ADDR 0xcf8 +#define GMCH_HOST_BRIDGE_CONFIG_DATA 0xcfc + +/* D0:F0 configuration space */ +#define GMCH_HOST_BRIDGE_REVISION_DEFUALT 0x0 + +#define GMCH_HOST_BRIDGE_PCIEXBAR 0x60 /* 64bit register */ +#define GMCH_HOST_BRIDGE_PCIEXBAR_SIZE 8 /* 64bit register */ +#define GMCH_HOST_BRIDGE_PCIEXBAR_DEFAULT 0xe0000000 +#define GMCH_HOST_BRIDGE_PCIEXBAR_ADMSK Q35_MASK(64, 35, 25) /* bit 35:28 */ +#define GMCH_HOST_BRIDGE_PCIEXBAR_128ADMSK ((uint64_t)(1 << 26)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_64ADMSK ((uint64_t)(1 << 25)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_MASK ((uint64_t)(0x3 << 1)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_256M ((uint64_t)(0x0 << 1)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_128M ((uint64_t)(0x1 << 1)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_64M ((uint64_t)(0x2 << 1)) +#define GMCH_HOST_BRIDGE_PCIEXBAR_LENGTH_RVD ((uint64_t)(0x3 << 1)) +#define GMCH_HOST_BRIDGE_PCIEXBAREN ((uint64_t)1) + +#define GMCH_HOST_BRIDGE_PAM_NB 7 +#define GMCH_HOST_BRIDGE_PAM_SIZE 7 +#define GMCH_HOST_BRIDGE_PAM0 0x90 +#define GMCH_HOST_BRIDGE_PAM_BIOS_AREA 0xf0000 +#define GMCH_HOST_BRIDGE_PAM_AREA_SIZE 0x10000 /* 16KB */ +#define GMCH_HOST_BRIDGE_PAM1 0x91 +#define GMCH_HOST_BRIDGE_PAM_EXPAN_AREA 0xc0000 +#define GMCH_HOST_BRIDGE_PAM_EXPAN_SIZE 0x04000 +#define GMCH_HOST_BRIDGE_PAM2 0x92 +#define GMCH_HOST_BRIDGE_PAM3 0x93 +#define GMCH_HOST_BRIDGE_PAM4 0x94 +#define GMCH_HOST_BRIDGE_PAM_EXBIOS_AREA 0xe0000 +#define GMCH_HOST_BRIDGE_PAM_EXBIOS_SIZE 0x04000 +#define GMCH_HOST_BRIDGE_PAM5 0x95 +#define GMCH_HOST_BRIDGE_PAM6 0x96 +#define GMCH_HOST_BRIDGE_PAM_WE_HI ((uint8_t)(0x2 << 4)) +#define GMCH_HOST_BRIDGE_PAM_RE_HI ((uint8_t)(0x1 << 4)) +#define GMCH_HOST_BRIDGE_PAM_HI_MASK ((uint8_t)(0x3 << 4)) +#define GMCH_HOST_BRIDGE_PAM_WE_LO ((uint8_t)0x2) +#define GMCH_HOST_BRIDGE_PAM_RE_LO ((uint8_t)0x1) +#define GMCH_HOST_BRIDGE_PAM_LO_MASK ((uint8_t)0x3) +#define GMCH_HOST_BRIDGE_PAM_WE ((uint8_t)0x2) +#define GMCH_HOST_BRIDGE_PAM_RE ((uint8_t)0x1) +#define GMCH_HOST_BRIDGE_PAM_MASK ((uint8_t)0x3) + +#define GMCH_HOST_BRDIGE_SMRAM 0x9d +#define GMCH_HOST_BRDIGE_SMRAM_SIZE 1 +#define GMCH_HOST_BRIDGE_SMRAM_DEFAULT ((uint8_t)0x2) +#define GMCH_HOST_BRIDGE_SMRAM_D_OPEN ((uint8_t)(1 << 6)) +#define GMCH_HOST_BRIDGE_SMRAM_D_CLS ((uint8_t)(1 << 5)) +#define GMCH_HOST_BRIDGE_SMRAM_D_LCK ((uint8_t)(1 << 4)) +#define GMCH_HOST_BRIDGE_SMRAM_G_SMRAME ((uint8_t)(1 << 3)) +#define GMCH_HOST_BRIDGE_SMRAM_C_BASE_SEG_MASK ((uint8_t)0x7) +#define GMCH_HOST_BRIDGE_SMRAM_C_BASE_SEG ((uint8_t)0x2) /* hardwired to b010 */ +#define GMCH_HOST_BRIDGE_SMRAM_C_BASE 0xa0000 +#define GMCH_HOST_BRIDGE_SMRAM_C_END 0xc0000 +#define GMCH_HOST_BRIDGE_SMRAM_C_SIZE 0x20000 +#define GMCH_HOST_BRIDGE_UPPER_SYSTEM_BIOS_END 0x100000 + +#define GMCH_HOST_BRIDGE_ESMRAMC 0x9e +#define GMCH_HOST_BRDIGE_ESMRAMC_H_SMRAME ((uint8_t)(1 << 6)) +#define GMCH_HOST_BRDIGE_ESMRAMC_E_SMERR ((uint8_t)(1 << 5)) +#define GMCH_HOST_BRDIGE_ESMRAMC_SM_CACHE ((uint8_t)(1 << 4)) +#define GMCH_HOST_BRDIGE_ESMRAMC_SM_L1 ((uint8_t)(1 << 3)) +#define GMCH_HOST_BRDIGE_ESMRAMC_SM_L2 ((uint8_t)(1 << 2)) +#define GMCH_HOST_BRDIGE_ESMRAMC_TSEG_SZ_MASK ((uint8_t)(0x3 << 1)) +#define GMCH_HOST_BRDIGE_ESMRAMC_TSEG_SZ_1MB ((uint8_t)(0x0 << 1)) +#define GMCH_HOST_BRDIGE_ESMRAMC_TSEG_SZ_2MB ((uint8_t)(0x1 << 1)) +#define GMCH_HOST_BRDIGE_ESMRAMC_TSEG_SZ_8MB ((uint8_t)(0x2 << 1)) +#define GMCH_HOST_BRDIGE_ESMRAMC_T_EN ((uint8_t)1) + +/* D1:F0 PCIE* port*/ +#define GMCH_PCIE_DEV 1 +#define GMCH_PCIE_FUNC 0 + +/* + * ich9 part + */ + +/* ICH9: Chipset Configuration Registers */ +#define ICH9_CC_SIZE (16 * 1024) /* 16KB */ +#define ICH9_CC_ADDR_MASK (ICH9_CC_SIZE - 1) + +#define ICH9_CC +#define ICH9_CC_D28IP 0x310C +#define ICH9_CC_D28IP_SHIFT 4 +#define ICH9_CC_D28IP_MASK 0xf +#define ICH9_CC_D28IP_DEFAULT 0x00214321 +#define ICH9_CC_D31IR 0x3140 +#define ICH9_CC_D30IR 0x3142 +#define ICH9_CC_D29IR 0x3144 +#define ICH9_CC_D28IR 0x3146 +#define ICH9_CC_D27IR 0x3148 +#define ICH9_CC_D26IR 0x314C +#define ICH9_CC_D25IR 0x3150 +#define ICH9_CC_DIR_DEFAULT 0x3210 +#define ICH9_CC_D30IR_DEFAULT 0x0 +#define ICH9_CC_DIR_SHIFT 4 +#define ICH9_CC_DIR_MASK 0x7 +#define ICH9_CC_OIC 0x31FF +#define ICH9_CC_OIC_AEN 0x1 + +/* D28:F[0-5] */ +#define ICH9_PCIE_DEV 28 +#define ICH9_PCIE_FUNC_MAX 6 + + +/* D29:F0 USB UHCI Controller #1 */ +#define ICH9_USB_UHCI1_DEV 29 +#define ICH9_USB_UHCI1_FUNC 0 + +/* 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 + +#define ICH9_D2P_SECONDARY_DEFAULT (256 - 8) + +#define ICH9_D2P_A2_REVISION 0x92 + + +/* D31: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_9 0x0 +#define ICH9_LPC_ACPI_CTRL_10 0x1 +#define ICH9_LPC_ACPI_CTRL_11 0x2 +#define ICH9_LPC_ACPI_CTRL_20 0x4 +#define ICH9_LPC_ACPI_CTRL_21 0x5 +#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 + +#define ICH9_LPC_PIC_NUM_PINS 16 +#define ICH9_LPC_IOAPIC_NUM_PINS 24 + +/* D31:F2 SATA Controller #1 */ +#define ICH9_SATA1_DEV 31 +#define ICH9_SATA1_FUNC 2 + +/* 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_GPE0_LEN 16 +#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 + + +/* D31: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)) + +/* D31:F3 SMBus I/O and memory mapped I/O registers */ +#define ICH9_SMB_DEV 31 +#define ICH9_SMB_FUNC 3 + +#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..fe445ac --- /dev/null +++ b/hw/q35_smbus.c @@ -0,0 +1,154 @@ +/* + * 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, see + */ +/* + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * This is based on acpi.c, but heavily rewritten. + */ +#include "hw.h" +#include "pc.h" +#include "pm_smbus.h" +#include "pci.h" +#include "sysemu.h" +#include "i2c.h" +#include "smbus.h" + +#include "q35.h" + +typedef struct ICH9_SMBState { + PCIDevice dev; + + PMSMBus 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_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_BASE_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); +} + +static int ich9_smb_initfn(PCIDevice *d) +{ + ICH9_SMBState *s = ich9_pci_to_smb(d); + + 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_set_word(d->wmask + PCI_STATUS, + PCI_STATUS_SIG_SYSTEM_ERROR | PCI_STATUS_DETECTED_PARITY); + + pci_config_set_revision(d->config, ICH9_A2_SMB_REVISION); + pci_config_set_prog_interface(d->config, ICH9_SMB_PI); + pci_config_set_class(d->config, PCI_CLASS_SERIAL_SMBUS); + + /* TODO? D31IP.SMIP in chipset configuration space */ + pci_config_set_interrupt_pin(d->config, 0x01); /* interrupt pin 1 */ + + pci_set_byte(d->config + ICH9_SMB_HOSTC, 0); + + /* + * update parameters based on + * paralell_hds[0] + * serial_hds[0] + * serial_hds[0] + * fdc + * + * Is there any OS that depends on them? + */ + + /* TODO smb_io_base */ + pci_set_byte(d->config + ICH9_SMB_HOSTC, 0); + /* TODO bar0, bar1: 64bit BAR support*/ + pci_register_bar(d, ICH9_SMB_SMB_BASE_BAR, + ICH9_SMB_SMB_BASE_SIZE, PCI_BASE_ADDRESS_SPACE_IO, + &ich9_smb_map_ioport); + + pm_smbus_init(&d->qdev, &s->smb); + return 0; +} + +i2c_bus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base) +{ + PCIDevice *d = + pci_create_simple_multifunction(bus, devfn, true, "ICH9 SMB"); + ICH9_SMBState *s = ich9_pci_to_smb(d); + return s->smb.smbus; +} + +static PCIDeviceInfo ich9_smb_info = { + .qdev.name = "ICH9 SMB", + .qdev.desc = "ICH9 SMBUS Bridge", + .qdev.size = sizeof(ICH9_SMBState), + .qdev.vmsd = &vmstate_ich9_smbus, + .qdev.no_user = 1, + .init = ich9_smb_initfn, +}; + +static void ich9_smb_register(void) +{ + pci_qdev_register(&ich9_smb_info); +} + +device_init(ich9_smb_register);