aboutsummaryrefslogtreecommitdiffstats
path: root/hw/isa
diff options
context:
space:
mode:
Diffstat (limited to 'hw/isa')
-rw-r--r--hw/isa/Kconfig72
-rw-r--r--hw/isa/apm.c97
-rw-r--r--hw/isa/i82378.c151
-rw-r--r--hw/isa/isa-bus.c309
-rw-r--r--hw/isa/isa-superio.c212
-rw-r--r--hw/isa/lpc_ich9.c857
-rw-r--r--hw/isa/meson.build11
-rw-r--r--hw/isa/pc87312.c387
-rw-r--r--hw/isa/piix3.c395
-rw-r--r--hw/isa/piix4.c275
-rw-r--r--hw/isa/smc37c669-superio.c116
-rw-r--r--hw/isa/trace-events23
-rw-r--r--hw/isa/trace.h1
-rw-r--r--hw/isa/vt82c686.c754
14 files changed, 3660 insertions, 0 deletions
diff --git a/hw/isa/Kconfig b/hw/isa/Kconfig
new file mode 100644
index 000000000..d42143a99
--- /dev/null
+++ b/hw/isa/Kconfig
@@ -0,0 +1,72 @@
+config ISA_BUS
+ bool
+
+config APM
+ bool
+
+config I82378
+ bool
+ select ISA_BUS
+ select I8259
+ select I8254
+ select I82374
+ select MC146818RTC
+ select PCSPK
+
+config ISA_SUPERIO
+ bool
+ select ISA_BUS
+ select PCKBD
+ select FDC_ISA
+
+config PC87312
+ bool
+ select ISA_SUPERIO
+ select I8259
+ select I8254
+ select I8257
+ select MC146818RTC
+ select SERIAL_ISA
+ select PARALLEL
+ select FDC_ISA
+ select IDE_ISA
+
+config PIIX3
+ bool
+ select ISA_BUS
+
+config PIIX4
+ bool
+ # For historical reasons, SuperIO devices are created in the board
+ # for PIIX4.
+ select ISA_BUS
+ select USB_UHCI
+
+config VT82C686
+ bool
+ select ISA_SUPERIO
+ select ACPI_SMBUS
+ select SERIAL_ISA
+ select FDC_ISA
+ select USB_UHCI
+ select APM
+ select I8254
+ select I8257
+ select I8259
+ select MC146818RTC
+ select PARALLEL
+
+config SMC37C669
+ bool
+ select ISA_SUPERIO
+ select SERIAL_ISA
+ select PARALLEL
+ select FDC_ISA
+
+config LPC_ICH9
+ bool
+ # For historical reasons, SuperIO devices are created in the board
+ # for ICH9.
+ select ISA_BUS
+ select ACPI_SMBUS
+ select ACPI_X86_ICH
diff --git a/hw/isa/apm.c b/hw/isa/apm.c
new file mode 100644
index 000000000..dfe9020d3
--- /dev/null
+++ b/hw/isa/apm.c
@@ -0,0 +1,97 @@
+/*
+ * QEMU PC APM controller Emulation
+ * This is split out from acpi.c
+ *
+ * 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.1 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 <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/apm.h"
+#include "hw/pci/pci.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+
+/* fixed I/O location */
+#define APM_STS_IOPORT 0xb3
+
+static void apm_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ APMState *apm = opaque;
+ addr &= 1;
+
+ trace_apm_io_write(addr, val);
+ if (addr == 0) {
+ apm->apmc = val;
+
+ if (apm->callback) {
+ (apm->callback)(val, apm->arg);
+ }
+ } else {
+ apm->apms = val;
+ }
+}
+
+static uint64_t apm_ioport_readb(void *opaque, hwaddr addr, unsigned size)
+{
+ APMState *apm = opaque;
+ uint32_t val;
+
+ addr &= 1;
+ if (addr == 0) {
+ val = apm->apmc;
+ } else {
+ val = apm->apms;
+ }
+ trace_apm_io_read(addr, val);
+
+ return val;
+}
+
+const VMStateDescription vmstate_apm = {
+ .name = "APM State",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(apmc, APMState),
+ VMSTATE_UINT8(apms, APMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const MemoryRegionOps apm_ops = {
+ .read = apm_ioport_readb,
+ .write = apm_ioport_writeb,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+void apm_init(PCIDevice *dev, APMState *apm, apm_ctrl_changed_t callback,
+ void *arg)
+{
+ apm->callback = callback;
+ apm->arg = arg;
+
+ /* ioport 0xb2, 0xb3 */
+ memory_region_init_io(&apm->io, OBJECT(dev), &apm_ops, apm, "apm-io", 2);
+ memory_region_add_subregion(pci_address_space_io(dev), APM_CNT_IOPORT,
+ &apm->io);
+}
diff --git a/hw/isa/i82378.c b/hw/isa/i82378.c
new file mode 100644
index 000000000..2a2ff05b9
--- /dev/null
+++ b/hw/isa/i82378.c
@@ -0,0 +1,151 @@
+/*
+ * QEMU Intel i82378 emulation (PCI to ISA bridge)
+ *
+ * Copyright (c) 2010-2011 Hervé Poussineau
+ *
+ * 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.1 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/irq.h"
+#include "hw/intc/i8259.h"
+#include "hw/timer/i8254.h"
+#include "migration/vmstate.h"
+#include "hw/audio/pcspk.h"
+#include "qom/object.h"
+
+#define TYPE_I82378 "i82378"
+OBJECT_DECLARE_SIMPLE_TYPE(I82378State, I82378)
+
+struct I82378State {
+ PCIDevice parent_obj;
+
+ qemu_irq out[2];
+ qemu_irq *i8259;
+ MemoryRegion io;
+};
+
+static const VMStateDescription vmstate_i82378 = {
+ .name = "pci-i82378",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, I82378State),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void i82378_request_out0_irq(void *opaque, int irq, int level)
+{
+ I82378State *s = opaque;
+ qemu_set_irq(s->out[0], level);
+}
+
+static void i82378_request_pic_irq(void *opaque, int irq, int level)
+{
+ DeviceState *dev = opaque;
+ I82378State *s = I82378(dev);
+
+ qemu_set_irq(s->i8259[irq], level);
+}
+
+static void i82378_realize(PCIDevice *pci, Error **errp)
+{
+ DeviceState *dev = DEVICE(pci);
+ I82378State *s = I82378(dev);
+ uint8_t *pci_conf;
+ ISABus *isabus;
+ ISADevice *pit;
+
+ pci_conf = pci->config;
+ pci_set_word(pci_conf + PCI_COMMAND,
+ PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+ pci_set_word(pci_conf + PCI_STATUS,
+ PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_config_set_interrupt_pin(pci_conf, 1); /* interrupt pin 0 */
+
+ isabus = isa_bus_new(dev, get_system_memory(),
+ pci_address_space_io(pci), errp);
+ if (!isabus) {
+ return;
+ }
+
+ /* This device has:
+ 2 82C59 (irq)
+ 1 82C54 (pit)
+ 2 82C37 (dma)
+ NMI
+ Utility Bus Support Registers
+
+ All devices accept byte access only, except timer
+ */
+
+ /* 2 82C59 (irq) */
+ s->i8259 = i8259_init(isabus,
+ qemu_allocate_irq(i82378_request_out0_irq, s, 0));
+ isa_bus_irqs(isabus, s->i8259);
+
+ /* 1 82C54 (pit) */
+ pit = i8254_pit_init(isabus, 0x40, 0, NULL);
+
+ /* speaker */
+ pcspk_init(isa_new(TYPE_PC_SPEAKER), isabus, pit);
+
+ /* 2 82C37 (dma) */
+ isa_create_simple(isabus, "i82374");
+}
+
+static void i82378_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ I82378State *s = I82378(obj);
+
+ qdev_init_gpio_out(dev, s->out, 1);
+ qdev_init_gpio_in(dev, i82378_request_pic_irq, 16);
+}
+
+static void i82378_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = i82378_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82378;
+ k->revision = 0x03;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ dc->vmsd = &vmstate_i82378;
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo i82378_type_info = {
+ .name = TYPE_I82378,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(I82378State),
+ .instance_init = i82378_init,
+ .class_init = i82378_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void i82378_register_types(void)
+{
+ type_register_static(&i82378_type_info);
+}
+
+type_init(i82378_register_types)
diff --git a/hw/isa/isa-bus.c b/hw/isa/isa-bus.c
new file mode 100644
index 000000000..6c31398dd
--- /dev/null
+++ b/hw/isa/isa-bus.c
@@ -0,0 +1,309 @@
+/*
+ * isa bus support for qdev.
+ *
+ * Copyright (c) 2009 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * 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.1 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "monitor/monitor.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "hw/isa/isa.h"
+
+static ISABus *isabus;
+
+static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent);
+static char *isabus_get_fw_dev_path(DeviceState *dev);
+
+static void isa_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+
+ k->print_dev = isabus_dev_print;
+ k->get_fw_dev_path = isabus_get_fw_dev_path;
+}
+
+static const TypeInfo isa_dma_info = {
+ .name = TYPE_ISADMA,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(IsaDmaClass),
+};
+
+static const TypeInfo isa_bus_info = {
+ .name = TYPE_ISA_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(ISABus),
+ .class_init = isa_bus_class_init,
+};
+
+ISABus *isa_bus_new(DeviceState *dev, MemoryRegion* address_space,
+ MemoryRegion *address_space_io, Error **errp)
+{
+ if (isabus) {
+ error_setg(errp, "Can't create a second ISA bus");
+ return NULL;
+ }
+ if (!dev) {
+ dev = qdev_new("isabus-bridge");
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ }
+
+ isabus = ISA_BUS(qbus_new(TYPE_ISA_BUS, dev, NULL));
+ isabus->address_space = address_space;
+ isabus->address_space_io = address_space_io;
+ return isabus;
+}
+
+void isa_bus_irqs(ISABus *bus, qemu_irq *irqs)
+{
+ bus->irqs = irqs;
+}
+
+/*
+ * isa_get_irq() returns the corresponding qemu_irq entry for the i8259.
+ *
+ * This function is only for special cases such as the 'ferr', and
+ * temporary use for normal devices until they are converted to qdev.
+ */
+qemu_irq isa_get_irq(ISADevice *dev, unsigned isairq)
+{
+ assert(!dev || ISA_BUS(qdev_get_parent_bus(DEVICE(dev))) == isabus);
+ assert(isairq < ISA_NUM_IRQS);
+ return isabus->irqs[isairq];
+}
+
+void isa_init_irq(ISADevice *dev, qemu_irq *p, unsigned isairq)
+{
+ assert(dev->nirqs < ARRAY_SIZE(dev->isairq));
+ assert(isairq < ISA_NUM_IRQS);
+ dev->isairq[dev->nirqs] = isairq;
+ *p = isa_get_irq(dev, isairq);
+ dev->nirqs++;
+}
+
+void isa_connect_gpio_out(ISADevice *isadev, int gpioirq, unsigned isairq)
+{
+ qemu_irq irq;
+ isa_init_irq(isadev, &irq, isairq);
+ qdev_connect_gpio_out(DEVICE(isadev), gpioirq, irq);
+}
+
+void isa_bus_dma(ISABus *bus, IsaDma *dma8, IsaDma *dma16)
+{
+ assert(bus && dma8 && dma16);
+ assert(!bus->dma[0] && !bus->dma[1]);
+ bus->dma[0] = dma8;
+ bus->dma[1] = dma16;
+}
+
+IsaDma *isa_get_dma(ISABus *bus, int nchan)
+{
+ assert(bus);
+ return bus->dma[nchan > 3 ? 1 : 0];
+}
+
+static inline void isa_init_ioport(ISADevice *dev, uint16_t ioport)
+{
+ if (dev && (dev->ioport_id == 0 || ioport < dev->ioport_id)) {
+ dev->ioport_id = ioport;
+ }
+}
+
+void isa_register_ioport(ISADevice *dev, MemoryRegion *io, uint16_t start)
+{
+ memory_region_add_subregion(isabus->address_space_io, start, io);
+ isa_init_ioport(dev, start);
+}
+
+int isa_register_portio_list(ISADevice *dev,
+ PortioList *piolist, uint16_t start,
+ const MemoryRegionPortio *pio_start,
+ void *opaque, const char *name)
+{
+ assert(piolist && !piolist->owner);
+
+ if (!isabus) {
+ return -ENODEV;
+ }
+
+ /* START is how we should treat DEV, regardless of the actual
+ contents of the portio array. This is how the old code
+ actually handled e.g. the FDC device. */
+ isa_init_ioport(dev, start);
+
+ portio_list_init(piolist, OBJECT(dev), pio_start, opaque, name);
+ portio_list_add(piolist, isabus->address_space_io, start);
+
+ return 0;
+}
+
+static void isa_device_init(Object *obj)
+{
+ ISADevice *dev = ISA_DEVICE(obj);
+
+ dev->isairq[0] = -1;
+ dev->isairq[1] = -1;
+}
+
+ISADevice *isa_new(const char *name)
+{
+ return ISA_DEVICE(qdev_new(name));
+}
+
+ISADevice *isa_try_new(const char *name)
+{
+ return ISA_DEVICE(qdev_try_new(name));
+}
+
+ISADevice *isa_create_simple(ISABus *bus, const char *name)
+{
+ ISADevice *dev;
+
+ dev = isa_new(name);
+ isa_realize_and_unref(dev, bus, &error_fatal);
+ return dev;
+}
+
+bool isa_realize_and_unref(ISADevice *dev, ISABus *bus, Error **errp)
+{
+ return qdev_realize_and_unref(&dev->parent_obj, &bus->parent_obj, errp);
+}
+
+ISADevice *isa_vga_init(ISABus *bus)
+{
+ switch (vga_interface_type) {
+ case VGA_CIRRUS:
+ return isa_create_simple(bus, "isa-cirrus-vga");
+ case VGA_QXL:
+ error_report("%s: qxl: no PCI bus", __func__);
+ return NULL;
+ case VGA_STD:
+ return isa_create_simple(bus, "isa-vga");
+ case VGA_VMWARE:
+ error_report("%s: vmware_vga: no PCI bus", __func__);
+ return NULL;
+ case VGA_VIRTIO:
+ error_report("%s: virtio-vga: no PCI bus", __func__);
+ return NULL;
+ case VGA_NONE:
+ default:
+ return NULL;
+ }
+}
+
+void isa_build_aml(ISABus *bus, Aml *scope)
+{
+ BusChild *kid;
+ ISADevice *dev;
+ ISADeviceClass *dc;
+
+ QTAILQ_FOREACH(kid, &bus->parent_obj.children, sibling) {
+ dev = ISA_DEVICE(kid->child);
+ dc = ISA_DEVICE_GET_CLASS(dev);
+ if (dc->build_aml) {
+ dc->build_aml(dev, scope);
+ }
+ }
+}
+
+static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+
+ if (d->isairq[1] != -1) {
+ monitor_printf(mon, "%*sisa irqs %d,%d\n", indent, "",
+ d->isairq[0], d->isairq[1]);
+ } else if (d->isairq[0] != -1) {
+ monitor_printf(mon, "%*sisa irq %d\n", indent, "",
+ d->isairq[0]);
+ }
+}
+
+static void isabus_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->fw_name = "isa";
+}
+
+static const TypeInfo isabus_bridge_info = {
+ .name = "isabus-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = isabus_bridge_class_init,
+};
+
+static void isa_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->bus_type = TYPE_ISA_BUS;
+}
+
+static const TypeInfo isa_device_type_info = {
+ .name = TYPE_ISA_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(ISADevice),
+ .instance_init = isa_device_init,
+ .abstract = true,
+ .class_size = sizeof(ISADeviceClass),
+ .class_init = isa_device_class_init,
+};
+
+static void isabus_register_types(void)
+{
+ type_register_static(&isa_dma_info);
+ type_register_static(&isa_bus_info);
+ type_register_static(&isabus_bridge_info);
+ type_register_static(&isa_device_type_info);
+}
+
+static char *isabus_get_fw_dev_path(DeviceState *dev)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ char path[40];
+ int off;
+
+ off = snprintf(path, sizeof(path), "%s", qdev_fw_name(dev));
+ if (d->ioport_id) {
+ snprintf(path + off, sizeof(path) - off, "@%04x", d->ioport_id);
+ }
+
+ return g_strdup(path);
+}
+
+MemoryRegion *isa_address_space(ISADevice *dev)
+{
+ if (dev) {
+ return isa_bus_from_device(dev)->address_space;
+ }
+
+ return isabus->address_space;
+}
+
+MemoryRegion *isa_address_space_io(ISADevice *dev)
+{
+ if (dev) {
+ return isa_bus_from_device(dev)->address_space_io;
+ }
+
+ return isabus->address_space_io;
+}
+
+type_init(isabus_register_types)
diff --git a/hw/isa/isa-superio.c b/hw/isa/isa-superio.c
new file mode 100644
index 000000000..c81bfe58e
--- /dev/null
+++ b/hw/isa/isa-superio.c
@@ -0,0 +1,212 @@
+/*
+ * Generic ISA Super I/O
+ *
+ * Copyright (c) 2010-2012 Herve Poussineau
+ * Copyright (c) 2011-2012 Andreas Färber
+ * Copyright (c) 2018 Philippe Mathieu-Daudé
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "sysemu/blockdev.h"
+#include "chardev/char.h"
+#include "hw/block/fdc.h"
+#include "hw/isa/superio.h"
+#include "hw/qdev-properties.h"
+#include "hw/input/i8042.h"
+#include "hw/char/serial.h"
+#include "trace.h"
+
+static void isa_superio_realize(DeviceState *dev, Error **errp)
+{
+ ISASuperIODevice *sio = ISA_SUPERIO(dev);
+ ISASuperIOClass *k = ISA_SUPERIO_GET_CLASS(sio);
+ ISABus *bus = isa_bus_from_device(ISA_DEVICE(dev));
+ ISADevice *isa;
+ DeviceState *d;
+ Chardev *chr;
+ DriveInfo *fd[MAX_FD];
+ char *name;
+ int i;
+
+ /* Parallel port */
+ for (i = 0; i < k->parallel.count; i++) {
+ if (i >= ARRAY_SIZE(sio->parallel)) {
+ warn_report("superio: ignoring %td parallel controllers",
+ k->parallel.count - ARRAY_SIZE(sio->parallel));
+ break;
+ }
+ if (!k->parallel.is_enabled || k->parallel.is_enabled(sio, i)) {
+ /* FIXME use a qdev chardev prop instead of parallel_hds[] */
+ chr = parallel_hds[i];
+ if (chr == NULL) {
+ name = g_strdup_printf("discarding-parallel%d", i);
+ chr = qemu_chr_new(name, "null", NULL);
+ } else {
+ name = g_strdup_printf("parallel%d", i);
+ }
+ isa = isa_new("isa-parallel");
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "index", i);
+ if (k->parallel.get_iobase) {
+ qdev_prop_set_uint32(d, "iobase",
+ k->parallel.get_iobase(sio, i));
+ }
+ if (k->parallel.get_irq) {
+ qdev_prop_set_uint32(d, "irq", k->parallel.get_irq(sio, i));
+ }
+ qdev_prop_set_chr(d, "chardev", chr);
+ object_property_add_child(OBJECT(dev), name, OBJECT(isa));
+ isa_realize_and_unref(isa, bus, &error_fatal);
+ sio->parallel[i] = isa;
+ trace_superio_create_parallel(i,
+ k->parallel.get_iobase ?
+ k->parallel.get_iobase(sio, i) : -1,
+ k->parallel.get_irq ?
+ k->parallel.get_irq(sio, i) : -1);
+ g_free(name);
+ }
+ }
+
+ /* Serial */
+ for (i = 0; i < k->serial.count; i++) {
+ if (i >= ARRAY_SIZE(sio->serial)) {
+ warn_report("superio: ignoring %td serial controllers",
+ k->serial.count - ARRAY_SIZE(sio->serial));
+ break;
+ }
+ if (!k->serial.is_enabled || k->serial.is_enabled(sio, i)) {
+ /* FIXME use a qdev chardev prop instead of serial_hd() */
+ chr = serial_hd(i);
+ if (chr == NULL) {
+ name = g_strdup_printf("discarding-serial%d", i);
+ chr = qemu_chr_new(name, "null", NULL);
+ } else {
+ name = g_strdup_printf("serial%d", i);
+ }
+ isa = isa_new(TYPE_ISA_SERIAL);
+ d = DEVICE(isa);
+ qdev_prop_set_uint32(d, "index", i);
+ if (k->serial.get_iobase) {
+ qdev_prop_set_uint32(d, "iobase",
+ k->serial.get_iobase(sio, i));
+ }
+ if (k->serial.get_irq) {
+ qdev_prop_set_uint32(d, "irq", k->serial.get_irq(sio, i));
+ }
+ qdev_prop_set_chr(d, "chardev", chr);
+ object_property_add_child(OBJECT(dev), name, OBJECT(isa));
+ isa_realize_and_unref(isa, bus, &error_fatal);
+ sio->serial[i] = isa;
+ trace_superio_create_serial(i,
+ k->serial.get_iobase ?
+ k->serial.get_iobase(sio, i) : -1,
+ k->serial.get_irq ?
+ k->serial.get_irq(sio, i) : -1);
+ g_free(name);
+ }
+ }
+
+ /* Floppy disc */
+ if (!k->floppy.is_enabled || k->floppy.is_enabled(sio, 0)) {
+ isa = isa_new(TYPE_ISA_FDC);
+ d = DEVICE(isa);
+ if (k->floppy.get_iobase) {
+ qdev_prop_set_uint32(d, "iobase", k->floppy.get_iobase(sio, 0));
+ }
+ if (k->floppy.get_irq) {
+ qdev_prop_set_uint32(d, "irq", k->floppy.get_irq(sio, 0));
+ }
+ /* FIXME use a qdev drive property instead of drive_get() */
+ for (i = 0; i < MAX_FD; i++) {
+ fd[i] = drive_get(IF_FLOPPY, 0, i);
+ }
+ object_property_add_child(OBJECT(sio), "isa-fdc", OBJECT(isa));
+ isa_realize_and_unref(isa, bus, &error_fatal);
+ isa_fdc_init_drives(isa, fd);
+ sio->floppy = isa;
+ trace_superio_create_floppy(0,
+ k->floppy.get_iobase ?
+ k->floppy.get_iobase(sio, 0) : -1,
+ k->floppy.get_irq ?
+ k->floppy.get_irq(sio, 0) : -1);
+ }
+
+ /* Keyboard, mouse */
+ isa = isa_new(TYPE_I8042);
+ object_property_add_child(OBJECT(sio), TYPE_I8042, OBJECT(isa));
+ isa_realize_and_unref(isa, bus, &error_fatal);
+ sio->kbc = isa;
+
+ /* IDE */
+ if (k->ide.count && (!k->ide.is_enabled || k->ide.is_enabled(sio, 0))) {
+ isa = isa_new("isa-ide");
+ d = DEVICE(isa);
+ if (k->ide.get_iobase) {
+ qdev_prop_set_uint32(d, "iobase", k->ide.get_iobase(sio, 0));
+ }
+ if (k->ide.get_iobase) {
+ qdev_prop_set_uint32(d, "iobase2", k->ide.get_iobase(sio, 1));
+ }
+ if (k->ide.get_irq) {
+ qdev_prop_set_uint32(d, "irq", k->ide.get_irq(sio, 0));
+ }
+ object_property_add_child(OBJECT(sio), "isa-ide", OBJECT(isa));
+ isa_realize_and_unref(isa, bus, &error_fatal);
+ sio->ide = isa;
+ trace_superio_create_ide(0,
+ k->ide.get_iobase ?
+ k->ide.get_iobase(sio, 0) : -1,
+ k->ide.get_irq ?
+ k->ide.get_irq(sio, 0) : -1);
+ }
+}
+
+static void isa_superio_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = isa_superio_realize;
+ /* Reason: Uses parallel_hds[0] in realize(), so it can't be used twice */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo isa_superio_type_info = {
+ .name = TYPE_ISA_SUPERIO,
+ .parent = TYPE_ISA_DEVICE,
+ .abstract = true,
+ .class_size = sizeof(ISASuperIOClass),
+ .class_init = isa_superio_class_init,
+};
+
+/* SMS FDC37M817 Super I/O */
+static void fdc37m81x_class_init(ObjectClass *klass, void *data)
+{
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ sc->serial.count = 2; /* NS16C550A */
+ sc->parallel.count = 1;
+ sc->floppy.count = 1; /* SMSC 82077AA Compatible */
+ sc->ide.count = 0;
+}
+
+static const TypeInfo fdc37m81x_type_info = {
+ .name = TYPE_FDC37M81X_SUPERIO,
+ .parent = TYPE_ISA_SUPERIO,
+ .instance_size = sizeof(ISASuperIODevice),
+ .class_init = fdc37m81x_class_init,
+};
+
+static void isa_superio_register_types(void)
+{
+ type_register_static(&isa_superio_type_info);
+ type_register_static(&fdc37m81x_type_info);
+}
+
+type_init(isa_superio_register_types)
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c
new file mode 100644
index 000000000..5f143dca1
--- /dev/null
+++ b/hw/isa/lpc_ich9.c
@@ -0,0 +1,857 @@
+/*
+ * QEMU ICH9 Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009, 2010, 2011
+ * Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on piix.c, but heavily modified.
+ *
+ * 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 "qemu/osdep.h"
+#include "qemu/log.h"
+#include "cpu.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/range.h"
+#include "hw/isa/isa.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/isa/apm.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/i386/ich9.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/ich9.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/qdev-properties.h"
+#include "sysemu/runstate.h"
+#include "sysemu/sysemu.h"
+#include "hw/core/cpu.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qemu/cutils.h"
+
+/*****************************************************************************/
+/* ICH9 LPC PCI to ISA bridge */
+
+static void ich9_lpc_reset(DeviceState *qdev);
+
+/* 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], uint16_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(ICH9LPCState *lpc)
+{
+ int slot;
+ int pci_intx;
+
+ const int reg_offsets[] = {
+ ICH9_CC_D25IR,
+ ICH9_CC_D26IR,
+ ICH9_CC_D27IR,
+ ICH9_CC_D28IR,
+ ICH9_CC_D29IR,
+ ICH9_CC_D30IR,
+ ICH9_CC_D31IR,
+ };
+ const int *offset;
+
+ /* D{25 - 31}IR, but D30IR is read only to 0. */
+ for (slot = 25, offset = reg_offsets; slot < 32; slot++, offset++) {
+ if (slot == 30) {
+ continue;
+ }
+ ich9_cc_update_ir(lpc->irr[slot],
+ pci_get_word(lpc->chip_config + *offset));
+ }
+
+ /*
+ * D30: DMI2PCI bridge
+ * It is arbitrarily decided how INTx lines of PCI devices behind
+ * the bridge are connected to pirq lines. Our choice is PIRQ[E-H].
+ * INT[A-D] are connected to PIRQ[E-H]
+ */
+ for (pci_intx = 0; pci_intx < PCI_NUM_PINS; pci_intx++) {
+ lpc->irr[30][pci_intx] = pci_intx + 4;
+ }
+}
+
+static void ich9_cc_init(ICH9LPCState *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(ICH9LPCState *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);
+ pci_set_long(c + ICH9_CC_GCS, ICH9_CC_GCS_DEFAULT);
+
+ ich9_cc_update(lpc);
+}
+
+static void ich9_cc_addr_len(uint64_t *addr, unsigned *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(void *opaque, hwaddr addr,
+ uint64_t val, unsigned len)
+{
+ ICH9LPCState *lpc = (ICH9LPCState *)opaque;
+
+ ich9_cc_addr_len(&addr, &len);
+ memcpy(lpc->chip_config + addr, &val, len);
+ pci_bus_fire_intx_routing_notifier(pci_get_bus(&lpc->d));
+ ich9_cc_update(lpc);
+}
+
+/* return value: little endian */
+static uint64_t ich9_cc_read(void *opaque, hwaddr addr,
+ unsigned len)
+{
+ ICH9LPCState *lpc = (ICH9LPCState *)opaque;
+
+ uint32_t val = 0;
+ ich9_cc_addr_len(&addr, &len);
+ memcpy(&val, lpc->chip_config + addr, len);
+ return val;
+}
+
+/* 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(ICH9LPCState *lpc, int pirq_num,
+ int *pic_irq, int *pic_dis)
+{
+ switch (pirq_num) {
+ case 0 ... 3: /* A-D */
+ ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQA_ROUT + pirq_num],
+ pic_irq, pic_dis);
+ return;
+ case 4 ... 7: /* E-H */
+ ich9_lpc_rout(lpc->d.config[ICH9_LPC_PIRQE_ROUT + (pirq_num - 4)],
+ pic_irq, pic_dis);
+ return;
+ default:
+ break;
+ }
+ abort();
+}
+
+/* gsi: i8259+ioapic irq 0-15, otherwise assert */
+static void ich9_lpc_update_pic(ICH9LPCState *lpc, int gsi)
+{
+ int i, pic_level;
+
+ assert(gsi < ICH9_LPC_PIC_NUM_PINS);
+
+ /* 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 && tmp_irq == gsi) {
+ pic_level |= pci_bus_get_irq_level(pci_get_bus(&lpc->d), i);
+ }
+ }
+ if (gsi == lpc->sci_gsi) {
+ pic_level |= lpc->sci_level;
+ }
+
+ qemu_set_irq(lpc->gsi[gsi], pic_level);
+}
+
+/* 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;
+}
+
+/* gsi: ioapic irq 16-23, otherwise assert */
+static void ich9_lpc_update_apic(ICH9LPCState *lpc, int gsi)
+{
+ int level = 0;
+
+ assert(gsi >= ICH9_LPC_PIC_NUM_PINS);
+
+ level |= pci_bus_get_irq_level(pci_get_bus(&lpc->d), ich9_gsi_to_pirq(gsi));
+ if (gsi == lpc->sci_gsi) {
+ level |= lpc->sci_level;
+ }
+
+ qemu_set_irq(lpc->gsi[gsi], level);
+}
+
+void ich9_lpc_set_irq(void *opaque, int pirq, int level)
+{
+ ICH9LPCState *lpc = opaque;
+ int pic_irq, pic_dis;
+
+ assert(0 <= pirq);
+ assert(pirq < ICH9_LPC_NB_PIRQS);
+
+ ich9_lpc_update_apic(lpc, ich9_pirq_to_gsi(pirq));
+ ich9_lpc_pic_irq(lpc, pirq, &pic_irq, &pic_dis);
+ ich9_lpc_update_pic(lpc, pic_irq);
+}
+
+/* return the pirq number (PIRQ[A-H]:0-7) corresponding to
+ * a given device irq pin.
+ */
+int ich9_lpc_map_irq(PCIDevice *pci_dev, int intx)
+{
+ BusState *bus = qdev_get_parent_bus(&pci_dev->qdev);
+ PCIBus *pci_bus = PCI_BUS(bus);
+ PCIDevice *lpc_pdev =
+ pci_bus->devices[PCI_DEVFN(ICH9_LPC_DEV, ICH9_LPC_FUNC)];
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pdev);
+
+ return lpc->irr[PCI_SLOT(pci_dev->devfn)][intx];
+}
+
+PCIINTxRoute ich9_route_intx_pin_to_irq(void *opaque, int pirq_pin)
+{
+ ICH9LPCState *lpc = opaque;
+ PCIINTxRoute route;
+ int pic_irq;
+ int pic_dis;
+
+ assert(0 <= pirq_pin);
+ assert(pirq_pin < ICH9_LPC_NB_PIRQS);
+
+ route.mode = PCI_INTX_ENABLED;
+ ich9_lpc_pic_irq(lpc, pirq_pin, &pic_irq, &pic_dis);
+ if (!pic_dis) {
+ if (pic_irq < ICH9_LPC_PIC_NUM_PINS) {
+ route.irq = pic_irq;
+ } else {
+ route.mode = PCI_INTX_DISABLED;
+ route.irq = -1;
+ }
+ } else {
+ route.irq = ich9_pirq_to_gsi(pirq_pin);
+ }
+
+ return route;
+}
+
+void ich9_generate_smi(void)
+{
+ cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI);
+}
+
+/* Returns -1 on error, IRQ number on success */
+static int ich9_lpc_sci_irq(ICH9LPCState *lpc)
+{
+ uint8_t sel = lpc->d.config[ICH9_LPC_ACPI_CTRL] &
+ ICH9_LPC_ACPI_CTRL_SCI_IRQ_SEL_MASK;
+ switch (sel) {
+ 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 */
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ICH9 LPC: SCI IRQ SEL #%u is reserved\n", sel);
+ break;
+ }
+ return -1;
+}
+
+static void ich9_set_sci(void *opaque, int irq_num, int level)
+{
+ ICH9LPCState *lpc = opaque;
+ int irq;
+
+ assert(irq_num == 0);
+ level = !!level;
+ if (level == lpc->sci_level) {
+ return;
+ }
+ lpc->sci_level = level;
+
+ irq = lpc->sci_gsi;
+ if (irq < 0) {
+ return;
+ }
+
+ if (irq >= ICH9_LPC_PIC_NUM_PINS) {
+ ich9_lpc_update_apic(lpc, irq);
+ } else {
+ ich9_lpc_update_pic(lpc, irq);
+ }
+}
+
+static void smi_features_ok_callback(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+ uint64_t guest_features;
+ uint64_t guest_cpu_hotplug_features;
+
+ if (lpc->smi_features_ok) {
+ /* negotiation already complete, features locked */
+ return;
+ }
+
+ memcpy(&guest_features, lpc->smi_guest_features_le, sizeof guest_features);
+ le64_to_cpus(&guest_features);
+ if (guest_features & ~lpc->smi_host_features) {
+ /* guest requests invalid features, leave @features_ok at zero */
+ return;
+ }
+
+ guest_cpu_hotplug_features = guest_features &
+ (BIT_ULL(ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT) |
+ BIT_ULL(ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT));
+ if (!(guest_features & BIT_ULL(ICH9_LPC_SMI_F_BROADCAST_BIT)) &&
+ guest_cpu_hotplug_features) {
+ /*
+ * cpu hot-[un]plug with SMI requires SMI broadcast,
+ * leave @features_ok at zero
+ */
+ return;
+ }
+
+ if (guest_cpu_hotplug_features ==
+ BIT_ULL(ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT)) {
+ /* cpu hot-unplug is unsupported without cpu-hotplug */
+ return;
+ }
+
+ /* valid feature subset requested, lock it down, report success */
+ lpc->smi_negotiated_features = guest_features;
+ lpc->smi_features_ok = 1;
+}
+
+void ich9_lpc_pm_init(PCIDevice *lpc_pci, bool smm_enabled)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pci);
+ qemu_irq sci_irq;
+ FWCfgState *fw_cfg = fw_cfg_find();
+
+ sci_irq = qemu_allocate_irq(ich9_set_sci, lpc, 0);
+ ich9_pm_init(lpc_pci, &lpc->pm, smm_enabled, sci_irq);
+
+ if (lpc->smi_host_features && fw_cfg) {
+ uint64_t host_features_le;
+
+ host_features_le = cpu_to_le64(lpc->smi_host_features);
+ memcpy(lpc->smi_host_features_le, &host_features_le,
+ sizeof host_features_le);
+ fw_cfg_add_file(fw_cfg, "etc/smi/supported-features",
+ lpc->smi_host_features_le,
+ sizeof lpc->smi_host_features_le);
+
+ /* The other two guest-visible fields are cleared on device reset, we
+ * just link them into fw_cfg here.
+ */
+ fw_cfg_add_file_callback(fw_cfg, "etc/smi/requested-features",
+ NULL, NULL, NULL,
+ lpc->smi_guest_features_le,
+ sizeof lpc->smi_guest_features_le,
+ false);
+ fw_cfg_add_file_callback(fw_cfg, "etc/smi/features-ok",
+ smi_features_ok_callback, NULL, lpc,
+ &lpc->smi_features_ok,
+ sizeof lpc->smi_features_ok,
+ true);
+ }
+
+ ich9_lpc_reset(DEVICE(lpc));
+}
+
+/* APM */
+
+static void ich9_apm_ctrl_changed(uint32_t val, void *arg)
+{
+ ICH9LPCState *lpc = arg;
+
+ /* ACPI specs 3.0, 4.7.2.5 */
+ acpi_pm1_cnt_update(&lpc->pm.acpi_regs,
+ val == ICH9_APM_ACPI_ENABLE,
+ val == ICH9_APM_ACPI_DISABLE);
+ if (val == ICH9_APM_ACPI_ENABLE || val == ICH9_APM_ACPI_DISABLE) {
+ return;
+ }
+
+ /* SMI_EN = PMBASE + 30. SMI control and enable register */
+ if (lpc->pm.smi_en & ICH9_PMIO_SMI_EN_APMC_EN) {
+ if (lpc->smi_negotiated_features &
+ (UINT64_C(1) << ICH9_LPC_SMI_F_BROADCAST_BIT)) {
+ CPUState *cs;
+ CPU_FOREACH(cs) {
+ cpu_interrupt(cs, CPU_INTERRUPT_SMI);
+ }
+ } else {
+ cpu_interrupt(current_cpu, CPU_INTERRUPT_SMI);
+ }
+ }
+}
+
+/* config:PMBASE */
+static void
+ich9_lpc_pmbase_sci_update(ICH9LPCState *lpc)
+{
+ uint32_t pm_io_base = pci_get_long(lpc->d.config + ICH9_LPC_PMBASE);
+ uint8_t acpi_cntl = pci_get_long(lpc->d.config + ICH9_LPC_ACPI_CTRL);
+ int new_gsi;
+
+ if (acpi_cntl & ICH9_LPC_ACPI_CTRL_ACPI_EN) {
+ pm_io_base &= ICH9_LPC_PMBASE_BASE_ADDRESS_MASK;
+ } else {
+ pm_io_base = 0;
+ }
+
+ ich9_pm_iospace_update(&lpc->pm, pm_io_base);
+
+ new_gsi = ich9_lpc_sci_irq(lpc);
+ if (new_gsi == -1) {
+ return;
+ }
+ if (lpc->sci_level && new_gsi != lpc->sci_gsi) {
+ qemu_set_irq(lpc->pm.irq, 0);
+ lpc->sci_gsi = new_gsi;
+ qemu_set_irq(lpc->pm.irq, 1);
+ }
+ lpc->sci_gsi = new_gsi;
+}
+
+/* config:RCBA */
+static void ich9_lpc_rcba_update(ICH9LPCState *lpc, uint32_t rcba_old)
+{
+ uint32_t rcba = pci_get_long(lpc->d.config + ICH9_LPC_RCBA);
+
+ if (rcba_old & ICH9_LPC_RCBA_EN) {
+ memory_region_del_subregion(get_system_memory(), &lpc->rcrb_mem);
+ }
+ if (rcba & ICH9_LPC_RCBA_EN) {
+ memory_region_add_subregion_overlap(get_system_memory(),
+ rcba & ICH9_LPC_RCBA_BA_MASK,
+ &lpc->rcrb_mem, 1);
+ }
+}
+
+/* config:GEN_PMCON* */
+static void
+ich9_lpc_pmcon_update(ICH9LPCState *lpc)
+{
+ uint16_t gen_pmcon_1 = pci_get_word(lpc->d.config + ICH9_LPC_GEN_PMCON_1);
+ uint16_t wmask;
+
+ if (gen_pmcon_1 & ICH9_LPC_GEN_PMCON_1_SMI_LOCK) {
+ wmask = pci_get_word(lpc->d.wmask + ICH9_LPC_GEN_PMCON_1);
+ wmask &= ~ICH9_LPC_GEN_PMCON_1_SMI_LOCK;
+ pci_set_word(lpc->d.wmask + ICH9_LPC_GEN_PMCON_1, wmask);
+ lpc->pm.smi_en_wmask &= ~1;
+ }
+}
+
+static int ich9_lpc_post_load(void *opaque, int version_id)
+{
+ ICH9LPCState *lpc = opaque;
+
+ ich9_lpc_pmbase_sci_update(lpc);
+ ich9_lpc_rcba_update(lpc, 0 /* disabled ICH9_LPC_RCBA_EN */);
+ ich9_lpc_pmcon_update(lpc);
+ return 0;
+}
+
+static void ich9_lpc_config_write(PCIDevice *d,
+ uint32_t addr, uint32_t val, int len)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ uint32_t rcba_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) ||
+ ranges_overlap(addr, len, ICH9_LPC_ACPI_CTRL, 1)) {
+ ich9_lpc_pmbase_sci_update(lpc);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_RCBA, 4)) {
+ ich9_lpc_rcba_update(lpc, rcba_old);
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_PIRQA_ROUT, 4)) {
+ pci_bus_fire_intx_routing_notifier(pci_get_bus(&lpc->d));
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_PIRQE_ROUT, 4)) {
+ pci_bus_fire_intx_routing_notifier(pci_get_bus(&lpc->d));
+ }
+ if (ranges_overlap(addr, len, ICH9_LPC_GEN_PMCON_1, 8)) {
+ ich9_lpc_pmcon_update(lpc);
+ }
+}
+
+static void ich9_lpc_reset(DeviceState *qdev)
+{
+ PCIDevice *d = PCI_DEVICE(qdev);
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ uint32_t rcba_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_sci_update(lpc);
+ ich9_lpc_rcba_update(lpc, rcba_old);
+
+ lpc->sci_level = 0;
+ lpc->rst_cnt = 0;
+
+ memset(lpc->smi_guest_features_le, 0, sizeof lpc->smi_guest_features_le);
+ lpc->smi_features_ok = 0;
+ lpc->smi_negotiated_features = 0;
+}
+
+/* root complex register block is mapped into memory space */
+static const MemoryRegionOps rcrb_mmio_ops = {
+ .read = ich9_cc_read,
+ .write = ich9_cc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ich9_lpc_machine_ready(Notifier *n, void *opaque)
+{
+ ICH9LPCState *s = container_of(n, ICH9LPCState, machine_ready);
+ MemoryRegion *io_as = pci_address_space_io(&s->d);
+ uint8_t *pci_conf;
+
+ pci_conf = s->d.config;
+ if (memory_region_present(io_as, 0x3f8)) {
+ /* com1 */
+ pci_conf[0x82] |= 0x01;
+ }
+ if (memory_region_present(io_as, 0x2f8)) {
+ /* com2 */
+ pci_conf[0x82] |= 0x02;
+ }
+ if (memory_region_present(io_as, 0x378)) {
+ /* lpt */
+ pci_conf[0x82] |= 0x04;
+ }
+ if (memory_region_present(io_as, 0x3f2)) {
+ /* floppy */
+ pci_conf[0x82] |= 0x08;
+ }
+}
+
+/* reset control */
+static void ich9_rst_cnt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned len)
+{
+ ICH9LPCState *lpc = opaque;
+
+ if (val & 4) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return;
+ }
+ lpc->rst_cnt = val & 0xA; /* keep FULL_RST (bit 3) and SYS_RST (bit 1) */
+}
+
+static uint64_t ich9_rst_cnt_read(void *opaque, hwaddr addr, unsigned len)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return lpc->rst_cnt;
+}
+
+static const MemoryRegionOps ich9_rst_cnt_ops = {
+ .read = ich9_rst_cnt_read,
+ .write = ich9_rst_cnt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+static void ich9_lpc_initfn(Object *obj)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(obj);
+
+ static const uint8_t acpi_enable_cmd = ICH9_APM_ACPI_ENABLE;
+ static const uint8_t acpi_disable_cmd = ICH9_APM_ACPI_DISABLE;
+
+ object_property_add_uint8_ptr(obj, ACPI_PM_PROP_SCI_INT,
+ &lpc->sci_gsi, OBJ_PROP_FLAG_READ);
+ object_property_add_uint8_ptr(OBJECT(lpc), ACPI_PM_PROP_ACPI_ENABLE_CMD,
+ &acpi_enable_cmd, OBJ_PROP_FLAG_READ);
+ object_property_add_uint8_ptr(OBJECT(lpc), ACPI_PM_PROP_ACPI_DISABLE_CMD,
+ &acpi_disable_cmd, OBJ_PROP_FLAG_READ);
+ object_property_add_uint64_ptr(obj, ICH9_LPC_SMI_NEGOTIATED_FEAT_PROP,
+ &lpc->smi_negotiated_features,
+ OBJ_PROP_FLAG_READ);
+
+ ich9_pm_add_properties(obj, &lpc->pm);
+}
+
+static void ich9_lpc_realize(PCIDevice *d, Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(d);
+ DeviceState *dev = DEVICE(d);
+ ISABus *isa_bus;
+
+ if ((lpc->smi_host_features & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT)) &&
+ !(lpc->smi_host_features & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT))) {
+ /*
+ * smi_features_ok_callback() throws an error on this.
+ *
+ * So bail out here instead of advertizing the invalid
+ * configuration and get obscure firmware failures from that.
+ */
+ error_setg(errp, "cpu hot-unplug requires cpu hot-plug");
+ return;
+ }
+
+ isa_bus = isa_bus_new(DEVICE(d), get_system_memory(), get_system_io(),
+ errp);
+ if (!isa_bus) {
+ return;
+ }
+
+ pci_set_long(d->wmask + ICH9_LPC_PMBASE,
+ ICH9_LPC_PMBASE_BASE_ADDRESS_MASK);
+ pci_set_byte(d->wmask + ICH9_LPC_PMBASE,
+ ICH9_LPC_ACPI_CTRL_ACPI_EN |
+ ICH9_LPC_ACPI_CTRL_SCI_IRQ_SEL_MASK);
+
+ memory_region_init_io(&lpc->rcrb_mem, OBJECT(d), &rcrb_mmio_ops, lpc,
+ "lpc-rcrb-mmio", ICH9_CC_SIZE);
+
+ lpc->isa_bus = isa_bus;
+
+ ich9_cc_init(lpc);
+ apm_init(d, &lpc->apm, ich9_apm_ctrl_changed, lpc);
+
+ lpc->machine_ready.notify = ich9_lpc_machine_ready;
+ qemu_add_machine_init_done_notifier(&lpc->machine_ready);
+
+ memory_region_init_io(&lpc->rst_cnt_mem, OBJECT(d), &ich9_rst_cnt_ops, lpc,
+ "lpc-reset-control", 1);
+ memory_region_add_subregion_overlap(pci_address_space_io(d),
+ ICH9_RST_CNT_IOPORT, &lpc->rst_cnt_mem,
+ 1);
+
+ qdev_init_gpio_out_named(dev, lpc->gsi, ICH9_GPIO_GSI, GSI_NUM_PINS);
+
+ isa_bus_irqs(isa_bus, lpc->gsi);
+}
+
+static bool ich9_rst_cnt_needed(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return (lpc->rst_cnt != 0);
+}
+
+static const VMStateDescription vmstate_ich9_rst_cnt = {
+ .name = "ICH9LPC/rst_cnt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ich9_rst_cnt_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rst_cnt, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool ich9_smi_feat_needed(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return !buffer_is_zero(lpc->smi_guest_features_le,
+ sizeof lpc->smi_guest_features_le) ||
+ lpc->smi_features_ok;
+}
+
+static const VMStateDescription vmstate_ich9_smi_feat = {
+ .name = "ICH9LPC/smi_feat",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ich9_smi_feat_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(smi_guest_features_le, ICH9LPCState,
+ sizeof(uint64_t)),
+ VMSTATE_UINT8(smi_features_ok, ICH9LPCState),
+ VMSTATE_UINT64(smi_negotiated_features, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ich9_lpc = {
+ .name = "ICH9LPC",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ich9_lpc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(d, ICH9LPCState),
+ VMSTATE_STRUCT(apm, ICH9LPCState, 0, vmstate_apm, APMState),
+ VMSTATE_STRUCT(pm, ICH9LPCState, 0, vmstate_ich9_pm, ICH9LPCPMRegs),
+ VMSTATE_UINT8_ARRAY(chip_config, ICH9LPCState, ICH9_CC_SIZE),
+ VMSTATE_UINT32(sci_level, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_ich9_rst_cnt,
+ &vmstate_ich9_smi_feat,
+ NULL
+ }
+};
+
+static Property ich9_lpc_properties[] = {
+ DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true),
+ DEFINE_PROP_BOOL("smm-compat", ICH9LPCState, pm.smm_compat, false),
+ DEFINE_PROP_BIT64("x-smi-broadcast", ICH9LPCState, smi_host_features,
+ ICH9_LPC_SMI_F_BROADCAST_BIT, true),
+ DEFINE_PROP_BIT64("x-smi-cpu-hotplug", ICH9LPCState, smi_host_features,
+ ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT, true),
+ DEFINE_PROP_BIT64("x-smi-cpu-hotunplug", ICH9LPCState, smi_host_features,
+ ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ich9_send_gpe(AcpiDeviceIf *adev, AcpiEventStatusBits ev)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(adev);
+
+ acpi_send_gpe_event(&s->pm.acpi_regs, s->pm.irq, ev);
+}
+
+static void ich9_lpc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+ dc->reset = ich9_lpc_reset;
+ k->realize = ich9_lpc_realize;
+ dc->vmsd = &vmstate_ich9_lpc;
+ device_class_set_props(dc, ich9_lpc_properties);
+ k->config_write = ich9_lpc_config_write;
+ dc->desc = "ICH9 LPC bridge";
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ICH9_8;
+ k->revision = ICH9_A2_LPC_REVISION;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ /*
+ * Reason: part of ICH9 southbridge, needs to be wired up by
+ * pc_q35_init()
+ */
+ dc->user_creatable = false;
+ hc->pre_plug = ich9_pm_device_pre_plug_cb;
+ hc->plug = ich9_pm_device_plug_cb;
+ hc->unplug_request = ich9_pm_device_unplug_request_cb;
+ hc->unplug = ich9_pm_device_unplug_cb;
+ adevc->ospm_status = ich9_pm_ospm_status;
+ adevc->send_event = ich9_send_gpe;
+ adevc->madt_cpu = pc_madt_cpu_entry;
+}
+
+static const TypeInfo ich9_lpc_info = {
+ .name = TYPE_ICH9_LPC_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ICH9LPCState),
+ .instance_init = ich9_lpc_initfn,
+ .class_init = ich9_lpc_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { TYPE_ACPI_DEVICE_IF },
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ }
+};
+
+static void ich9_lpc_register(void)
+{
+ type_register_static(&ich9_lpc_info);
+}
+
+type_init(ich9_lpc_register);
diff --git a/hw/isa/meson.build b/hw/isa/meson.build
new file mode 100644
index 000000000..8bf678ca0
--- /dev/null
+++ b/hw/isa/meson.build
@@ -0,0 +1,11 @@
+softmmu_ss.add(when: 'CONFIG_APM', if_true: files('apm.c'))
+softmmu_ss.add(when: 'CONFIG_I82378', if_true: files('i82378.c'))
+softmmu_ss.add(when: 'CONFIG_ISA_BUS', if_true: files('isa-bus.c'))
+softmmu_ss.add(when: 'CONFIG_ISA_SUPERIO', if_true: files('isa-superio.c'))
+softmmu_ss.add(when: 'CONFIG_PC87312', if_true: files('pc87312.c'))
+softmmu_ss.add(when: 'CONFIG_PIIX3', if_true: files('piix3.c'))
+softmmu_ss.add(when: 'CONFIG_PIIX4', if_true: files('piix4.c'))
+softmmu_ss.add(when: 'CONFIG_SMC37C669', if_true: files('smc37c669-superio.c'))
+softmmu_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686.c'))
+
+specific_ss.add(when: 'CONFIG_LPC_ICH9', if_true: files('lpc_ich9.c'))
diff --git a/hw/isa/pc87312.c b/hw/isa/pc87312.c
new file mode 100644
index 000000000..8d7b8d3db
--- /dev/null
+++ b/hw/isa/pc87312.c
@@ -0,0 +1,387 @@
+/*
+ * QEMU National Semiconductor PC87312 (Super I/O)
+ *
+ * Copyright (c) 2010-2012 Herve Poussineau
+ * Copyright (c) 2011-2012 Andreas Färber
+ *
+ * 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 "qemu/osdep.h"
+#include "hw/isa/pc87312.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+
+#define REG_FER 0
+#define REG_FAR 1
+#define REG_PTR 2
+
+#define FER_PARALLEL_EN 0x01
+#define FER_UART1_EN 0x02
+#define FER_UART2_EN 0x04
+#define FER_FDC_EN 0x08
+#define FER_FDC_4 0x10
+#define FER_FDC_ADDR 0x20
+#define FER_IDE_EN 0x40
+#define FER_IDE_ADDR 0x80
+
+#define FAR_PARALLEL_ADDR 0x03
+#define FAR_UART1_ADDR 0x0C
+#define FAR_UART2_ADDR 0x30
+#define FAR_UART_3_4 0xC0
+
+#define PTR_POWER_DOWN 0x01
+#define PTR_CLOCK_DOWN 0x02
+#define PTR_PWDN 0x04
+#define PTR_IRQ_5_7 0x08
+#define PTR_UART1_TEST 0x10
+#define PTR_UART2_TEST 0x20
+#define PTR_LOCK_CONF 0x40
+#define PTR_EPP_MODE 0x80
+
+
+/* Parallel port */
+
+static bool is_parallel_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+ return index ? false : s->regs[REG_FER] & FER_PARALLEL_EN;
+}
+
+static const uint16_t parallel_base[] = { 0x378, 0x3bc, 0x278, 0x00 };
+
+static uint16_t get_parallel_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+ return parallel_base[s->regs[REG_FAR] & FAR_PARALLEL_ADDR];
+}
+
+static const unsigned int parallel_irq[] = { 5, 7, 5, 0 };
+
+static unsigned int get_parallel_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+ int idx;
+ idx = (s->regs[REG_FAR] & FAR_PARALLEL_ADDR);
+ if (idx == 0) {
+ return (s->regs[REG_PTR] & PTR_IRQ_5_7) ? 7 : 5;
+ } else {
+ return parallel_irq[idx];
+ }
+}
+
+
+/* UARTs */
+
+static const uint16_t uart_base[2][4] = {
+ { 0x3e8, 0x338, 0x2e8, 0x220 },
+ { 0x2e8, 0x238, 0x2e0, 0x228 }
+};
+
+static uint16_t get_uart_iobase(ISASuperIODevice *sio, uint8_t i)
+{
+ PC87312State *s = PC87312(sio);
+ int idx;
+ idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3;
+ if (idx == 0) {
+ return 0x3f8;
+ } else if (idx == 1) {
+ return 0x2f8;
+ } else {
+ return uart_base[idx & 1][(s->regs[REG_FAR] & FAR_UART_3_4) >> 6];
+ }
+}
+
+static unsigned int get_uart_irq(ISASuperIODevice *sio, uint8_t i)
+{
+ PC87312State *s = PC87312(sio);
+ int idx;
+ idx = (s->regs[REG_FAR] >> (2 * i + 2)) & 0x3;
+ return (idx & 1) ? 3 : 4;
+}
+
+static bool is_uart_enabled(ISASuperIODevice *sio, uint8_t i)
+{
+ PC87312State *s = PC87312(sio);
+ return s->regs[REG_FER] & (FER_UART1_EN << i);
+}
+
+
+/* Floppy controller */
+
+static bool is_fdc_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+ assert(!index);
+ return s->regs[REG_FER] & FER_FDC_EN;
+}
+
+static uint16_t get_fdc_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+ assert(!index);
+ return (s->regs[REG_FER] & FER_FDC_ADDR) ? 0x370 : 0x3f0;
+}
+
+static unsigned int get_fdc_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ assert(!index);
+ return 6;
+}
+
+
+/* IDE controller */
+
+static bool is_ide_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+
+ return s->regs[REG_FER] & FER_IDE_EN;
+}
+
+static uint16_t get_ide_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ PC87312State *s = PC87312(sio);
+
+ if (index == 1) {
+ return get_ide_iobase(sio, 0) + 0x206;
+ }
+ return (s->regs[REG_FER] & FER_IDE_ADDR) ? 0x170 : 0x1f0;
+}
+
+static unsigned int get_ide_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ assert(index == 0);
+ return 14;
+}
+
+static void reconfigure_devices(PC87312State *s)
+{
+ error_report("pc87312: unsupported device reconfiguration (%02x %02x %02x)",
+ s->regs[REG_FER], s->regs[REG_FAR], s->regs[REG_PTR]);
+}
+
+static void pc87312_soft_reset(PC87312State *s)
+{
+ static const uint8_t fer_init[] = {
+ 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4b, 0x4b,
+ 0x4b, 0x4b, 0x4b, 0x4b, 0x0f, 0x0f, 0x0f, 0x0f,
+ 0x49, 0x49, 0x49, 0x49, 0x07, 0x07, 0x07, 0x07,
+ 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x08, 0x00,
+ };
+ static const uint8_t far_init[] = {
+ 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x00, 0x01,
+ 0x01, 0x09, 0x08, 0x08, 0x10, 0x11, 0x39, 0x24,
+ 0x00, 0x01, 0x01, 0x00, 0x10, 0x11, 0x39, 0x24,
+ 0x10, 0x11, 0x11, 0x39, 0x24, 0x38, 0x10, 0x10,
+ };
+ static const uint8_t ptr_init[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ };
+
+ s->read_id_step = 0;
+ s->selected_index = REG_FER;
+
+ s->regs[REG_FER] = fer_init[s->config & 0x1f];
+ s->regs[REG_FAR] = far_init[s->config & 0x1f];
+ s->regs[REG_PTR] = ptr_init[s->config & 0x1f];
+}
+
+static void pc87312_hard_reset(PC87312State *s)
+{
+ pc87312_soft_reset(s);
+}
+
+static void pc87312_io_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int size)
+{
+ PC87312State *s = opaque;
+
+ trace_pc87312_io_write(addr, val);
+
+ if ((addr & 1) == 0) {
+ /* Index register */
+ s->read_id_step = 2;
+ s->selected_index = val;
+ } else {
+ /* Data register */
+ if (s->selected_index < 3) {
+ s->regs[s->selected_index] = val;
+ reconfigure_devices(s);
+ }
+ }
+}
+
+static uint64_t pc87312_io_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ PC87312State *s = opaque;
+ uint32_t val;
+
+ if ((addr & 1) == 0) {
+ /* Index register */
+ if (s->read_id_step++ == 0) {
+ val = 0x88;
+ } else if (s->read_id_step++ == 1) {
+ val = 0;
+ } else {
+ val = s->selected_index;
+ }
+ } else {
+ /* Data register */
+ if (s->selected_index < 3) {
+ val = s->regs[s->selected_index];
+ } else {
+ /* Invalid selected index */
+ val = 0;
+ }
+ }
+
+ trace_pc87312_io_read(addr, val);
+ return val;
+}
+
+static const MemoryRegionOps pc87312_io_ops = {
+ .read = pc87312_io_read,
+ .write = pc87312_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int pc87312_post_load(void *opaque, int version_id)
+{
+ PC87312State *s = opaque;
+
+ reconfigure_devices(s);
+ return 0;
+}
+
+static void pc87312_reset(DeviceState *d)
+{
+ PC87312State *s = PC87312(d);
+
+ pc87312_soft_reset(s);
+}
+
+static void pc87312_realize(DeviceState *dev, Error **errp)
+{
+ PC87312State *s;
+ ISADevice *isa;
+ Error *local_err = NULL;
+
+ s = PC87312(dev);
+ isa = ISA_DEVICE(dev);
+ isa_register_ioport(isa, &s->io, s->iobase);
+ pc87312_hard_reset(s);
+
+ ISA_SUPERIO_GET_CLASS(dev)->parent_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+}
+
+static void pc87312_initfn(Object *obj)
+{
+ PC87312State *s = PC87312(obj);
+
+ memory_region_init_io(&s->io, obj, &pc87312_io_ops, s, "pc87312", 2);
+}
+
+static const VMStateDescription vmstate_pc87312 = {
+ .name = "pc87312",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = pc87312_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(read_id_step, PC87312State),
+ VMSTATE_UINT8(selected_index, PC87312State),
+ VMSTATE_UINT8_ARRAY(regs, PC87312State, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property pc87312_properties[] = {
+ DEFINE_PROP_UINT16("iobase", PC87312State, iobase, 0x398),
+ DEFINE_PROP_UINT8("config", PC87312State, config, 1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pc87312_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ sc->parent_realize = dc->realize;
+ dc->realize = pc87312_realize;
+ dc->reset = pc87312_reset;
+ dc->vmsd = &vmstate_pc87312;
+ device_class_set_props(dc, pc87312_properties);
+
+ sc->parallel = (ISASuperIOFuncs){
+ .count = 1,
+ .is_enabled = is_parallel_enabled,
+ .get_iobase = get_parallel_iobase,
+ .get_irq = get_parallel_irq,
+ };
+ sc->serial = (ISASuperIOFuncs){
+ .count = 2,
+ .is_enabled = is_uart_enabled,
+ .get_iobase = get_uart_iobase,
+ .get_irq = get_uart_irq,
+ };
+ sc->floppy = (ISASuperIOFuncs){
+ .count = 1,
+ .is_enabled = is_fdc_enabled,
+ .get_iobase = get_fdc_iobase,
+ .get_irq = get_fdc_irq,
+ };
+ sc->ide = (ISASuperIOFuncs){
+ .count = 1,
+ .is_enabled = is_ide_enabled,
+ .get_iobase = get_ide_iobase,
+ .get_irq = get_ide_irq,
+ };
+}
+
+static const TypeInfo pc87312_type_info = {
+ .name = TYPE_PC87312,
+ .parent = TYPE_ISA_SUPERIO,
+ .instance_size = sizeof(PC87312State),
+ .instance_init = pc87312_initfn,
+ .class_init = pc87312_class_init,
+ /* FIXME use a qdev drive property instead of drive_get() */
+};
+
+static void pc87312_register_types(void)
+{
+ type_register_static(&pc87312_type_info);
+}
+
+type_init(pc87312_register_types)
diff --git a/hw/isa/piix3.c b/hw/isa/piix3.c
new file mode 100644
index 000000000..dab901c9a
--- /dev/null
+++ b/hw/isa/piix3.c
@@ -0,0 +1,395 @@
+/*
+ * QEMU PIIX PCI ISA Bridge Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/range.h"
+#include "hw/southbridge/piix.h"
+#include "hw/irq.h"
+#include "hw/isa/isa.h"
+#include "hw/xen/xen.h"
+#include "sysemu/xen.h"
+#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
+#include "migration/vmstate.h"
+
+#define XEN_PIIX_NUM_PIRQS 128ULL
+
+#define TYPE_PIIX3_DEVICE "PIIX3"
+#define TYPE_PIIX3_XEN_DEVICE "PIIX3-xen"
+
+static void piix3_set_irq_pic(PIIX3State *piix3, int pic_irq)
+{
+ qemu_set_irq(piix3->pic[pic_irq],
+ !!(piix3->pic_levels &
+ (((1ULL << PIIX_NUM_PIRQS) - 1) <<
+ (pic_irq * PIIX_NUM_PIRQS))));
+}
+
+static void piix3_set_irq_level_internal(PIIX3State *piix3, int pirq, int level)
+{
+ int pic_irq;
+ uint64_t mask;
+
+ pic_irq = piix3->dev.config[PIIX_PIRQCA + pirq];
+ if (pic_irq >= PIIX_NUM_PIC_IRQS) {
+ return;
+ }
+
+ mask = 1ULL << ((pic_irq * PIIX_NUM_PIRQS) + pirq);
+ piix3->pic_levels &= ~mask;
+ piix3->pic_levels |= mask * !!level;
+}
+
+static void piix3_set_irq_level(PIIX3State *piix3, int pirq, int level)
+{
+ int pic_irq;
+
+ pic_irq = piix3->dev.config[PIIX_PIRQCA + pirq];
+ if (pic_irq >= PIIX_NUM_PIC_IRQS) {
+ return;
+ }
+
+ piix3_set_irq_level_internal(piix3, pirq, level);
+
+ piix3_set_irq_pic(piix3, pic_irq);
+}
+
+static void piix3_set_irq(void *opaque, int pirq, int level)
+{
+ PIIX3State *piix3 = opaque;
+ piix3_set_irq_level(piix3, pirq, level);
+}
+
+static PCIINTxRoute piix3_route_intx_pin_to_irq(void *opaque, int pin)
+{
+ PIIX3State *piix3 = opaque;
+ int irq = piix3->dev.config[PIIX_PIRQCA + pin];
+ PCIINTxRoute route;
+
+ if (irq < PIIX_NUM_PIC_IRQS) {
+ route.mode = PCI_INTX_ENABLED;
+ route.irq = irq;
+ } else {
+ route.mode = PCI_INTX_DISABLED;
+ route.irq = -1;
+ }
+ return route;
+}
+
+/* irq routing is changed. so rebuild bitmap */
+static void piix3_update_irq_levels(PIIX3State *piix3)
+{
+ PCIBus *bus = pci_get_bus(&piix3->dev);
+ int pirq;
+
+ piix3->pic_levels = 0;
+ for (pirq = 0; pirq < PIIX_NUM_PIRQS; pirq++) {
+ piix3_set_irq_level(piix3, pirq, pci_bus_get_irq_level(bus, pirq));
+ }
+}
+
+static void piix3_write_config(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_default_write_config(dev, address, val, len);
+ if (ranges_overlap(address, len, PIIX_PIRQCA, 4)) {
+ PIIX3State *piix3 = PIIX3_PCI_DEVICE(dev);
+ int pic_irq;
+
+ pci_bus_fire_intx_routing_notifier(pci_get_bus(&piix3->dev));
+ piix3_update_irq_levels(piix3);
+ for (pic_irq = 0; pic_irq < PIIX_NUM_PIC_IRQS; pic_irq++) {
+ piix3_set_irq_pic(piix3, pic_irq);
+ }
+ }
+}
+
+static void piix3_write_config_xen(PCIDevice *dev,
+ uint32_t address, uint32_t val, int len)
+{
+ xen_piix_pci_write_config_client(address, val, len);
+ piix3_write_config(dev, address, val, len);
+}
+
+static void piix3_reset(void *opaque)
+{
+ PIIX3State *d = opaque;
+ uint8_t *pci_conf = d->dev.config;
+
+ pci_conf[0x04] = 0x07; /* master, memory and I/O */
+ pci_conf[0x05] = 0x00;
+ pci_conf[0x06] = 0x00;
+ pci_conf[0x07] = 0x02; /* PCI_status_devsel_medium */
+ pci_conf[0x4c] = 0x4d;
+ pci_conf[0x4e] = 0x03;
+ pci_conf[0x4f] = 0x00;
+ pci_conf[0x60] = 0x80;
+ pci_conf[0x61] = 0x80;
+ pci_conf[0x62] = 0x80;
+ pci_conf[0x63] = 0x80;
+ pci_conf[0x69] = 0x02;
+ pci_conf[0x70] = 0x80;
+ pci_conf[0x76] = 0x0c;
+ pci_conf[0x77] = 0x0c;
+ pci_conf[0x78] = 0x02;
+ pci_conf[0x79] = 0x00;
+ pci_conf[0x80] = 0x00;
+ pci_conf[0x82] = 0x00;
+ pci_conf[0xa0] = 0x08;
+ pci_conf[0xa2] = 0x00;
+ pci_conf[0xa3] = 0x00;
+ pci_conf[0xa4] = 0x00;
+ pci_conf[0xa5] = 0x00;
+ pci_conf[0xa6] = 0x00;
+ pci_conf[0xa7] = 0x00;
+ pci_conf[0xa8] = 0x0f;
+ pci_conf[0xaa] = 0x00;
+ pci_conf[0xab] = 0x00;
+ pci_conf[0xac] = 0x00;
+ pci_conf[0xae] = 0x00;
+
+ d->pic_levels = 0;
+ d->rcr = 0;
+}
+
+static int piix3_post_load(void *opaque, int version_id)
+{
+ PIIX3State *piix3 = opaque;
+ int pirq;
+
+ /*
+ * Because the i8259 has not been deserialized yet, qemu_irq_raise
+ * might bring the system to a different state than the saved one;
+ * for example, the interrupt could be masked but the i8259 would
+ * not know that yet and would trigger an interrupt in the CPU.
+ *
+ * Here, we update irq levels without raising the interrupt.
+ * Interrupt state will be deserialized separately through the i8259.
+ */
+ piix3->pic_levels = 0;
+ for (pirq = 0; pirq < PIIX_NUM_PIRQS; pirq++) {
+ piix3_set_irq_level_internal(piix3, pirq,
+ pci_bus_get_irq_level(pci_get_bus(&piix3->dev), pirq));
+ }
+ return 0;
+}
+
+static int piix3_pre_save(void *opaque)
+{
+ int i;
+ PIIX3State *piix3 = opaque;
+
+ for (i = 0; i < ARRAY_SIZE(piix3->pci_irq_levels_vmstate); i++) {
+ piix3->pci_irq_levels_vmstate[i] =
+ pci_bus_get_irq_level(pci_get_bus(&piix3->dev), i);
+ }
+
+ return 0;
+}
+
+static bool piix3_rcr_needed(void *opaque)
+{
+ PIIX3State *piix3 = opaque;
+
+ return (piix3->rcr != 0);
+}
+
+static const VMStateDescription vmstate_piix3_rcr = {
+ .name = "PIIX3/rcr",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = piix3_rcr_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(rcr, PIIX3State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_piix3 = {
+ .name = "PIIX3",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = piix3_post_load,
+ .pre_save = piix3_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PIIX3State),
+ VMSTATE_INT32_ARRAY_V(pci_irq_levels_vmstate, PIIX3State,
+ PIIX_NUM_PIRQS, 3),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_piix3_rcr,
+ NULL
+ }
+};
+
+
+static void rcr_write(void *opaque, hwaddr addr, uint64_t val, unsigned len)
+{
+ PIIX3State *d = opaque;
+
+ if (val & 4) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return;
+ }
+ d->rcr = val & 2; /* keep System Reset type only */
+}
+
+static uint64_t rcr_read(void *opaque, hwaddr addr, unsigned len)
+{
+ PIIX3State *d = opaque;
+
+ return d->rcr;
+}
+
+static const MemoryRegionOps rcr_ops = {
+ .read = rcr_read,
+ .write = rcr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+static void piix3_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX3State *d = PIIX3_PCI_DEVICE(dev);
+
+ if (!isa_bus_new(DEVICE(d), get_system_memory(),
+ pci_address_space_io(dev), errp)) {
+ return;
+ }
+
+ memory_region_init_io(&d->rcr_mem, OBJECT(dev), &rcr_ops, d,
+ "piix3-reset-control", 1);
+ memory_region_add_subregion_overlap(pci_address_space_io(dev),
+ PIIX_RCR_IOPORT, &d->rcr_mem, 1);
+
+ qemu_register_reset(piix3_reset, d);
+}
+
+static void pci_piix3_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_piix3;
+ dc->hotpluggable = false;
+ k->realize = piix3_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ /* 82371SB PIIX3 PCI-to-ISA bridge (Step A1) */
+ k->device_id = PCI_DEVICE_ID_INTEL_82371SB_0;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ /*
+ * Reason: part of PIIX3 southbridge, needs to be wired up by
+ * pc_piix.c's pc_init1()
+ */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo piix3_pci_type_info = {
+ .name = TYPE_PIIX3_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX3State),
+ .abstract = true,
+ .class_init = pci_piix3_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void piix3_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_write = piix3_write_config;
+}
+
+static const TypeInfo piix3_info = {
+ .name = TYPE_PIIX3_DEVICE,
+ .parent = TYPE_PIIX3_PCI_DEVICE,
+ .class_init = piix3_class_init,
+};
+
+static void piix3_xen_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->config_write = piix3_write_config_xen;
+};
+
+static const TypeInfo piix3_xen_info = {
+ .name = TYPE_PIIX3_XEN_DEVICE,
+ .parent = TYPE_PIIX3_PCI_DEVICE,
+ .class_init = piix3_xen_class_init,
+};
+
+static void piix3_register_types(void)
+{
+ type_register_static(&piix3_pci_type_info);
+ type_register_static(&piix3_info);
+ type_register_static(&piix3_xen_info);
+}
+
+type_init(piix3_register_types)
+
+/*
+ * 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 pci_intx)
+{
+ int slot_addend;
+ slot_addend = PCI_SLOT(pci_dev->devfn) - 1;
+ return (pci_intx + slot_addend) & 3;
+}
+
+PIIX3State *piix3_create(PCIBus *pci_bus, ISABus **isa_bus)
+{
+ PIIX3State *piix3;
+ PCIDevice *pci_dev;
+
+ /*
+ * Xen supports additional interrupt routes from the PCI devices to
+ * the IOAPIC: the four pins of each PCI device on the bus are also
+ * connected to the IOAPIC directly.
+ * These additional routes can be discovered through ACPI.
+ */
+ if (xen_enabled()) {
+ pci_dev = pci_create_simple_multifunction(pci_bus, -1, true,
+ TYPE_PIIX3_XEN_DEVICE);
+ piix3 = PIIX3_PCI_DEVICE(pci_dev);
+ pci_bus_irqs(pci_bus, xen_piix3_set_irq, xen_pci_slot_get_pirq,
+ piix3, XEN_PIIX_NUM_PIRQS);
+ } else {
+ pci_dev = pci_create_simple_multifunction(pci_bus, -1, true,
+ TYPE_PIIX3_DEVICE);
+ piix3 = PIIX3_PCI_DEVICE(pci_dev);
+ pci_bus_irqs(pci_bus, piix3_set_irq, pci_slot_get_pirq,
+ piix3, PIIX_NUM_PIRQS);
+ pci_bus_set_route_irq_fn(pci_bus, piix3_route_intx_pin_to_irq);
+ }
+ *isa_bus = ISA_BUS(qdev_get_child_bus(DEVICE(piix3), "isa.0"));
+
+ return piix3;
+}
diff --git a/hw/isa/piix4.c b/hw/isa/piix4.c
new file mode 100644
index 000000000..0fe7b69bc
--- /dev/null
+++ b/hw/isa/piix4.c
@@ -0,0 +1,275 @@
+/*
+ * QEMU PIIX4 PCI Bridge Emulation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2018 Hervé Poussineau
+ *
+ * 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 "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "hw/southbridge/piix.h"
+#include "hw/pci/pci.h"
+#include "hw/isa/isa.h"
+#include "hw/intc/i8259.h"
+#include "hw/dma/i8257.h"
+#include "hw/timer/i8254.h"
+#include "hw/rtc/mc146818rtc.h"
+#include "hw/ide/pci.h"
+#include "migration/vmstate.h"
+#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
+#include "qom/object.h"
+
+PCIDevice *piix4_dev;
+
+struct PIIX4State {
+ PCIDevice dev;
+ qemu_irq cpu_intr;
+ qemu_irq *isa;
+
+ RTCState rtc;
+ /* Reset Control Register */
+ MemoryRegion rcr_mem;
+ uint8_t rcr;
+};
+
+OBJECT_DECLARE_SIMPLE_TYPE(PIIX4State, PIIX4_PCI_DEVICE)
+
+static void piix4_isa_reset(DeviceState *dev)
+{
+ PIIX4State *d = PIIX4_PCI_DEVICE(dev);
+ uint8_t *pci_conf = d->dev.config;
+
+ pci_conf[0x04] = 0x07; // master, memory and I/O
+ pci_conf[0x05] = 0x00;
+ pci_conf[0x06] = 0x00;
+ pci_conf[0x07] = 0x02; // PCI_status_devsel_medium
+ pci_conf[0x4c] = 0x4d;
+ pci_conf[0x4e] = 0x03;
+ pci_conf[0x4f] = 0x00;
+ pci_conf[0x60] = 0x0a; // PCI A -> IRQ 10
+ pci_conf[0x61] = 0x0a; // PCI B -> IRQ 10
+ pci_conf[0x62] = 0x0b; // PCI C -> IRQ 11
+ pci_conf[0x63] = 0x0b; // PCI D -> IRQ 11
+ pci_conf[0x69] = 0x02;
+ pci_conf[0x70] = 0x80;
+ pci_conf[0x76] = 0x0c;
+ pci_conf[0x77] = 0x0c;
+ pci_conf[0x78] = 0x02;
+ pci_conf[0x79] = 0x00;
+ pci_conf[0x80] = 0x00;
+ pci_conf[0x82] = 0x00;
+ pci_conf[0xa0] = 0x08;
+ pci_conf[0xa2] = 0x00;
+ pci_conf[0xa3] = 0x00;
+ pci_conf[0xa4] = 0x00;
+ pci_conf[0xa5] = 0x00;
+ pci_conf[0xa6] = 0x00;
+ pci_conf[0xa7] = 0x00;
+ pci_conf[0xa8] = 0x0f;
+ pci_conf[0xaa] = 0x00;
+ pci_conf[0xab] = 0x00;
+ pci_conf[0xac] = 0x00;
+ pci_conf[0xae] = 0x00;
+}
+
+static int piix4_ide_post_load(void *opaque, int version_id)
+{
+ PIIX4State *s = opaque;
+
+ if (version_id == 2) {
+ s->rcr = 0;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_piix4 = {
+ .name = "PIIX4",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .post_load = piix4_ide_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PIIX4State),
+ VMSTATE_UINT8_V(rcr, PIIX4State, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void piix4_request_i8259_irq(void *opaque, int irq, int level)
+{
+ PIIX4State *s = opaque;
+ qemu_set_irq(s->cpu_intr, level);
+}
+
+static void piix4_set_i8259_irq(void *opaque, int irq, int level)
+{
+ PIIX4State *s = opaque;
+ qemu_set_irq(s->isa[irq], level);
+}
+
+static void piix4_rcr_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned int len)
+{
+ PIIX4State *s = opaque;
+
+ if (val & 4) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return;
+ }
+
+ s->rcr = val & 2; /* keep System Reset type only */
+}
+
+static uint64_t piix4_rcr_read(void *opaque, hwaddr addr, unsigned int len)
+{
+ PIIX4State *s = opaque;
+
+ return s->rcr;
+}
+
+static const MemoryRegionOps piix4_rcr_ops = {
+ .read = piix4_rcr_read,
+ .write = piix4_rcr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void piix4_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX4State *s = PIIX4_PCI_DEVICE(dev);
+ ISABus *isa_bus;
+ qemu_irq *i8259_out_irq;
+
+ isa_bus = isa_bus_new(DEVICE(dev), pci_address_space(dev),
+ pci_address_space_io(dev), errp);
+ if (!isa_bus) {
+ return;
+ }
+
+ qdev_init_gpio_in_named(DEVICE(dev), piix4_set_i8259_irq,
+ "isa", ISA_NUM_IRQS);
+ qdev_init_gpio_out_named(DEVICE(dev), &s->cpu_intr,
+ "intr", 1);
+
+ memory_region_init_io(&s->rcr_mem, OBJECT(dev), &piix4_rcr_ops, s,
+ "reset-control", 1);
+ memory_region_add_subregion_overlap(pci_address_space_io(dev),
+ PIIX_RCR_IOPORT, &s->rcr_mem, 1);
+
+ /* initialize i8259 pic */
+ i8259_out_irq = qemu_allocate_irqs(piix4_request_i8259_irq, s, 1);
+ s->isa = i8259_init(isa_bus, *i8259_out_irq);
+
+ /* initialize ISA irqs */
+ isa_bus_irqs(isa_bus, s->isa);
+
+ /* initialize pit */
+ i8254_pit_init(isa_bus, 0x40, 0, NULL);
+
+ /* DMA */
+ i8257_dma_init(isa_bus, 0);
+
+ /* RTC */
+ qdev_prop_set_int32(DEVICE(&s->rtc), "base_year", 2000);
+ if (!qdev_realize(DEVICE(&s->rtc), BUS(isa_bus), errp)) {
+ return;
+ }
+ isa_init_irq(ISA_DEVICE(&s->rtc), &s->rtc.irq, RTC_ISA_IRQ);
+
+ piix4_dev = dev;
+}
+
+static void piix4_init(Object *obj)
+{
+ PIIX4State *s = PIIX4_PCI_DEVICE(obj);
+
+ object_initialize(&s->rtc, sizeof(s->rtc), TYPE_MC146818_RTC);
+}
+
+static void piix4_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = piix4_realize;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371AB_0;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ dc->reset = piix4_isa_reset;
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_piix4;
+ /*
+ * Reason: part of PIIX4 southbridge, needs to be wired up,
+ * e.g. by mips_malta_init()
+ */
+ dc->user_creatable = false;
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo piix4_info = {
+ .name = TYPE_PIIX4_PCI_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX4State),
+ .instance_init = piix4_init,
+ .class_init = piix4_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void piix4_register_types(void)
+{
+ type_register_static(&piix4_info);
+}
+
+type_init(piix4_register_types)
+
+DeviceState *piix4_create(PCIBus *pci_bus, ISABus **isa_bus, I2CBus **smbus)
+{
+ PCIDevice *pci;
+ DeviceState *dev;
+ int devfn = PCI_DEVFN(10, 0);
+
+ pci = pci_create_simple_multifunction(pci_bus, devfn, true,
+ TYPE_PIIX4_PCI_DEVICE);
+ dev = DEVICE(pci);
+ if (isa_bus) {
+ *isa_bus = ISA_BUS(qdev_get_child_bus(dev, "isa.0"));
+ }
+
+ pci = pci_create_simple(pci_bus, devfn + 1, "piix4-ide");
+ pci_ide_create_devs(pci);
+
+ pci_create_simple(pci_bus, devfn + 2, "piix4-usb-uhci");
+ if (smbus) {
+ *smbus = piix4_pm_init(pci_bus, devfn + 3, 0x1100,
+ qdev_get_gpio_in_named(dev, "isa", 9),
+ NULL, 0, NULL);
+ }
+
+ return dev;
+}
diff --git a/hw/isa/smc37c669-superio.c b/hw/isa/smc37c669-superio.c
new file mode 100644
index 000000000..18287741c
--- /dev/null
+++ b/hw/isa/smc37c669-superio.c
@@ -0,0 +1,116 @@
+/*
+ * SMC FDC37C669 Super I/O controller
+ *
+ * Copyright (c) 2018 Philippe Mathieu-Daudé
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/superio.h"
+#include "qemu/module.h"
+
+/* UARTs (compatible with NS16450 or PC16550) */
+
+static bool is_serial_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ return index < 2;
+}
+
+static uint16_t get_serial_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ return index ? 0x2f8 : 0x3f8;
+}
+
+static unsigned int get_serial_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ return index ? 3 : 4;
+}
+
+/* Parallel port */
+
+static bool is_parallel_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ return index < 1;
+}
+
+static uint16_t get_parallel_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ return 0x378;
+}
+
+static unsigned int get_parallel_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ return 7;
+}
+
+static unsigned int get_parallel_dma(ISASuperIODevice *sio, uint8_t index)
+{
+ return 3;
+}
+
+/* Diskette controller (Software compatible with the Intel PC8477) */
+
+static bool is_fdc_enabled(ISASuperIODevice *sio, uint8_t index)
+{
+ return index < 1;
+}
+
+static uint16_t get_fdc_iobase(ISASuperIODevice *sio, uint8_t index)
+{
+ return 0x3f0;
+}
+
+static unsigned int get_fdc_irq(ISASuperIODevice *sio, uint8_t index)
+{
+ return 6;
+}
+
+static unsigned int get_fdc_dma(ISASuperIODevice *sio, uint8_t index)
+{
+ return 2;
+}
+
+static void smc37c669_class_init(ObjectClass *klass, void *data)
+{
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ sc->parallel = (ISASuperIOFuncs){
+ .count = 1,
+ .is_enabled = is_parallel_enabled,
+ .get_iobase = get_parallel_iobase,
+ .get_irq = get_parallel_irq,
+ .get_dma = get_parallel_dma,
+ };
+ sc->serial = (ISASuperIOFuncs){
+ .count = 2,
+ .is_enabled = is_serial_enabled,
+ .get_iobase = get_serial_iobase,
+ .get_irq = get_serial_irq,
+ };
+ sc->floppy = (ISASuperIOFuncs){
+ .count = 1,
+ .is_enabled = is_fdc_enabled,
+ .get_iobase = get_fdc_iobase,
+ .get_irq = get_fdc_irq,
+ .get_dma = get_fdc_dma,
+ };
+ sc->ide.count = 0;
+}
+
+static const TypeInfo smc37c669_type_info = {
+ .name = TYPE_SMC37C669_SUPERIO,
+ .parent = TYPE_ISA_SUPERIO,
+ .instance_size = sizeof(ISASuperIODevice),
+ .class_size = sizeof(ISASuperIOClass),
+ .class_init = smc37c669_class_init,
+};
+
+static void smc37c669_register_types(void)
+{
+ type_register_static(&smc37c669_type_info);
+}
+
+type_init(smc37c669_register_types)
diff --git a/hw/isa/trace-events b/hw/isa/trace-events
new file mode 100644
index 000000000..b8f877e1e
--- /dev/null
+++ b/hw/isa/trace-events
@@ -0,0 +1,23 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# isa-superio.c
+superio_create_parallel(int id, uint16_t base, unsigned int irq) "id=%d, base 0x%03x, irq %u"
+superio_create_serial(int id, uint16_t base, unsigned int irq) "id=%d, base 0x%03x, irq %u"
+superio_create_floppy(int id, uint16_t base, unsigned int irq) "id=%d, base 0x%03x, irq %u"
+superio_create_ide(int id, uint16_t base, unsigned int irq) "id=%d, base 0x%03x, irq %u"
+
+# pc87312.c
+pc87312_io_read(uint32_t addr, uint32_t val) "read addr=0x%x val=0x%x"
+pc87312_io_write(uint32_t addr, uint32_t val) "write addr=0x%x val=0x%x"
+
+# apm.c
+apm_io_read(uint8_t addr, uint8_t val) "read addr=0x%x val=0x%02x"
+apm_io_write(uint8_t addr, uint8_t val) "write addr=0x%x val=0x%02x"
+
+# vt82c686.c
+via_isa_write(uint32_t addr, uint32_t val, int len) "addr 0x%x val 0x%x len 0x%x"
+via_pm_write(uint32_t addr, uint32_t val, int len) "addr 0x%x val 0x%x len 0x%x"
+via_pm_io_read(uint32_t addr, uint32_t val, int len) "addr 0x%x val 0x%x len 0x%x"
+via_pm_io_write(uint32_t addr, uint32_t val, int len) "addr 0x%x val 0x%x len 0x%x"
+via_superio_read(uint8_t addr, uint8_t val) "addr 0x%x val 0x%x"
+via_superio_write(uint8_t addr, uint32_t val) "addr 0x%x val 0x%x"
diff --git a/hw/isa/trace.h b/hw/isa/trace.h
new file mode 100644
index 000000000..501205cfc
--- /dev/null
+++ b/hw/isa/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_isa.h"
diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c
new file mode 100644
index 000000000..8f656251b
--- /dev/null
+++ b/hw/isa/vt82c686.c
@@ -0,0 +1,754 @@
+/*
+ * VT82C686B south bridge support
+ *
+ * Copyright (c) 2008 yajin (yajin@vm-kernel.org)
+ * Copyright (c) 2009 chenming (chenming@rdc.faw.com.cn)
+ * Copyright (c) 2010 Huacai Chen (zltjiangshi@gmail.com)
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ * VT8231 south bridge support and general clean up to allow it
+ * Copyright (c) 2018-2020 BALATON Zoltan
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/vt82c686.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/isa/isa.h"
+#include "hw/isa/superio.h"
+#include "hw/intc/i8259.h"
+#include "hw/irq.h"
+#include "hw/dma/i8257.h"
+#include "hw/timer/i8254.h"
+#include "hw/rtc/mc146818rtc.h"
+#include "migration/vmstate.h"
+#include "hw/isa/apm.h"
+#include "hw/acpi/acpi.h"
+#include "hw/i2c/pm_smbus.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/range.h"
+#include "qemu/timer.h"
+#include "trace.h"
+
+#define TYPE_VIA_PM "via-pm"
+OBJECT_DECLARE_SIMPLE_TYPE(ViaPMState, VIA_PM)
+
+struct ViaPMState {
+ PCIDevice dev;
+ MemoryRegion io;
+ ACPIREGS ar;
+ APMState apm;
+ PMSMBus smb;
+};
+
+static void pm_io_space_update(ViaPMState *s)
+{
+ uint32_t pmbase = pci_get_long(s->dev.config + 0x48) & 0xff80UL;
+
+ memory_region_transaction_begin();
+ memory_region_set_address(&s->io, pmbase);
+ memory_region_set_enabled(&s->io, s->dev.config[0x41] & BIT(7));
+ memory_region_transaction_commit();
+}
+
+static void smb_io_space_update(ViaPMState *s)
+{
+ uint32_t smbase = pci_get_long(s->dev.config + 0x90) & 0xfff0UL;
+
+ memory_region_transaction_begin();
+ memory_region_set_address(&s->smb.io, smbase);
+ memory_region_set_enabled(&s->smb.io, s->dev.config[0xd2] & BIT(0));
+ memory_region_transaction_commit();
+}
+
+static int vmstate_acpi_post_load(void *opaque, int version_id)
+{
+ ViaPMState *s = opaque;
+
+ pm_io_space_update(s);
+ smb_io_space_update(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_acpi = {
+ .name = "vt82c686b_pm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_acpi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, ViaPMState),
+ VMSTATE_UINT16(ar.pm1.evt.sts, ViaPMState),
+ VMSTATE_UINT16(ar.pm1.evt.en, ViaPMState),
+ VMSTATE_UINT16(ar.pm1.cnt.cnt, ViaPMState),
+ VMSTATE_STRUCT(apm, ViaPMState, 0, vmstate_apm, APMState),
+ VMSTATE_TIMER_PTR(ar.tmr.timer, ViaPMState),
+ VMSTATE_INT64(ar.tmr.overflow_time, ViaPMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pm_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int len)
+{
+ ViaPMState *s = VIA_PM(d);
+
+ trace_via_pm_write(addr, val, len);
+ pci_default_write_config(d, addr, val, len);
+ if (ranges_overlap(addr, len, 0x48, 4)) {
+ uint32_t v = pci_get_long(s->dev.config + 0x48);
+ pci_set_long(s->dev.config + 0x48, (v & 0xff80UL) | 1);
+ }
+ if (range_covers_byte(addr, len, 0x41)) {
+ pm_io_space_update(s);
+ }
+ if (ranges_overlap(addr, len, 0x90, 4)) {
+ uint32_t v = pci_get_long(s->dev.config + 0x90);
+ pci_set_long(s->dev.config + 0x90, (v & 0xfff0UL) | 1);
+ }
+ if (range_covers_byte(addr, len, 0xd2)) {
+ s->dev.config[0xd2] &= 0xf;
+ smb_io_space_update(s);
+ }
+}
+
+static void pm_io_write(void *op, hwaddr addr, uint64_t data, unsigned size)
+{
+ trace_via_pm_io_write(addr, data, size);
+}
+
+static uint64_t pm_io_read(void *op, hwaddr addr, unsigned size)
+{
+ trace_via_pm_io_read(addr, 0, size);
+ return 0;
+}
+
+static const MemoryRegionOps pm_io_ops = {
+ .read = pm_io_read,
+ .write = pm_io_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void pm_update_sci(ViaPMState *s)
+{
+ int sci_level, pmsts;
+
+ pmsts = acpi_pm1_evt_get_sts(&s->ar);
+ sci_level = (((pmsts & s->ar.pm1.evt.en) &
+ (ACPI_BITMASK_RT_CLOCK_ENABLE |
+ ACPI_BITMASK_POWER_BUTTON_ENABLE |
+ ACPI_BITMASK_GLOBAL_LOCK_ENABLE |
+ ACPI_BITMASK_TIMER_ENABLE)) != 0);
+ if (pci_get_byte(s->dev.config + PCI_INTERRUPT_PIN)) {
+ /*
+ * FIXME:
+ * Fix device model that realizes this PM device and remove
+ * this work around.
+ * The device model should wire SCI and setup
+ * PCI_INTERRUPT_PIN properly.
+ * If PIN# = 0(interrupt pin isn't used), don't raise SCI as
+ * work around.
+ */
+ pci_set_irq(&s->dev, sci_level);
+ }
+ /* schedule a timer interruption if needed */
+ acpi_pm_tmr_update(&s->ar, (s->ar.pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) &&
+ !(pmsts & ACPI_BITMASK_TIMER_STATUS));
+}
+
+static void pm_tmr_timer(ACPIREGS *ar)
+{
+ ViaPMState *s = container_of(ar, ViaPMState, ar);
+ pm_update_sci(s);
+}
+
+static void via_pm_reset(DeviceState *d)
+{
+ ViaPMState *s = VIA_PM(d);
+
+ memset(s->dev.config + PCI_CONFIG_HEADER_SIZE, 0,
+ PCI_CONFIG_SPACE_SIZE - PCI_CONFIG_HEADER_SIZE);
+ /* Power Management IO base */
+ pci_set_long(s->dev.config + 0x48, 1);
+ /* SMBus IO base */
+ pci_set_long(s->dev.config + 0x90, 1);
+
+ acpi_pm1_evt_reset(&s->ar);
+ acpi_pm1_cnt_reset(&s->ar);
+ acpi_pm_tmr_reset(&s->ar);
+ pm_update_sci(s);
+
+ pm_io_space_update(s);
+ smb_io_space_update(s);
+}
+
+static void via_pm_realize(PCIDevice *dev, Error **errp)
+{
+ ViaPMState *s = VIA_PM(dev);
+
+ pci_set_word(dev->config + PCI_STATUS, PCI_STATUS_FAST_BACK |
+ PCI_STATUS_DEVSEL_MEDIUM);
+
+ pm_smbus_init(DEVICE(s), &s->smb, false);
+ memory_region_add_subregion(pci_address_space_io(dev), 0, &s->smb.io);
+ memory_region_set_enabled(&s->smb.io, false);
+
+ apm_init(dev, &s->apm, NULL, s);
+
+ memory_region_init_io(&s->io, OBJECT(dev), &pm_io_ops, s, "via-pm", 128);
+ memory_region_add_subregion(pci_address_space_io(dev), 0, &s->io);
+ memory_region_set_enabled(&s->io, false);
+
+ acpi_pm_tmr_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_evt_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_cnt_init(&s->ar, &s->io, false, false, 2, false);
+}
+
+typedef struct via_pm_init_info {
+ uint16_t device_id;
+} ViaPMInitInfo;
+
+static void via_pm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ ViaPMInitInfo *info = data;
+
+ k->realize = via_pm_realize;
+ k->config_write = pm_write_config;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = info->device_id;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ k->revision = 0x40;
+ dc->reset = via_pm_reset;
+ /* Reason: part of VIA south bridge, does not exist stand alone */
+ dc->user_creatable = false;
+ dc->vmsd = &vmstate_acpi;
+}
+
+static const TypeInfo via_pm_info = {
+ .name = TYPE_VIA_PM,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ViaPMState),
+ .abstract = true,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static const ViaPMInitInfo vt82c686b_pm_init_info = {
+ .device_id = PCI_DEVICE_ID_VIA_82C686B_PM,
+};
+
+static const TypeInfo vt82c686b_pm_info = {
+ .name = TYPE_VT82C686B_PM,
+ .parent = TYPE_VIA_PM,
+ .class_init = via_pm_class_init,
+ .class_data = (void *)&vt82c686b_pm_init_info,
+};
+
+static const ViaPMInitInfo vt8231_pm_init_info = {
+ .device_id = PCI_DEVICE_ID_VIA_8231_PM,
+};
+
+static const TypeInfo vt8231_pm_info = {
+ .name = TYPE_VT8231_PM,
+ .parent = TYPE_VIA_PM,
+ .class_init = via_pm_class_init,
+ .class_data = (void *)&vt8231_pm_init_info,
+};
+
+
+#define TYPE_VIA_SUPERIO "via-superio"
+OBJECT_DECLARE_SIMPLE_TYPE(ViaSuperIOState, VIA_SUPERIO)
+
+struct ViaSuperIOState {
+ ISASuperIODevice superio;
+ uint8_t regs[0x100];
+ const MemoryRegionOps *io_ops;
+ MemoryRegion io;
+};
+
+static inline void via_superio_io_enable(ViaSuperIOState *s, bool enable)
+{
+ memory_region_set_enabled(&s->io, enable);
+}
+
+static void via_superio_realize(DeviceState *d, Error **errp)
+{
+ ViaSuperIOState *s = VIA_SUPERIO(d);
+ ISASuperIOClass *ic = ISA_SUPERIO_GET_CLASS(s);
+ Error *local_err = NULL;
+
+ assert(s->io_ops);
+ ic->parent_realize(d, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ memory_region_init_io(&s->io, OBJECT(d), s->io_ops, s, "via-superio", 2);
+ memory_region_set_enabled(&s->io, false);
+ /* The floppy also uses 0x3f0 and 0x3f1 but this seems to work anyway */
+ memory_region_add_subregion(isa_address_space_io(ISA_DEVICE(s)), 0x3f0,
+ &s->io);
+}
+
+static uint64_t via_superio_cfg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ ViaSuperIOState *sc = opaque;
+ uint8_t idx = sc->regs[0];
+ uint8_t val = sc->regs[idx];
+
+ if (addr == 0) {
+ return idx;
+ }
+ if (addr == 1 && idx == 0) {
+ val = 0; /* reading reg 0 where we store index value */
+ }
+ trace_via_superio_read(idx, val);
+ return val;
+}
+
+static void via_superio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ sc->parent_realize = dc->realize;
+ dc->realize = via_superio_realize;
+}
+
+static const TypeInfo via_superio_info = {
+ .name = TYPE_VIA_SUPERIO,
+ .parent = TYPE_ISA_SUPERIO,
+ .instance_size = sizeof(ViaSuperIOState),
+ .class_size = sizeof(ISASuperIOClass),
+ .class_init = via_superio_class_init,
+ .abstract = true,
+};
+
+#define TYPE_VT82C686B_SUPERIO "vt82c686b-superio"
+
+static void vt82c686b_superio_cfg_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ ViaSuperIOState *sc = opaque;
+ uint8_t idx = sc->regs[0];
+
+ if (addr == 0) { /* config index register */
+ sc->regs[0] = data;
+ return;
+ }
+
+ /* config data register */
+ trace_via_superio_write(idx, data);
+ switch (idx) {
+ case 0x00 ... 0xdf:
+ case 0xe4:
+ case 0xe5:
+ case 0xe9 ... 0xed:
+ case 0xf3:
+ case 0xf5:
+ case 0xf7:
+ case 0xf9 ... 0xfb:
+ case 0xfd ... 0xff:
+ /* ignore write to read only registers */
+ return;
+ /* case 0xe6 ... 0xe8: Should set base port of parallel and serial */
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "via_superio_cfg: unimplemented register 0x%x\n", idx);
+ break;
+ }
+ sc->regs[idx] = data;
+}
+
+static const MemoryRegionOps vt82c686b_superio_cfg_ops = {
+ .read = via_superio_cfg_read,
+ .write = vt82c686b_superio_cfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void vt82c686b_superio_reset(DeviceState *dev)
+{
+ ViaSuperIOState *s = VIA_SUPERIO(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ /* Device ID */
+ vt82c686b_superio_cfg_write(s, 0, 0xe0, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0x3c, 1);
+ /* Function select - all disabled */
+ vt82c686b_superio_cfg_write(s, 0, 0xe2, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0x03, 1);
+ /* Floppy ctrl base addr 0x3f0-7 */
+ vt82c686b_superio_cfg_write(s, 0, 0xe3, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0xfc, 1);
+ /* Parallel port base addr 0x378-f */
+ vt82c686b_superio_cfg_write(s, 0, 0xe6, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0xde, 1);
+ /* Serial port 1 base addr 0x3f8-f */
+ vt82c686b_superio_cfg_write(s, 0, 0xe7, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0xfe, 1);
+ /* Serial port 2 base addr 0x2f8-f */
+ vt82c686b_superio_cfg_write(s, 0, 0xe8, 1);
+ vt82c686b_superio_cfg_write(s, 1, 0xbe, 1);
+
+ vt82c686b_superio_cfg_write(s, 0, 0, 1);
+}
+
+static void vt82c686b_superio_init(Object *obj)
+{
+ VIA_SUPERIO(obj)->io_ops = &vt82c686b_superio_cfg_ops;
+}
+
+static void vt82c686b_superio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ dc->reset = vt82c686b_superio_reset;
+ sc->serial.count = 2;
+ sc->parallel.count = 1;
+ sc->ide.count = 0; /* emulated by via-ide */
+ sc->floppy.count = 1;
+}
+
+static const TypeInfo vt82c686b_superio_info = {
+ .name = TYPE_VT82C686B_SUPERIO,
+ .parent = TYPE_VIA_SUPERIO,
+ .instance_size = sizeof(ViaSuperIOState),
+ .instance_init = vt82c686b_superio_init,
+ .class_size = sizeof(ISASuperIOClass),
+ .class_init = vt82c686b_superio_class_init,
+};
+
+
+#define TYPE_VT8231_SUPERIO "vt8231-superio"
+
+static void vt8231_superio_cfg_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ ViaSuperIOState *sc = opaque;
+ uint8_t idx = sc->regs[0];
+
+ if (addr == 0) { /* config index register */
+ sc->regs[0] = data;
+ return;
+ }
+
+ /* config data register */
+ trace_via_superio_write(idx, data);
+ switch (idx) {
+ case 0x00 ... 0xdf:
+ case 0xe7 ... 0xef:
+ case 0xf0 ... 0xf1:
+ case 0xf5:
+ case 0xf8:
+ case 0xfd:
+ /* ignore write to read only registers */
+ return;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "via_superio_cfg: unimplemented register 0x%x\n", idx);
+ break;
+ }
+ sc->regs[idx] = data;
+}
+
+static const MemoryRegionOps vt8231_superio_cfg_ops = {
+ .read = via_superio_cfg_read,
+ .write = vt8231_superio_cfg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void vt8231_superio_reset(DeviceState *dev)
+{
+ ViaSuperIOState *s = VIA_SUPERIO(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ /* Device ID */
+ s->regs[0xf0] = 0x3c;
+ /* Device revision */
+ s->regs[0xf1] = 0x01;
+ /* Function select - all disabled */
+ vt8231_superio_cfg_write(s, 0, 0xf2, 1);
+ vt8231_superio_cfg_write(s, 1, 0x03, 1);
+ /* Serial port base addr */
+ vt8231_superio_cfg_write(s, 0, 0xf4, 1);
+ vt8231_superio_cfg_write(s, 1, 0xfe, 1);
+ /* Parallel port base addr */
+ vt8231_superio_cfg_write(s, 0, 0xf6, 1);
+ vt8231_superio_cfg_write(s, 1, 0xde, 1);
+ /* Floppy ctrl base addr */
+ vt8231_superio_cfg_write(s, 0, 0xf7, 1);
+ vt8231_superio_cfg_write(s, 1, 0xfc, 1);
+
+ vt8231_superio_cfg_write(s, 0, 0, 1);
+}
+
+static void vt8231_superio_init(Object *obj)
+{
+ VIA_SUPERIO(obj)->io_ops = &vt8231_superio_cfg_ops;
+}
+
+static uint16_t vt8231_superio_serial_iobase(ISASuperIODevice *sio,
+ uint8_t index)
+{
+ return 0x2f8; /* FIXME: This should be settable via registers f2-f4 */
+}
+
+static void vt8231_superio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISASuperIOClass *sc = ISA_SUPERIO_CLASS(klass);
+
+ dc->reset = vt8231_superio_reset;
+ sc->serial.count = 1;
+ sc->serial.get_iobase = vt8231_superio_serial_iobase;
+ sc->parallel.count = 1;
+ sc->ide.count = 0; /* emulated by via-ide */
+ sc->floppy.count = 1;
+}
+
+static const TypeInfo vt8231_superio_info = {
+ .name = TYPE_VT8231_SUPERIO,
+ .parent = TYPE_VIA_SUPERIO,
+ .instance_size = sizeof(ViaSuperIOState),
+ .instance_init = vt8231_superio_init,
+ .class_size = sizeof(ISASuperIOClass),
+ .class_init = vt8231_superio_class_init,
+};
+
+
+#define TYPE_VIA_ISA "via-isa"
+OBJECT_DECLARE_SIMPLE_TYPE(ViaISAState, VIA_ISA)
+
+struct ViaISAState {
+ PCIDevice dev;
+ qemu_irq cpu_intr;
+ qemu_irq *isa_irqs;
+ ISABus *isa_bus;
+ ViaSuperIOState *via_sio;
+};
+
+static const VMStateDescription vmstate_via = {
+ .name = "via-isa",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, ViaISAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const TypeInfo via_isa_info = {
+ .name = TYPE_VIA_ISA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ViaISAState),
+ .abstract = true,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+void via_isa_set_irq(PCIDevice *d, int n, int level)
+{
+ ViaISAState *s = VIA_ISA(d);
+ qemu_set_irq(s->isa_irqs[n], level);
+}
+
+static void via_isa_request_i8259_irq(void *opaque, int irq, int level)
+{
+ ViaISAState *s = opaque;
+ qemu_set_irq(s->cpu_intr, level);
+}
+
+static void via_isa_realize(PCIDevice *d, Error **errp)
+{
+ ViaISAState *s = VIA_ISA(d);
+ DeviceState *dev = DEVICE(d);
+ qemu_irq *isa_irq;
+ int i;
+
+ qdev_init_gpio_out(dev, &s->cpu_intr, 1);
+ isa_irq = qemu_allocate_irqs(via_isa_request_i8259_irq, s, 1);
+ s->isa_bus = isa_bus_new(dev, get_system_memory(), pci_address_space_io(d),
+ &error_fatal);
+ s->isa_irqs = i8259_init(s->isa_bus, *isa_irq);
+ isa_bus_irqs(s->isa_bus, s->isa_irqs);
+ i8254_pit_init(s->isa_bus, 0x40, 0, NULL);
+ i8257_dma_init(s->isa_bus, 0);
+ mc146818_rtc_init(s->isa_bus, 2000, NULL);
+
+ for (i = 0; i < PCI_CONFIG_HEADER_SIZE; i++) {
+ if (i < PCI_COMMAND || i >= PCI_REVISION_ID) {
+ d->wmask[i] = 0;
+ }
+ }
+}
+
+/* TYPE_VT82C686B_ISA */
+
+static void vt82c686b_write_config(PCIDevice *d, uint32_t addr,
+ uint32_t val, int len)
+{
+ ViaISAState *s = VIA_ISA(d);
+
+ trace_via_isa_write(addr, val, len);
+ pci_default_write_config(d, addr, val, len);
+ if (addr == 0x85) {
+ /* BIT(1): enable or disable superio config io ports */
+ via_superio_io_enable(s->via_sio, val & BIT(1));
+ }
+}
+
+static void vt82c686b_isa_reset(DeviceState *dev)
+{
+ ViaISAState *s = VIA_ISA(dev);
+ uint8_t *pci_conf = s->dev.config;
+
+ pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SPECIAL);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_conf[0x48] = 0x01; /* Miscellaneous Control 3 */
+ pci_conf[0x4a] = 0x04; /* IDE interrupt Routing */
+ pci_conf[0x4f] = 0x03; /* DMA/Master Mem Access Control 3 */
+ pci_conf[0x50] = 0x2d; /* PnP DMA Request Control */
+ pci_conf[0x59] = 0x04;
+ pci_conf[0x5a] = 0x04; /* KBC/RTC Control*/
+ pci_conf[0x5f] = 0x04;
+ pci_conf[0x77] = 0x10; /* GPIO Control 1/2/3/4 */
+}
+
+static void vt82c686b_realize(PCIDevice *d, Error **errp)
+{
+ ViaISAState *s = VIA_ISA(d);
+
+ via_isa_realize(d, errp);
+ s->via_sio = VIA_SUPERIO(isa_create_simple(s->isa_bus,
+ TYPE_VT82C686B_SUPERIO));
+}
+
+static void vt82c686b_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt82c686b_realize;
+ k->config_write = vt82c686b_write_config;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_82C686B_ISA;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ k->revision = 0x40;
+ dc->reset = vt82c686b_isa_reset;
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_via;
+ /* Reason: part of VIA VT82C686 southbridge, needs to be wired up */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo vt82c686b_isa_info = {
+ .name = TYPE_VT82C686B_ISA,
+ .parent = TYPE_VIA_ISA,
+ .instance_size = sizeof(ViaISAState),
+ .class_init = vt82c686b_class_init,
+};
+
+/* TYPE_VT8231_ISA */
+
+static void vt8231_write_config(PCIDevice *d, uint32_t addr,
+ uint32_t val, int len)
+{
+ ViaISAState *s = VIA_ISA(d);
+
+ trace_via_isa_write(addr, val, len);
+ pci_default_write_config(d, addr, val, len);
+ if (addr == 0x50) {
+ /* BIT(2): enable or disable superio config io ports */
+ via_superio_io_enable(s->via_sio, val & BIT(2));
+ }
+}
+
+static void vt8231_isa_reset(DeviceState *dev)
+{
+ ViaISAState *s = VIA_ISA(dev);
+ uint8_t *pci_conf = s->dev.config;
+
+ pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0);
+ pci_set_word(pci_conf + PCI_COMMAND, PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
+ PCI_COMMAND_MASTER | PCI_COMMAND_SPECIAL);
+ pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM);
+
+ pci_conf[0x58] = 0x40; /* Miscellaneous Control 0 */
+ pci_conf[0x67] = 0x08; /* Fast IR Config */
+ pci_conf[0x6b] = 0x01; /* Fast IR I/O Base */
+}
+
+static void vt8231_realize(PCIDevice *d, Error **errp)
+{
+ ViaISAState *s = VIA_ISA(d);
+
+ via_isa_realize(d, errp);
+ s->via_sio = VIA_SUPERIO(isa_create_simple(s->isa_bus,
+ TYPE_VT8231_SUPERIO));
+}
+
+static void vt8231_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = vt8231_realize;
+ k->config_write = vt8231_write_config;
+ k->vendor_id = PCI_VENDOR_ID_VIA;
+ k->device_id = PCI_DEVICE_ID_VIA_8231_ISA;
+ k->class_id = PCI_CLASS_BRIDGE_ISA;
+ k->revision = 0x10;
+ dc->reset = vt8231_isa_reset;
+ dc->desc = "ISA bridge";
+ dc->vmsd = &vmstate_via;
+ /* Reason: part of VIA VT8231 southbridge, needs to be wired up */
+ dc->user_creatable = false;
+}
+
+static const TypeInfo vt8231_isa_info = {
+ .name = TYPE_VT8231_ISA,
+ .parent = TYPE_VIA_ISA,
+ .instance_size = sizeof(ViaISAState),
+ .class_init = vt8231_class_init,
+};
+
+
+static void vt82c686b_register_types(void)
+{
+ type_register_static(&via_pm_info);
+ type_register_static(&vt82c686b_pm_info);
+ type_register_static(&vt8231_pm_info);
+ type_register_static(&via_superio_info);
+ type_register_static(&vt82c686b_superio_info);
+ type_register_static(&vt8231_superio_info);
+ type_register_static(&via_isa_info);
+ type_register_static(&vt82c686b_isa_info);
+ type_register_static(&vt8231_isa_info);
+}
+
+type_init(vt82c686b_register_types)