qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 1/2] ich9: add TCO interface emulation


From: Paulo Alcantara
Subject: [Qemu-devel] [PATCH 1/2] ich9: add TCO interface emulation
Date: Tue, 19 May 2015 23:02:54 -0300

This interface is provides some registers within a 32-byte range and can
be accessed through PCI-to-LPC bridge interface (PMBASE + 0x60).

It's commonly used as a watchdog timer to detect system lockups through
SMIs that are generated by it (if TCO_EN bit is enabled) on every
timeout and then giving the system firmware the ability to perform any
action -- if NO_REBOOT bit is not set in GCS (General Control and Status
register), the system will be reseted upon second TCO timeout.

This patch focus on adding full support to TCO watchdog logic and a few
features like mapping NMIs to SMIs (NMI2SMI_EN bit), system intruder
detection (TCO interrupt due to INTRUDER# signal), etc. are not
implemented yet.

Signed-off-by: Paulo Alcantara <address@hidden>
---
 hw/acpi/Makefile.objs  |   1 +
 hw/acpi/ich9.c         |  36 +++++++++
 hw/acpi/tco.c          | 194 +++++++++++++++++++++++++++++++++++++++++++++++++
 hw/isa/lpc_ich9.c      |  10 +++
 include/hw/acpi/ich9.h |   4 +
 include/hw/acpi/tco.h  | 139 +++++++++++++++++++++++++++++++++++
 include/hw/i386/ich9.h |   8 ++
 7 files changed, 392 insertions(+)
 create mode 100644 hw/acpi/tco.c
 create mode 100644 include/hw/acpi/tco.h

diff --git a/hw/acpi/Makefile.objs b/hw/acpi/Makefile.objs
index b9fefa7..a32b7f8 100644
--- a/hw/acpi/Makefile.objs
+++ b/hw/acpi/Makefile.objs
@@ -1,4 +1,5 @@
 common-obj-$(CONFIG_ACPI) += core.o piix4.o ich9.o pcihp.o cpu_hotplug.o
+common-obj-$(CONFIG_ACPI) += tco.o
 common-obj-$(CONFIG_ACPI) += memory_hotplug.o
 common-obj-$(CONFIG_ACPI) += acpi_interface.o
 common-obj-$(CONFIG_ACPI) += bios-linker-loader.o
diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c
index 84e5bb8..8c4364b 100644
--- a/hw/acpi/ich9.c
+++ b/hw/acpi/ich9.c
@@ -30,6 +30,7 @@
 #include "qemu/timer.h"
 #include "sysemu/sysemu.h"
 #include "hw/acpi/acpi.h"
+#include "hw/acpi/tco.h"
 #include "sysemu/kvm.h"
 #include "exec/address-spaces.h"
 
@@ -92,8 +93,15 @@ static void ich9_smi_writel(void *opaque, hwaddr addr, 
uint64_t val,
                             unsigned width)
 {
     ICH9LPCPMRegs *pm = opaque;
+    TCOIORegs *tr = &pm->tco_regs;
+
     switch (addr) {
     case 0:
+        /* once TCO_LOCK bit is set, TCO_EN bit cannot be overwritten */
+        if (tr->tco.cnt1 & TCO_LOCK) {
+            val &= ~ICH9_PMIO_SMI_EN_TCO_EN;
+            val |= pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN;
+        }
         pm->smi_en = val;
         break;
     }
@@ -107,6 +115,29 @@ static const MemoryRegionOps ich9_smi_ops = {
     .endianness = DEVICE_LITTLE_ENDIAN,
 };
 
+static uint64_t ich9_tco_readw(void *opaque, hwaddr addr, unsigned width)
+{
+    ICH9LPCPMRegs *pm = opaque;
+    return acpi_pm_tco_ioport_readw(&pm->tco_regs, addr);
+}
+
+static void ich9_tco_writew(void *opaque, hwaddr addr, uint64_t val,
+                            unsigned width)
+{
+    ICH9LPCPMRegs *pm = opaque;
+    acpi_pm_tco_ioport_writew(&pm->tco_regs, addr, val);
+}
+
+static const MemoryRegionOps ich9_tco_ops = {
+    .read = ich9_tco_readw,
+    .write = ich9_tco_writew,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 4,
+    .impl.min_access_size = 1,
+    .impl.max_access_size = 2,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
 void ich9_pm_iospace_update(ICH9LPCPMRegs *pm, uint32_t pm_io_base)
 {
     ICH9_DEBUG("to 0x%x\n", pm_io_base);
@@ -230,6 +261,11 @@ void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm,
                           "acpi-smi", 8);
     memory_region_add_subregion(&pm->io, ICH9_PMIO_SMI_EN, &pm->io_smi);
 
+    acpi_pm_tco_init(&pm->tco_regs);
+    memory_region_init_io(&pm->io_tco, OBJECT(lpc_pci), &ich9_tco_ops, pm,
+                         "sm-tco", ICH9_PMIO_TCO_LEN);
+    memory_region_add_subregion(&pm->io, ICH9_PMIO_TCO_RLD, &pm->io_tco);
+
     pm->irq = sci_irq;
     qemu_register_reset(pm_reset, pm);
     pm->powerdown_notifier.notify = pm_powerdown_req;
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c
new file mode 100644
index 0000000..702881d
--- /dev/null
+++ b/hw/acpi/tco.c
@@ -0,0 +1,194 @@
+/*
+ * QEMU ICH9 TCO emulation
+ *
+ * Copyright (c) 2015 Paulo Alcantara <address@hidden>
+ *
+ * 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-common.h"
+#include "qapi/visitor.h"
+#include "qemu/range.h"
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/ich9.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+
+#include "hw/acpi/ich9.h"
+#include "hw/acpi/tco.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define TCO_DEBUG(fmt, ...)                                     \
+    do {                                                        \
+        fprintf(stderr, "%s "fmt, __func__, ## __VA_ARGS__);    \
+    } while (0)
+#else
+#define TCO_DEBUG(fmt, ...) do { } while (0)
+#endif
+
+static QEMUTimer *tco_timer;
+static unsigned int timeouts_no;
+
+static void tco_timer_reload(void)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+    timer_del(tco_timer);
+    timer_mod(tco_timer, now + tco_ticks_per_sec());
+}
+
+static void tco_timer_stop(void)
+{
+    timer_del(tco_timer);
+}
+
+static void tco_timer_expired(void *opaque)
+{
+    TCOIORegs *tr = opaque;
+    ICH9LPCPMRegs *pm = container_of(tr, ICH9LPCPMRegs, tco_regs);
+    ICH9LPCState *lpc = container_of(pm, ICH9LPCState, pm);
+    uint32_t gcs = pci_get_long(lpc->chip_config + ICH9_LPC_RCBA_GCS);
+
+    tr->tco.rld--;
+    if ((tr->tco.rld & TCO_RLD_MASK) == 0) {
+        goto out;
+    }
+
+    tr->tco.sts1 |= TCO_TIMEOUT;
+    if (++timeouts_no == 2) {
+        tr->tco.sts1 |= TCO_SECOND_TO_STS;
+        tr->tco.sts1 |= TCO_BOOT_STS;
+        timeouts_no = 0;
+
+        if (!(gcs & ICH9_LPC_RCBA_GCS_NO_REBOOT)) {
+            tco_timer_stop();
+            qemu_system_reset_request();
+            return;
+        }
+    }
+    tr->tco.rld = tr->tco.tmr;
+
+    if (pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN) {
+        ich9_generate_smi();
+    } else {
+        ich9_generate_nmi();
+    }
+
+out:
+    tco_timer_reload();
+}
+
+void acpi_pm_tco_init(TCOIORegs *tr)
+{
+    *tr = TCO_IO_REGS_DEFAULTS_INIT();
+    tco_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tco_timer_expired, tr);
+}
+
+uint32_t acpi_pm_tco_ioport_readw(TCOIORegs *tr, uint32_t addr)
+{
+    switch (addr) {
+    case TCO_RLD:
+        return tr->tco.rld;
+    case TCO_DAT_IN:
+        return tr->tco.din;
+    case TCO_DAT_OUT:
+        return tr->tco.dout;
+    case TCO1_STS:
+        return tr->tco.sts1;
+    case TCO2_STS:
+        return tr->tco.sts2;
+    case TCO1_CNT:
+        return tr->tco.cnt1;
+    case TCO2_CNT:
+        return tr->tco.cnt2;
+    case TCO_MESSAGE1:
+        return tr->tco.msg1;
+    case TCO_MESSAGE2:
+        return tr->tco.msg2;
+    case TCO_WDCNT:
+        return tr->tco.wdcnt;
+    case TCO_TMR:
+        return tr->tco.tmr;
+    case SW_IRQ_GEN:
+        return tr->sw_irq_gen;
+    }
+    return 0;
+}
+
+void acpi_pm_tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val)
+{
+    switch (addr) {
+    case TCO_RLD:
+        if (can_start_tco_timer(tr)) {
+            tr->tco.rld = tr->tco.tmr;
+            tco_timer_reload();
+        } else {
+            tr->tco.rld = val;
+        }
+        break;
+    case TCO_DAT_IN:
+        tr->tco.din = val;
+        tr->tco.sts1 |= SW_TCO_SMI;
+        ich9_generate_smi();
+        break;
+    case TCO_DAT_OUT:
+        tr->tco.dout = val;
+        tr->tco.sts1 |= TCO_INT_STS;
+        /* TODO: cause an interrupt, as selected by the TCO_INT_SEL bits */
+        break;
+    case TCO1_STS:
+        tr->tco.sts1 = val & TCO1_STS_MASK;
+        break;
+    case TCO2_STS:
+        tr->tco.sts2 = val & TCO2_STS_MASK;
+        break;
+    case TCO1_CNT:
+        val &= TCO1_CNT_MASK;
+        /* TCO_LOCK bit cannot be changed once set */
+        tr->tco.cnt1 = (val & ~TCO_LOCK) | (tr->tco.cnt1 & TCO_LOCK);
+        if (can_start_tco_timer(tr)) {
+            tr->tco.rld = tr->tco.tmr;
+            tco_timer_reload();
+        } else {
+            tco_timer_stop();
+        }
+        break;
+    case TCO2_CNT:
+        tr->tco.cnt2 = val;
+        break;
+    case TCO_MESSAGE1:
+        tr->tco.msg1 = val;
+        break;
+    case TCO_MESSAGE2:
+        tr->tco.msg2 = val;
+        break;
+    case TCO_WDCNT:
+        tr->tco.wdcnt = val;
+        break;
+    case TCO_TMR:
+        tr->tco.tmr = val;
+        break;
+    case SW_IRQ_GEN:
+        tr->sw_irq_gen = val;
+        break;
+    }
+}
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c
index dba7585..7bfb683 100644
--- a/hw/isa/lpc_ich9.c
+++ b/hw/isa/lpc_ich9.c
@@ -313,6 +313,16 @@ PCIINTxRoute ich9_route_intx_pin_to_irq(void *opaque, int 
pirq_pin)
     return route;
 }
 
