From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:57911) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z6pk8-0002WZ-1c for qemu-devel@nongnu.org; Sun, 21 Jun 2015 20:37:47 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z6pk4-0003KQ-P7 for qemu-devel@nongnu.org; Sun, 21 Jun 2015 20:37:43 -0400 Received: from mail-qk0-x235.google.com ([2607:f8b0:400d:c09::235]:33007) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z6pk4-0003KM-I4 for qemu-devel@nongnu.org; Sun, 21 Jun 2015 20:37:40 -0400 Received: by qkhu186 with SMTP id u186so92896110qkh.0 for ; Sun, 21 Jun 2015 17:37:39 -0700 (PDT) From: Paulo Alcantara Date: Sun, 21 Jun 2015 21:37:01 -0300 Message-Id: <1434933423-10496-1-git-send-email-pcacjr@zytor.com> In-Reply-To: <1432686576-14816-1-git-send-email-pcacjr@zytor.com> References: <1432686576-14816-1-git-send-email-pcacjr@zytor.com> Subject: [Qemu-devel] [PATCH v4 1/3] ich9: add TCO interface emulation List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: pbonzini@redhat.com, seabios@seabios.org, Paulo Alcantara , mst@redhat.com This interface provides some registers within a 32-byte range and can be acessed 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 -- if TCO_EN bit is set -- on every timeout. If NO_REBOOT bit is not set in GCS (General Control and Status register), the system will be resetted upon second timeout if TCO_RLD register wasn't previously written to prevent timeout. This patch adds support to TCO watchdog logic and few other features like mapping NMIs to SMIs (NMI2SMI_EN bit), system intruder detection, etc. are not implemented yet. Signed-off-by: Paulo Alcantara --- v1 -> v2: * add migration support for TCO I/O device state * wake up only when total time expired instead of every 0.6s * some cleanup suggested by Paolo Bonzini v2 -> v3: * set SECOND_TO_STS and BOOT_STS bits in TCO2_STS instead * improve handling of TCO_LOCK bit in TCO1_CNT register v3 -> v4: * fix some conflicts in hw/acpi/ich9.c after rebasing against master * remove meaningless "use_tco" field from TCOIORegs structure * add a object property named "enable_tco" and only enable TCO support on pc-q35-2.4 and later --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 55 +++++++++- hw/acpi/tco.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/i386/pc_q35.c | 4 +- hw/isa/lpc_ich9.c | 15 ++- include/hw/acpi/ich9.h | 7 +- include/hw/acpi/tco.h | 98 +++++++++++++++++ include/hw/boards.h | 3 +- include/hw/i386/ich9.h | 10 +- include/hw/i386/pc.h | 1 + 10 files changed, 466 insertions(+), 8 deletions(-) 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 29d46d8..3db1f07 100644 --- a/hw/acpi/Makefile.objs +++ b/hw/acpi/Makefile.objs @@ -1,4 +1,4 @@ -common-obj-$(CONFIG_ACPI_X86) += core.o piix4.o ich9.o pcihp.o +common-obj-$(CONFIG_ACPI_X86) += core.o piix4.o ich9.o pcihp.o tco.o common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu_hotplug.o common-obj-$(CONFIG_ACPI_MEMORY_HOTPLUG) += memory_hotplug.o common-obj-$(CONFIG_ACPI) += acpi_interface.o diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c index 8a64ffb..d3d9953 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,16 @@ static void ich9_smi_writel(void *opaque, hwaddr addr, uint64_t val, unsigned width) { ICH9LPCPMRegs *pm = opaque; + TCOIORegs *tr = &pm->tco_regs; + uint64_t tco_en; + switch (addr) { case 0: + tco_en = pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN; + /* once TCO_LOCK bit is set, TCO_EN bit cannot be overwritten */ + if (tr->tco.cnt1 & TCO_LOCK) { + val = (val & ~ICH9_PMIO_SMI_EN_TCO_EN) | tco_en; + } pm->smi_en &= ~pm->smi_en_wmask; pm->smi_en |= (val & pm->smi_en_wmask); break; @@ -159,6 +168,25 @@ static const VMStateDescription vmstate_memhp_state = { } }; +static bool vmstate_test_use_tco(void *opaque) +{ + ICH9LPCPMRegs *s = opaque; + return s->enable_tco; +} + +static const VMStateDescription vmstate_tco_io_state = { + .name = "ich9_pm/tco", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .needed = vmstate_test_use_tco, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(tco_regs, ICH9LPCPMRegs, 1, vmstate_tco_io_sts, + TCOIORegs), + VMSTATE_END_OF_LIST() + } +}; + const VMStateDescription vmstate_ich9_pm = { .name = "ich9_pm", .version_id = 1, @@ -179,6 +207,10 @@ const VMStateDescription vmstate_ich9_pm = { .subsections = (const VMStateDescription*[]) { &vmstate_memhp_state, NULL + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_tco_io_state, + NULL } }; @@ -209,7 +241,7 @@ static void pm_powerdown_req(Notifier *n, void *opaque) acpi_pm1_evt_power_down(&pm->acpi_regs); } -void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, +void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, bool enable_tco, qemu_irq sci_irq) { memory_region_init(&pm->io, OBJECT(lpc_pci), "ich9-pm", ICH9_PMIO_SIZE); @@ -231,6 +263,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); + pm->enable_tco = enable_tco; + if (pm->enable_tco) { + acpi_pm_tco_init(&pm->tco_regs, &pm->io); + } + pm->irq = sci_irq; qemu_register_reset(pm_reset, pm); pm->powerdown_notifier.notify = pm_powerdown_req; @@ -351,6 +388,18 @@ out: error_propagate(errp, local_err); } +static bool ich9_pm_get_enable_tco(Object *obj, Error **errp) +{ + ICH9LPCState *s = ICH9_LPC_DEVICE(obj); + return s->pm.enable_tco; +} + +static void ich9_pm_set_enable_tco(Object *obj, bool value, Error **errp) +{ + ICH9LPCState *s = ICH9_LPC_DEVICE(obj); + s->pm.enable_tco = value; +} + void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) { static const uint32_t gpe0_len = ICH9_PMIO_GPE0_LEN; @@ -382,6 +431,10 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) ich9_pm_get_s4_val, ich9_pm_set_s4_val, NULL, pm, NULL); + object_property_add_bool(obj, ACPI_PM_PROP_TCO_ENABLED, + ich9_pm_get_enable_tco, + ich9_pm_set_enable_tco, + NULL); } void ich9_pm_device_plug_cb(ICH9LPCPMRegs *pm, DeviceState *dev, Error **errp) diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..b6af1d9 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,279 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara + * + * 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 "sysemu/watchdog.h" +#include "hw/i386/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 + +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, +}; + +static inline void tco_timer_reload(TCOIORegs *tr) +{ + tr->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + ((int64_t)(tr->tco.tmr & TCO_TMR_MASK) * TCO_TICK_NSEC); + timer_mod(tr->tco_timer, tr->expire_time); +} + +static inline void tco_timer_stop(TCOIORegs *tr) +{ + tr->expire_time = -1; +} + +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 = 0; + tr->tco.sts1 |= TCO_TIMEOUT; + if (++tr->timeouts_no == 2) { + tr->tco.sts2 |= TCO_SECOND_TO_STS; + tr->tco.sts2 |= TCO_BOOT_STS; + tr->timeouts_no = 0; + + if (!(gcs & ICH9_LPC_RCBA_GCS_NO_REBOOT)) { + watchdog_perform_action(); + tco_timer_stop(tr); + return; + } + } + + if (pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN) { + ich9_generate_smi(); + } else { + ich9_generate_nmi(); + } + tr->tco.rld = tr->tco.tmr; + tco_timer_reload(tr); +} + +/* NOTE: values of 0 or 1 will be ignored by ICH */ +static inline int can_start_tco_timer(TCOIORegs *tr) +{ + return !(tr->tco.cnt1 & TCO_TMR_HLT) && tr->tco.tmr > 1; +} + +static uint32_t tco_ioport_readw(TCOIORegs *tr, uint32_t addr) +{ + uint16_t rld; + + switch (addr) { + case TCO_RLD: + if (tr->expire_time != -1) { + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int64_t elapsed = (tr->expire_time - now) / TCO_TICK_NSEC; + rld = (uint16_t)elapsed | (tr->tco.rld & ~TCO_RLD_MASK); + } else { + rld = tr->tco.rld; + } + return 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; +} + +static void tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val) +{ + switch (addr) { + case TCO_RLD: + tr->timeouts_no = 0; + if (can_start_tco_timer(tr)) { + tr->tco.rld = tr->tco.tmr; + tco_timer_reload(tr); + } 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; + /* + * once TCO_LOCK bit is set, it can not be cleared by software. a reset + * is required to change this bit from 1 to 0 -- it defaults to 0. + */ + tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK); + if (can_start_tco_timer(tr)) { + tr->tco.rld = tr->tco.tmr; + tco_timer_reload(tr); + } else { + tco_timer_stop(tr); + } + 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; + } +} + +static uint64_t tco_io_readw(void *opaque, hwaddr addr, unsigned width) +{ + TCOIORegs *tr = opaque; + return tco_ioport_readw(tr, addr); +} + +static void tco_io_writew(void *opaque, hwaddr addr, uint64_t val, + unsigned width) +{ + TCOIORegs *tr = opaque; + tco_ioport_writew(tr, addr, val); +} + +static const MemoryRegionOps tco_io_ops = { + .read = tco_io_readw, + .write = tco_io_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 acpi_pm_tco_init(TCOIORegs *tr, MemoryRegion *parent) +{ + *tr = (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_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tco_timer_expired, tr), + .expire_time = -1, + .timeouts_no = 0, + }; + memory_region_init_io(&tr->io, memory_region_owner(parent), + &tco_io_ops, tr, "sm-tco", ICH9_PMIO_TCO_LEN); + memory_region_add_subregion(parent, ICH9_PMIO_TCO_RLD, &tr->io); +} + +const VMStateDescription vmstate_tco_io_sts = { + .name = "tco io device status", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(tco.rld, TCOIORegs), + VMSTATE_UINT8(tco.din, TCOIORegs), + VMSTATE_UINT8(tco.dout, TCOIORegs), + VMSTATE_UINT16(tco.sts1, TCOIORegs), + VMSTATE_UINT16(tco.sts2, TCOIORegs), + VMSTATE_UINT16(tco.cnt1, TCOIORegs), + VMSTATE_UINT16(tco.cnt2, TCOIORegs), + VMSTATE_UINT8(tco.msg1, TCOIORegs), + VMSTATE_UINT8(tco.msg2, TCOIORegs), + VMSTATE_UINT8(tco.wdcnt, TCOIORegs), + VMSTATE_UINT16(tco.tmr, TCOIORegs), + VMSTATE_UINT8(sw_irq_gen, TCOIORegs), + VMSTATE_TIMER_PTR(tco_timer, TCOIORegs), + VMSTATE_INT64(expire_time, TCOIORegs), + VMSTATE_UINT8(timeouts_no, TCOIORegs), + VMSTATE_END_OF_LIST() + } +}; diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 082cd93..7e35a36 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -253,7 +253,7 @@ static void pc_q35_init(MachineState *machine) (pc_machine->vmport != ON_OFF_AUTO_ON), 0xff0104); /* connect pm stuff to lpc */ - ich9_lpc_pm_init(lpc); + ich9_lpc_pm_init(lpc, !mc->no_tco); /* ahci and SATA device, for q35 1 ahci controller is built-in */ ahci = pci_create_simple_multifunction(host_bus, @@ -393,6 +393,7 @@ static void pc_q35_2_4_machine_options(MachineClass *m) m->default_machine_opts = "firmware=bios-256k.bin"; m->default_display = "std"; m->no_floppy = 1; + m->no_tco = 0; m->alias = "q35"; } @@ -404,6 +405,7 @@ static void pc_q35_2_3_machine_options(MachineClass *m) { pc_q35_2_4_machine_options(m); m->no_floppy = 0; + m->no_tco = 1; m->alias = NULL; SET_MACHINE_COMPAT(m, PC_COMPAT_2_3); } diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b3e0b1f..acf262c 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] & @@ -357,11 +367,12 @@ static void ich9_set_sci(void *opaque, int irq_num, int level) } } -void ich9_lpc_pm_init(PCIDevice *lpc_pci) +void ich9_lpc_pm_init(PCIDevice *lpc_pci, bool enable_tco) { ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pci); - ich9_pm_init(lpc_pci, &lpc->pm, qemu_allocate_irq(ich9_set_sci, lpc, 0)); + ich9_pm_init(lpc_pci, &lpc->pm, enable_tco, + qemu_allocate_irq(ich9_set_sci, lpc, 0)); ich9_lpc_reset(&lpc->d.qdev); } diff --git a/include/hw/acpi/ich9.h b/include/hw/acpi/ich9.h index 77cc65c..a7eb421 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_en_wmask; @@ -54,9 +56,12 @@ typedef struct ICH9LPCPMRegs { uint8_t disable_s3; uint8_t disable_s4; uint8_t s4_val; + bool enable_tco; + + TCOIORegs tco_regs; } ICH9LPCPMRegs; -void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, +void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, bool enable_tco, qemu_irq sci_irq); void ich9_pm_iospace_update(ICH9LPCPMRegs *pm, uint32_t pm_io_base); extern const VMStateDescription vmstate_ich9_pm; diff --git a/include/hw/acpi/tco.h b/include/hw/acpi/tco.h new file mode 100644 index 0000000..477a32c --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,98 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara + * + * 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" + +/* As per ICH9 spec, the internal timer has an error of ~0.6s on every tick */ +#define TCO_TICK_NSEC 600000000LL + +/* 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 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 { + bool use_tco; + 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; + + QEMUTimer *tco_timer; + int64_t expire_time; + uint8_t timeouts_no; + + MemoryRegion io; +} TCOIORegs; + +/* tco.c */ +void acpi_pm_tco_init(TCOIORegs *tr, MemoryRegion *parent); + +extern const VMStateDescription vmstate_tco_io_sts; + +#endif /* HW_ACPI_TCO_H */ diff --git a/include/hw/boards.h b/include/hw/boards.h index 6379901..2aec9cb 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -99,7 +99,8 @@ struct MachineClass { no_floppy:1, no_cdrom:1, no_sdcard:1, - has_dynamic_sysbus:1; + has_dynamic_sysbus:1, + no_tco:1; int is_default; const char *default_machine_opts; const char *default_boot_order; diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index a2cc15c..80a5653 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -17,9 +17,12 @@ void ich9_lpc_set_irq(void *opaque, int irq_num, int level); int ich9_lpc_map_irq(PCIDevice *pci_dev, int intx); PCIINTxRoute ich9_route_intx_pin_to_irq(void *opaque, int pirq_pin); -void ich9_lpc_pm_init(PCIDevice *pci_lpc); +void ich9_lpc_pm_init(PCIDevice *pci_lpc, bool enable_tco); 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" @@ -162,6 +165,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 @@ -186,7 +191,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 diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index 86c5651..c1afdc0 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h @@ -89,6 +89,7 @@ typedef struct PcPciInfo { #define ACPI_PM_PROP_PM_IO_BASE "pm_io_base" #define ACPI_PM_PROP_GPE0_BLK "gpe0_blk" #define ACPI_PM_PROP_GPE0_BLK_LEN "gpe0_blk_len" +#define ACPI_PM_PROP_TCO_ENABLED "enable_tco" struct PcGuestInfo { bool isapc_ram_fw; -- 2.1.0