+void ich9_generate_smi(void)
+{
+    cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI);
+}
+
+void ich9_generate_nmi(void)
+{
+    cpu_interrupt(first_cpu, CPU_INTERRUPT_NMI);
+}
+
 static int ich9_lpc_sci_irq(ICH9LPCState *lpc)
 {
     switch (lpc->d.config[ICH9_LPC_ACPI_CTRL] &
diff --git a/include/hw/acpi/ich9.h b/include/hw/acpi/ich9.h
index c2d3dba..31c74af 100644
--- a/include/hw/acpi/ich9.h
+++ b/include/hw/acpi/ich9.h
@@ -25,6 +25,7 @@
 #include "hw/acpi/cpu_hotplug.h"
 #include "hw/acpi/memory_hotplug.h"
 #include "hw/acpi/acpi_dev_interface.h"
+#include "hw/acpi/tco.h"
 
 typedef struct ICH9LPCPMRegs {
     /*
@@ -37,6 +38,7 @@ typedef struct ICH9LPCPMRegs {
     MemoryRegion io;
     MemoryRegion io_gpe;
     MemoryRegion io_smi;
+    MemoryRegion io_tco;
 
     uint32_t smi_en;
     uint32_t smi_sts;
@@ -53,6 +55,8 @@ typedef struct ICH9LPCPMRegs {
     uint8_t disable_s3;
     uint8_t disable_s4;
     uint8_t s4_val;
+
+    TCOIORegs tco_regs;
 } ICH9LPCPMRegs;
 
 void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm,
diff --git a/include/hw/acpi/tco.h b/include/hw/acpi/tco.h
new file mode 100644
index 0000000..bd333f6
--- /dev/null
+++ b/include/hw/acpi/tco.h
@@ -0,0 +1,139 @@
+/*
+ * QEMU ICH9 TCO emulation
+ *
+ * Copyright (c) 2015 Paulo Alcantara <address@hidden>
+ *
+ * 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.
+ */
+#ifndef HW_ACPI_TCO_H
+#define HW_ACPI_TCO_H
+
+#include "qemu/typedefs.h"
+#include "qemu-common.h"
+
+/* TCO I/O register offsets */
+enum {
+    TCO_RLD           = 0x00,
+    TCO_DAT_IN        = 0x02,
+    TCO_DAT_OUT       = 0x03,
+    TCO1_STS          = 0x04,
+    TCO2_STS          = 0x06,
+    TCO1_CNT          = 0x08,
+    TCO2_CNT          = 0x0a,
+    TCO_MESSAGE1      = 0x0c,
+    TCO_MESSAGE2      = 0x0d,
+    TCO_WDCNT         = 0x0e,
+    SW_IRQ_GEN        = 0x10,
+    TCO_TMR           = 0x12,
+};
+
+/* TCO I/O register defaults */
+enum {
+    TCO_RLD_DEFAULT         = 0x0000,
+    TCO_DAT_IN_DEFAULT      = 0x00,
+    TCO_DAT_OUT_DEFAULT     = 0x00,
+    TCO1_STS_DEFAULT        = 0x0000,
+    TCO2_STS_DEFAULT        = 0x0000,
+    TCO1_CNT_DEFAULT        = 0x0000,
+    TCO2_CNT_DEFAULT        = 0x0008,
+    TCO_MESSAGE1_DEFAULT    = 0x00,
+    TCO_MESSAGE2_DEFAULT    = 0x00,
+    TCO_WDCNT_DEFAULT       = 0x00,
+    TCO_TMR_DEFAULT         = 0x0004,
+    SW_IRQ_GEN_DEFAULT      = 0x03,
+};
+
+/* TCO I/O register control/status bits */
+enum {
+    SW_TCO_SMI           = (1 << 1),
+    TCO_INT_STS          = (1 << 2),
+    TCO_LOCK             = (1  << 12),
+    TCO_TMR_HLT          = (1 << 11),
+    TCO_TIMEOUT          = (1 << 3),
+    TCO_SECOND_TO_STS    = (1 << 1),
+    TCO_BOOT_STS         = (1 << 2),
+};
+
+/* TCO I/O registers mask bits */
+enum {
+    TCO_RLD_MASK     = 0x3ff,
+    TCO1_STS_MASK    = 0xe870,
+    TCO2_STS_MASK    = 0xfff8,
+    TCO1_CNT_MASK    = 0xfeff,
+    TCO_TMR_MASK     = 0x3ff,
+};
+
+typedef struct TCOIORegs {
+    struct {
+        uint16_t rld;
+        uint8_t din;
+        uint8_t dout;
+        uint16_t sts1;
+        uint16_t sts2;
+        uint16_t cnt1;
+        uint16_t cnt2;
+        uint8_t msg1;
+        uint8_t msg2;
+        uint8_t wdcnt;
+        uint16_t tmr;
+    } tco;
+    uint8_t sw_irq_gen;
+} TCOIORegs;
+
+#define TCO_IO_REGS_DEFAULTS_INIT()             \
+    (TCOIORegs) {                               \
+        .tco = {                                \
+            .rld      = TCO_RLD_DEFAULT,        \
+            .din      = TCO_DAT_IN_DEFAULT,     \
+            .dout     = TCO_DAT_OUT_DEFAULT,    \
+            .sts1     = TCO1_STS_DEFAULT,       \
+            .sts2     = TCO2_STS_DEFAULT,       \
+            .cnt1     = TCO1_CNT_DEFAULT,       \
+            .cnt2     = TCO2_CNT_DEFAULT,       \
+            .msg1     = TCO_MESSAGE1_DEFAULT,   \
+            .msg2     = TCO_MESSAGE2_DEFAULT,   \
+            .wdcnt    = TCO_WDCNT_DEFAULT,      \
+            .tmr      = TCO_TMR_DEFAULT,        \
+        },                                      \
+        .sw_irq_gen = SW_IRQ_GEN_DEFAULT        \
+    }
+
+/* tco.c */
+void acpi_pm_tco_init(TCOIORegs *tr);
+uint32_t acpi_pm_tco_ioport_readw(TCOIORegs *tr, uint32_t addr);
+void acpi_pm_tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val);
+
+/* As per ICH9 spec, the internal timer has an error of ~0.6s on every tick */
+static inline int64_t tco_ticks_per_sec(void)
+{
+    return 600000000LL;
+}
+
+static inline int is_valid_tco_time(uint32_t val)
+{
+    /* values of 0 or 1 will be ignored by ICH */
+    return val > 1;
+}
+
+static inline int can_start_tco_timer(TCOIORegs *tr)
+{
+    return !(tr->tco.cnt1 & TCO_TMR_HLT) && is_valid_tco_time(tr->tco.tmr);
+}
+
+#endif /* HW_ACPI_TCO_H */
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h
index f4e522c..f41cca6 100644
--- a/include/hw/i386/ich9.h
+++ b/include/hw/i386/ich9.h
@@ -20,6 +20,9 @@ PCIINTxRoute ich9_route_intx_pin_to_irq(void *opaque, int 
pirq_pin);
 void ich9_lpc_pm_init(PCIDevice *pci_lpc);
 I2CBus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base);
 
+void ich9_generate_smi(void);
+void ich9_generate_nmi(void);
+
 #define ICH9_CC_SIZE                            (16 * 1024)     /* 16KB */
 
 #define TYPE_ICH9_LPC_DEVICE "ICH9-LPC"
@@ -156,6 +159,8 @@ Object *ich9_lpc_find(void);
 #define ICH9_LPC_RCBA_BA_MASK                   Q35_MASK(32, 31, 14)
 #define ICH9_LPC_RCBA_EN                        0x1
 #define ICH9_LPC_RCBA_DEFAULT                   0x0
+#define ICH9_LPC_RCBA_GCS                       0x3410
+#define ICH9_LPC_RCBA_GCS_NO_REBOOT             (1 << 5)
 
 #define ICH9_LPC_PIC_NUM_PINS                   16
 #define ICH9_LPC_IOAPIC_NUM_PINS                24
@@ -180,7 +185,10 @@ Object *ich9_lpc_find(void);
 #define ICH9_PMIO_GPE0_LEN                      16
 #define ICH9_PMIO_SMI_EN                        0x30
 #define ICH9_PMIO_SMI_EN_APMC_EN                (1 << 5)
+#define ICH9_PMIO_SMI_EN_TCO_EN                 (1 << 13)
 #define ICH9_PMIO_SMI_STS                       0x34
+#define ICH9_PMIO_TCO_RLD                       0x60
+#define ICH9_PMIO_TCO_LEN                       32
 
 /* FADT ACPI_ENABLE/ACPI_DISABLE */
 #define ICH9_APM_ACPI_ENABLE                    0x2
-- 
2.1.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]