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 pcacjr@zytor.com --- hw/acpi/Makefile.objs | 1 + hw/acpi/ich9.c | 36 ++++++++++ hw/acpi/tco.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++ 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, 386 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..3a44a95 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,188 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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 + +static QEMUTimer *tco_timer; +static unsigned int timeouts_no; + +static inline 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 inline 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) { + 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)) { + watchdog_perform_action(); + tco_timer_stop(); + 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: + timeouts_no = 0; + 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..700532c --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,139 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/i386/acpi-dsdt-pdrc.dsl | 46 ++++++++++++++++++++++++++++++++++++++++++ hw/i386/q35-acpi-dsdt.dsl | 1 + tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7795 bytes 3 files changed, 47 insertions(+) create mode 100644 hw/i386/acpi-dsdt-pdrc.dsl
diff --git a/hw/i386/acpi-dsdt-pdrc.dsl b/hw/i386/acpi-dsdt-pdrc.dsl new file mode 100644 index 0000000..badb410 --- /dev/null +++ b/hw/i386/acpi-dsdt-pdrc.dsl @@ -0,0 +1,46 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, see http://www.gnu.org/licenses/. + */ + +/**************************************************************** + * PCI Device Resource Comsumption + ****************************************************************/ + +Scope(_SB.PCI0) { + Device (PDRC) { + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + Name (PDRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) + }) + + Method (_CRS, 0, Serialized) { + Return(PDRS) + } + } +} + +Scope(_SB) { + OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000) + Field (RCRB, DWordAcc, Lock, Preserve) { + Offset(0x3000), + TCTL, 8, + , 24, + Offset(0x3400), + RTCC, 32, + HPTC, 32, + GCSR, 32, + } +} diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..32b680e 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,7 @@ DefinitionBlock ( } }
+#include "acpi-dsdt-pdrc.dsl" #include "acpi-dsdt-hpet.dsl"
diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..beea54c234954c54c3b008b52e3cd167701253c7 100644 GIT binary patch delta 154 zcmexl{n>`gCD<jTSdM{#annYwc*c5H7QOgjr}zM8PlM<tivX7(XO4IePZu7?3p`95 z@u8kBj2uA0U_n7HzBWz<Mur0y|1mf)FjO*#aK}3b1#>I`$qHD3%!qCXat?B0-~p-O xW^jmZQ~~jY85$TQLYzZ<I2a@t8vZc`g*ZDacm#wvE4Vuc2Qeya&S3140RYvzDYXCq
delta 24 gcmext^U0daCD<k8lPm)R<DrdQ@r;{aF?PxT0CYGA#sB~S
On 27/05/2015 02:29, Paulo Alcantara wrote:
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/i386/acpi-dsdt-pdrc.dsl | 46 ++++++++++++++++++++++++++++++++++++++++++
Why pdrc and not e.g. ccr (chipset configuration registers)? I cannot find PDRC / PCI device resource consumption in the ICH9 spec.
hw/i386/q35-acpi-dsdt.dsl | 1 + tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7795 bytes 3 files changed, 47 insertions(+) create mode 100644 hw/i386/acpi-dsdt-pdrc.dsl
diff --git a/hw/i386/acpi-dsdt-pdrc.dsl b/hw/i386/acpi-dsdt-pdrc.dsl new file mode 100644 index 0000000..badb410 --- /dev/null +++ b/hw/i386/acpi-dsdt-pdrc.dsl @@ -0,0 +1,46 @@ +/*
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with this program; if not, see http://www.gnu.org/licenses/.
- */
+/****************************************************************
- PCI Device Resource Comsumption
"Chipset configuration registers"
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (PDRC) {
Device (CCR)
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (PDRS, ResourceTemplate() {
Just use Name(_CRS, ResourceTemplate() { ... })
Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000)
})
Method (_CRS, 0, Serialized) {
Return(PDRS)
}
- }
+}
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
- Field (RCRB, DWordAcc, Lock, Preserve) {
Offset(0x3000),
- TCTL, 8,
- , 24,
- Offset(0x3400),
- RTCC, 32,
- HPTC, 32,
- GCSR, 32,
- }
Why do you need the RCRB OperationRegion if you never access it?
+} diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..32b680e 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,7 @@ DefinitionBlock ( } }
+#include "acpi-dsdt-pdrc.dsl"
Just include it in this file since it's not shared between i440FX and Q35.
Thanks,
Paolo
#include "acpi-dsdt-hpet.dsl"
diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..beea54c234954c54c3b008b52e3cd167701253c7 100644 GIT binary patch delta 154 zcmexl{n>`gCD<jTSdM{#annYwc*c5H7QOgjr}zM8PlM<tivX7(XO4IePZu7?3p`95 z@u8kBj2uA0U_n7HzBWz<Mur0y|1mf)FjO*#aK}3b1#>I`$qHD3%!qCXat?B0-~p-O xW^jmZQ~~jY85$TQLYzZ<I2a@t8vZc`g*ZDacm#wvE4Vuc2Qeya&S3140RYvzDYXCq
delta 24 gcmext^U0daCD<k8lPm)R<DrdQ@r;{aF?PxT0CYGA#sB~S
On Wed, 27 May 2015 14:03:59 +0200 Paolo Bonzini pbonzini@redhat.com wrote:
On 27/05/2015 02:29, Paulo Alcantara wrote:
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/i386/acpi-dsdt-pdrc.dsl | 46 ++++++++++++++++++++++++++++++++++++++++++
Why pdrc and not e.g. ccr (chipset configuration registers)? I cannot find PDRC / PCI device resource consumption in the ICH9 spec.
I've found the "PDRC" name being used in ACPI DSDT table definitions on the internet for the MS legacy PNP ID "PNP0C02". However, the ICH9 does mention "Chipset Configuration registers" so it's really more meaningful to name it as "CCR" and then matching spec.
hw/i386/q35-acpi-dsdt.dsl | 1 + tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7795 bytes 3 files changed, 47 insertions(+) create mode 100644 hw/i386/acpi-dsdt-pdrc.dsl
diff --git a/hw/i386/acpi-dsdt-pdrc.dsl b/hw/i386/acpi-dsdt-pdrc.dsl new file mode 100644 index 0000000..badb410 --- /dev/null +++ b/hw/i386/acpi-dsdt-pdrc.dsl @@ -0,0 +1,46 @@ +/*
- This program is free software; you can redistribute it and/or
modify
- it under the terms of the GNU General Public License as
published by
- the Free Software Foundation; either version 2 of the License,
or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public
License along
- with this program; if not, see http://www.gnu.org/licenses/.
- */
+/****************************************************************
- PCI Device Resource Comsumption
"Chipset configuration registers"
OK.
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (PDRC) {
Device (CCR)
OK.
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (PDRS, ResourceTemplate() {
Just use Name(_CRS, ResourceTemplate() { ... })
OK.
Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000)
})
Method (_CRS, 0, Serialized) {
Return(PDRS)
}
- }
+}
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
- Field (RCRB, DWordAcc, Lock, Preserve) {
Offset(0x3000),
- TCTL, 8,
- , 24,
- Offset(0x3400),
- RTCC, 32,
- HPTC, 32,
- GCSR, 32,
- }
Why do you need the RCRB OperationRegion if you never access it?
Indeed. It's not being referenced in any AML code so I'll remove it.
+} diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..32b680e 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,7 @@ DefinitionBlock ( } }
+#include "acpi-dsdt-pdrc.dsl"
Just include it in this file since it's not shared between i440FX and Q35.
OK.
I'll send a v2 with all proposed changes.
Thanks,
Paulo
Hi,
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
Where does this address come from? Is this a standard location suggested by intel specs? Or is the firmware free to choose it?
cheers, Gerd
Hi Gerd,
On Thu, 28 May 2015 09:13:35 +0200 Gerd Hoffmann kraxel@redhat.com wrote:
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
Where does this address come from?
This address is reserved in an ACPI DSDT table for Intel Haswell in Coreboot project, Vlv2DeviceRefCodePkg package in EDK II as well as my Haswell laptop on which I can see it through `dmesg` :-)
Is this a standard location suggested by intel specs?
I haven't found any Intel spec or any other document that suggests such address, but from "9.4 Memory Map" section in ICH9 spec, it seems safe to use that MMIO region for the 16KiB of chipset configuration registers.
Or is the firmware free to choose it?
Given that, I would say so.
Thanks,
Paulo
On Sa, 2015-05-30 at 07:57 -0300, Paulo Alcantara wrote:
Hi Gerd,
On Thu, 28 May 2015 09:13:35 +0200 Gerd Hoffmann kraxel@redhat.com wrote:
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
Where does this address come from?
This address is reserved in an ACPI DSDT table for Intel Haswell in Coreboot project, Vlv2DeviceRefCodePkg package in EDK II as well as my Haswell laptop on which I can see it through `dmesg` :-)
So it seems to be kind of standard. Same address on my laptop btw.
Is this a standard location suggested by intel specs?
I haven't found any Intel spec or any other document that suggests such address, but from "9.4 Memory Map" section in ICH9 spec, it seems safe to use that MMIO region for the 16KiB of chipset configuration registers.
Or is the firmware free to choose it?
Given that, I would say so.
There are a few more cases where addresses programmed by the firmware and addresses in the acpi tables must match: acpi registers, pci mmconfig space for example. They are handles this way:
(1) firmware programs the hardware registers as it pleases. (2) when the firmware fetches the acpi tables from qemu (via fw_cfg) qemu will update the tables according to the hardware programming.
So the question is whenever we better do that here too, or whenever it is fine to simply hardcore this to 0xfed1c000 everywhere ...
I tend to think hardcoding is fine in that case. coreboot, edk2 & seabios all place it at the same location, and it is highly unlikely we ever want move this to another place some day for some reason.
cheers, Gerd
On Mon, June 1, 2015 4:16 am, Gerd Hoffmann wrote:
On Sa, 2015-05-30 at 07:57 -0300, Paulo Alcantara wrote:
Hi Gerd,
On Thu, 28 May 2015 09:13:35 +0200 Gerd Hoffmann kraxel@redhat.com wrote:
+Scope(_SB) {
- OperationRegion (RCRB, SystemMemory, 0xfed1c000, 0x4000)
Where does this address come from?
This address is reserved in an ACPI DSDT table for Intel Haswell in Coreboot project, Vlv2DeviceRefCodePkg package in EDK II as well as my Haswell laptop on which I can see it through `dmesg` :-)
So it seems to be kind of standard. Same address on my laptop btw.
Is this a standard location suggested by intel specs?
I haven't found any Intel spec or any other document that suggests such address, but from "9.4 Memory Map" section in ICH9 spec, it seems safe to use that MMIO region for the 16KiB of chipset configuration registers.
Or is the firmware free to choose it?
Given that, I would say so.
There are a few more cases where addresses programmed by the firmware and addresses in the acpi tables must match: acpi registers, pci mmconfig space for example. They are handles this way:
(1) firmware programs the hardware registers as it pleases. (2) when the firmware fetches the acpi tables from qemu (via fw_cfg) qemu will update the tables according to the hardware programming.
So the question is whenever we better do that here too, or whenever it is fine to simply hardcore this to 0xfed1c000 everywhere ...
I tend to think hardcoding is fine in that case. coreboot, edk2 & seabios all place it at the same location, and it is highly unlikely we ever want move this to another place some day for some reason.
Thanks for the explanation. So, handling it the same way as we do for acpi regs and pci mmconfig space seems to be the proper solution, however I think it's OK to hard-code that address for now since it's being used in many places already.
Paulo
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- tests/Makefile | 2 + tests/tco-test.c | 347 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..ed5a685 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,347 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000 + +#define TCO_SECS_TO_TICKS(secs) ((secs) * 10 / 16) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t secs) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, + TCO_SECS_TO_TICKS(secs)); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS); + if (enable) { + val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT; + } else { + val |= ICH9_LPC_RCBA_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, 4); + load_tco(&d); + start_tco(&d); + clock_step(4 * 1000000000LL); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(4 * 1000000000LL); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, 16); + load_tco(&td); + start_tco(&td); + clock_step(16 * 1000000000LL); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, 16); + load_tco(&td); + start_tco(&td); + clock_step(16 * 1000000000LL); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, 16); + load_tco(&td); + start_tco(&td); + clock_step(16 * 1000000000LL); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, 16); + load_tco(&td); + start_tco(&td); + clock_step(16 * 1000000000LL); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + int secs = 8; + unsigned int ticks; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + set_tco_timeout(&d, secs); + load_tco(&d); + start_tco(&d); + + ticks = TCO_SECS_TO_TICKS(secs); + do { + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + ticks); + clock_step(600000000LL); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + return g_test_run(); +}
On 27/05/2015 02:29, Paulo Alcantara wrote:
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 pcacjr@zytor.com
Hi,
the main issue here is lack of migration support. You need to add a new subsection to vmstate_ich9_pm that is migrated if the registers are different from the default values.
Other comments inline.
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..3a44a95 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,188 @@ +/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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
+static QEMUTimer *tco_timer; +static unsigned int timeouts_no;
These must not be globals. Instead, add them to TCOIORegs.
+static inline 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());
No need for del+mod. Also, these are not tco_ticks_per_sec() but rather TCO_TICK_NSEC. It can be a #define instead of a function.
For a little extra challenge, you could the base value when the timer was started inside TCOIORegs. The timer can be made to expire directly after 0.6*TCO_TMR seconds; you can use the base value and the current time to compute the value of TCO_RLD. Something like:
case TCO_RLD: rld = tr->tco.rld; /* base_clock is set to -1 in tco_timer_stop, reloaded in * tco_timer_reload. */ if (base_clock != -1) { elapsed = (qemu_get_clock(QEMU_CLOCK_VIRTUAL) - base_clock) / TCO_TICK_NSEC; rld -= MIN(elapsed, rld); }
This makes it possible for QEMU to sleep most of the time when the guest is idle, instead of waking up every 0.6 seconds. But feel free to work on this afterwards.
+void acpi_pm_tco_init(TCOIORegs *tr) +{
- *tr = TCO_IO_REGS_DEFAULTS_INIT();
Just inline the macro definition here.
+void acpi_pm_tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val) +{
- switch (addr) {
- case TCO_RLD:
timeouts_no = 0;
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);
This means that TCO_LOCK is never set in tr->tco.cnt1, I think? If I'm correct, it should be covered by tests.
if (can_start_tco_timer(tr)) {
tr->tco.rld = tr->tco.tmr;
tco_timer_reload();
} else {
tco_timer_stop();
}
break;
diff --git a/include/hw/acpi/tco.h b/include/hw/acpi/tco.h new file mode 100644 index 0000000..700532c --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,139 @@ +/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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 \
- }
No need for this definition. This and the *_DEFAULT definitions as well should be in the .c file.
+/* 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);
+}
These three inlines should be in the .c file.
Don't be discouraged by the comments. It's good work!
Paolo
+#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
On Wed, 27 May 2015 13:58:57 +0200 Paolo Bonzini pbonzini@redhat.com wrote:
the main issue here is lack of migration support. You need to add a new subsection to vmstate_ich9_pm that is migrated if the registers are different from the default values.
Hrm - OK. I didn't even take that into account. I'll do it.
+static QEMUTimer *tco_timer; +static unsigned int timeouts_no;
These must not be globals. Instead, add them to TCOIORegs.
OK.
+static inline 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());
No need for del+mod. Also, these are not tco_ticks_per_sec() but rather TCO_TICK_NSEC. It can be a #define instead of a function.
OK.
For a little extra challenge, you could the base value when the timer was started inside TCOIORegs. The timer can be made to expire directly after 0.6*TCO_TMR seconds; you can use the base value and the current time to compute the value of TCO_RLD. Something like:
case TCO_RLD: rld = tr->tco.rld; /* base_clock is set to -1 in tco_timer_stop, reloaded in * tco_timer_reload. */ if (base_clock != -1) { elapsed = (qemu_get_clock(QEMU_CLOCK_VIRTUAL) -
base_clock) / TCO_TICK_NSEC; rld -= MIN(elapsed, rld); }
This makes it possible for QEMU to sleep most of the time when the guest is idle, instead of waking up every 0.6 seconds. But feel free to work on this afterwards.
Nice. I liked your approach and that will definitely avoid overheard waking up on every 0.6s. No, I will work on this approach right now so I can sleep well.
+void acpi_pm_tco_init(TCOIORegs *tr) +{
- *tr = TCO_IO_REGS_DEFAULTS_INIT();
Just inline the macro definition here.
OK.
- 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);
This means that TCO_LOCK is never set in tr->tco.cnt1, I think? If I'm correct, it should be covered by tests.
No. The TCO_LOCK bit is set in tr->tco.cnt1, but cannot be unset afterwards -- on reset, of course, it will. Maybe a test that covers setting it and then unsetting it might be interesting to have in tests/tco-test.c.
+#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 \
- }
No need for this definition. This and the *_DEFAULT definitions as well should be in the .c file.
OK.
+/* 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); +}
These three inlines should be in the .c file.
OK.
Don't be discouraged by the comments. It's good work!
Alright. Thank you for the comments! I'll send a v2 soon.
Paulo
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.
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
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 59 ++++++++++++ hw/acpi/tco.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/isa/lpc_ich9.c | 10 ++ include/hw/acpi/ich9.h | 4 + include/hw/acpi/tco.h | 98 +++++++++++++++++++ include/hw/i386/ich9.h | 8 ++ 7 files changed, 434 insertions(+), 1 deletion(-) 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 84e5bb8..10959fa 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); @@ -157,6 +188,24 @@ static const VMStateDescription vmstate_memhp_state = { } };
+static bool vmstate_test_use_tco(void *opaque) +{ + ICH9LPCPMRegs *s = opaque; + return s->tco_regs.use_tco; +} + +static const VMStateDescription vmstate_tco_io_state = { + .name = "ich9_pm/tco", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .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 +228,10 @@ const VMStateDescription vmstate_ich9_pm = { .vmsd = &vmstate_memhp_state, .needed = vmstate_test_use_memhp, }, + { + .vmsd = &vmstate_tco_io_state, + .needed = vmstate_test_use_tco, + }, VMSTATE_END_OF_LIST() } }; @@ -230,6 +283,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; @@ -357,6 +415,7 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) pm->disable_s3 = 0; pm->disable_s4 = 0; pm->s4_val = 2; + pm->tco_regs.use_tco = true;
object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE, &pm->pm_io_base, errp); diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..5dd6e11 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,254 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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.sts1 |= TCO_SECOND_TO_STS; + tr->tco.sts1 |= 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); +} + +void acpi_pm_tco_init(TCOIORegs *tr) +{ + *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, + }; +} + +/* 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; +} + +uint32_t acpi_pm_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; +} + +void acpi_pm_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 = tr->tco.cnt1 & TCO_LOCK ? val | TCO_LOCK : val; + 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; + } +} + +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_BOOL(use_tco, TCOIORegs), + 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/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..7240522 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,98 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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; +} TCOIORegs; + +/* 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); + +extern const VMStateDescription vmstate_tco_io_sts; + +#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
v1 -> v2: * s/PDRC/CCR/ for clarity and match ICH9 spec * remove unnecessary OperationRegion for RCRB
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/i386/q35-acpi-dsdt.dsl | 14 ++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 2 files changed, 14 insertions(+)
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..92675c8 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,20 @@ DefinitionBlock ( } }
+/**************************************************************** + * Chipset Configuration Registers + ****************************************************************/ +Scope(_SB.PCI0) { + Device (CCR) { + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + Name (_CRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) // RCBA + }) + } +} + #include "acpi-dsdt-hpet.dsl"
diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
v1 -> v2: * some cleanup * added test for TCO_LOCK bit
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- tests/Makefile | 2 + tests/tco-test.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..47c15c9 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,419 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS); + if (enable) { + val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT; + } else { + val |= ICH9_LPC_RCBA_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_lock_bit(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +int main(int argc, char **argv) +{ + (void)test_tco_ticks_counter; + g_test_init(&argc, &argv, NULL); + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/lock_bit", test_tco_lock_bit); + return g_test_run(); +}
On 31/05/2015 00:04, Paulo Alcantara wrote:
v1 -> v2:
- some cleanup
- added test for TCO_LOCK bit
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
tests/Makefile | 2 + tests/tco-test.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..47c15c9 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,419 @@ +/*
- QEMU ICH9 TCO emulation tests
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h>
+#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h"
+#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000
+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,
+};
+#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10)
+typedef struct {
- const char *args;
- QPCIDevice *dev;
- void *lpc_base;
- void *tco_io_base;
+} TestData;
+static void test_init(TestData *d) +{
- QPCIBus *bus;
- QTestState *qs;
- char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args);
- qs = qtest_start(s);
- qtest_irq_intercept_in(qs, "ioapic");
- g_free(s);
- bus = qpci_init_pc();
- d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00));
- g_assert(d->dev != NULL);
- /* map PCI-to-LPC bridge interface BAR */
- d->lpc_base = qpci_iomap(d->dev, 0, NULL);
- qpci_device_enable(d->dev);
- g_assert(d->lpc_base != NULL);
- /* set ACPI PM I/O space base address */
- qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE,
PM_IO_BASE_ADDR | 0x1);
- /* enable ACPI I/O */
- qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL,
0x80);
- /* set Root Complex BAR */
- qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA,
RCBA_BASE_ADDR | 0x1);
- d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60);
+}
+static void stop_tco(const TestData *d) +{
- uint32_t val;
- val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT);
- val |= TCO_TMR_HLT;
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val);
+}
+static void start_tco(const TestData *d) +{
- uint32_t val;
- val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT);
- val &= ~TCO_TMR_HLT;
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val);
+}
+static void load_tco(const TestData *d) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4);
+}
+static void set_tco_timeout(const TestData *d, uint16_t ticks) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks);
+}
+static void clear_tco_status(const TestData *d) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008);
- qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002);
- qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004);
+}
+static void reset_on_second_timeout(bool enable) +{
- uint32_t val;
- val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS);
- if (enable) {
val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT;
- } else {
val |= ICH9_LPC_RCBA_GCS_NO_REBOOT;
- }
- writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val);
+}
+static void test_tco_defaults(void) +{
- TestData d;
- d.args = NULL;
- test_init(&d);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==,
TCO_RLD_DEFAULT);
- /* TCO_DAT_IN & TCO_DAT_OUT */
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==,
(TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT);
- /* TCO1_STS & TCO2_STS */
- g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==,
(TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT);
- /* TCO1_CNT & TCO2_CNT */
- g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==,
(TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT);
- /* TCO_MESSAGE1 & TCO_MESSAGE2 */
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==,
(TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT);
- g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==,
TCO_WDCNT_DEFAULT);
- g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==,
SW_IRQ_GEN_DEFAULT);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==,
TCO_TMR_DEFAULT);
- qtest_end();
+}
+static void test_tco_timeout(void) +{
- TestData d;
- const uint16_t ticks = TCO_SECS_TO_TICKS(4);
- uint32_t val;
- int ret;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(ticks * TCO_TICK_NSEC);
- /* test first timeout */
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- /* test clearing timeout bit */
- val |= TCO_TIMEOUT;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 0);
- /* test second timeout */
- clock_step(ticks * TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_SECOND_TO_STS ? 1 : 0;
- g_assert(ret == 1);
- stop_tco(&d);
- qtest_end();
+}
+static void test_tco_max_timeout(void) +{
- TestData d;
- const uint16_t ticks = 0xffff;
- uint32_t val;
- int ret;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD);
- g_assert_cmpint(val & TCO_RLD_MASK, ==, 1);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 0);
- clock_step(TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- stop_tco(&d);
- qtest_end();
+}
+static QDict *get_watchdog_action(void) +{
- QDict *ev = qmp("");
- QDict *data;
- g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG"));
- data = qdict_get_qdict(ev, "data");
- QINCREF(data);
- QDECREF(ev);
- return data;
+}
+static void test_tco_second_timeout_pause(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(32);
- QDict *ad;
- td.args = "-watchdog-action pause";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "pause"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_reset(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(16);
- QDict *ad;
- td.args = "-watchdog-action reset";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "reset"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_shutdown(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(128);
- QDict *ad;
- td.args = "-watchdog-action shutdown";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, ticks);
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_none(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(256);
- QDict *ad;
- td.args = "-watchdog-action none";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, ticks);
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "none"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_ticks_counter(void) +{
- TestData d;
- uint16_t ticks = TCO_SECS_TO_TICKS(8);
- uint16_t rld;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- do {
rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK;
g_assert_cmpint(rld, ==, ticks);
clock_step(TCO_TICK_NSEC);
ticks--;
- } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT));
- stop_tco(&d);
- qtest_end();
+}
+static void test_tco_lock_bit(void) +{
- TestData d;
- uint16_t val;
- d.args = NULL;
- test_init(&d);
- val = TCO_LOCK;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val);
- val &= ~TCO_LOCK;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==,
TCO_LOCK);
- qtest_end();
+}
+int main(int argc, char **argv) +{
- (void)test_tco_ticks_counter;
- g_test_init(&argc, &argv, NULL);
- qtest_add_func("tco/defaults", test_tco_defaults);
- qtest_add_func("tco/timeout/no_action", test_tco_timeout);
- qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout);
- qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause);
- qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset);
- qtest_add_func("tco/second_timeout/shutdown",
test_tco_second_timeout_shutdown);
- qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none);
- qtest_add_func("tco/counter", test_tco_ticks_counter);
- qtest_add_func("tco/lock_bit", test_tco_lock_bit);
- return g_test_run();
+}
Looks good.
If you want, you can add a test for the reserved bits of the various registers.
Paolo
On 31/05/2015 00:04, Paulo Alcantara wrote:
- 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;
Please mask out bits outside TCO_RLD_MASK here, same as you do for TCO1_STS_MASK and friends.
}
break;
[...]
tr->tco.cnt1 = tr->tco.cnt1 & TCO_LOCK ? val | TCO_LOCK : val;
Since you have to respin, you can do:
tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK);
Otherwise looks good to me.
CCing the maintainer.
Paolo
On Mon, June 1, 2015 6:05 am, Paolo Bonzini wrote:
On 31/05/2015 00:04, Paulo Alcantara wrote:
- 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;
Please mask out bits outside TCO_RLD_MASK here, same as you do for TCO1_STS_MASK and friends.
OK.
tr->tco.cnt1 = tr->tco.cnt1 & TCO_LOCK ? val | TCO_LOCK : val;
Since you have to respin, you can do:
tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK);
OK.
Otherwise looks good to me.
CCing the maintainer.
OK. Sorry for not CCing him earlier.
Thanks,
Paulo
On Mon, 1 Jun 2015 10:38:32 -0300 "Paulo Alcantara" pcacjr@zytor.com wrote:
On Mon, June 1, 2015 6:05 am, Paolo Bonzini wrote:
On 31/05/2015 00:04, Paulo Alcantara wrote:
- 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;
Please mask out bits outside TCO_RLD_MASK here, same as you do for TCO1_STS_MASK and friends.
OK.
Hrm - actually, I can't do that. Unlike TCO1_STS, TCO2_STS and TCO1_CNT registers that have some bits which are disabled by writing 1 to them -- that's why I mask them out before updating -- the TCO_RLD and TCO_TMR registers only have bits 15:10 marked as _reserved_ and I should not zero them out. Does that seem correct to you?
Thanks,
Paulo
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.
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
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 59 ++++++++++++ hw/acpi/tco.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/isa/lpc_ich9.c | 10 ++ include/hw/acpi/ich9.h | 4 + include/hw/acpi/tco.h | 98 +++++++++++++++++++ include/hw/i386/ich9.h | 8 ++ 7 files changed, 434 insertions(+), 1 deletion(-) 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 84e5bb8..10959fa 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); @@ -157,6 +188,24 @@ static const VMStateDescription vmstate_memhp_state = { } };
+static bool vmstate_test_use_tco(void *opaque) +{ + ICH9LPCPMRegs *s = opaque; + return s->tco_regs.use_tco; +} + +static const VMStateDescription vmstate_tco_io_state = { + .name = "ich9_pm/tco", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .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 +228,10 @@ const VMStateDescription vmstate_ich9_pm = { .vmsd = &vmstate_memhp_state, .needed = vmstate_test_use_memhp, }, + { + .vmsd = &vmstate_tco_io_state, + .needed = vmstate_test_use_tco, + }, VMSTATE_END_OF_LIST() } }; @@ -230,6 +283,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; @@ -357,6 +415,7 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) pm->disable_s3 = 0; pm->disable_s4 = 0; pm->s4_val = 2; + pm->tco_regs.use_tco = true;
object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE, &pm->pm_io_base, errp); diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..c159281 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,254 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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); +} + +void acpi_pm_tco_init(TCOIORegs *tr) +{ + *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, + }; +} + +/* 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; +} + +uint32_t acpi_pm_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; +} + +void acpi_pm_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; + } +} + +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_BOOL(use_tco, TCOIORegs), + 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/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..7240522 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,98 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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; +} TCOIORegs; + +/* 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); + +extern const VMStateDescription vmstate_tco_io_sts; + +#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
v1 -> v2: * s/PDRC/CCR/ for clarity and match ICH9 spec * remove unnecessary OperationRegion for RCRB
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/i386/q35-acpi-dsdt.dsl | 14 ++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 2 files changed, 14 insertions(+)
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..92675c8 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,20 @@ DefinitionBlock ( } }
+/**************************************************************** + * Chipset Configuration Registers + ****************************************************************/ +Scope(_SB.PCI0) { + Device (CCR) { + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + Name (_CRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) // RCBA + }) + } +} + #include "acpi-dsdt-hpet.dsl"
diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Mon, Jun 01, 2015 at 08:48:40PM -0300, Paulo Alcantara wrote:
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
changelog should come after ---. An empty commit log is too terse I think. Could you quote spec in the changelog?
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/i386/q35-acpi-dsdt.dsl | 14 ++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 2 files changed, 14 insertions(+)
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..92675c8 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,20 @@ DefinitionBlock ( } }
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) // RCBA
Indent a bit more please.
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
-- 2.1.0
On Wed, 17 Jun 2015 15:33:07 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Mon, Jun 01, 2015 at 08:48:40PM -0300, Paulo Alcantara wrote:
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
changelog should come after ---.
Ok.
An empty commit log is too terse I think. Could you quote spec in the changelog?
Yes, sure.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/i386/q35-acpi-dsdt.dsl | 14 ++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 2 files changed, 14 insertions(+)
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..92675c8 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,20 @@ DefinitionBlock ( } }
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, 0xfed1c000, 0x00004000) //
RCBA
Indent a bit more please.
Ok.
Thanks,
Paulo
v1 -> v2: * some cleanup * add test for TCO_LOCK bit v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- tests/Makefile | 2 + tests/tco-test.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..b9acf43 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,475 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS); + if (enable) { + val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT; + } else { + val |= ICH9_LPC_RCBA_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
On Mon, Jun 01, 2015 at 08:48:41PM -0300, Paulo Alcantara wrote:
v1 -> v2:
- some cleanup
- add test for TCO_LOCK bit
v2 -> v3:
- add tests for TCO control & status bits
- fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
Changelog after -- please. Pls add a bit of description here.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
tests/Makefile | 2 + tests/tco-test.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..b9acf43 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,475 @@ +/*
- QEMU ICH9 TCO emulation tests
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h>
+#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h"
+#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000
This is duplicated in DSL. Add a macro?
+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,
+};
+#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10)
+typedef struct {
- const char *args;
- QPCIDevice *dev;
- void *lpc_base;
- void *tco_io_base;
+} TestData;
+static void test_init(TestData *d) +{
- QPCIBus *bus;
- QTestState *qs;
- char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args);
- qs = qtest_start(s);
- qtest_irq_intercept_in(qs, "ioapic");
- g_free(s);
- bus = qpci_init_pc();
- d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00));
- g_assert(d->dev != NULL);
- /* map PCI-to-LPC bridge interface BAR */
- d->lpc_base = qpci_iomap(d->dev, 0, NULL);
- qpci_device_enable(d->dev);
- g_assert(d->lpc_base != NULL);
- /* set ACPI PM I/O space base address */
- qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE,
PM_IO_BASE_ADDR | 0x1);
- /* enable ACPI I/O */
- qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL,
0x80);
- /* set Root Complex BAR */
- qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA,
RCBA_BASE_ADDR | 0x1);
- d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60);
+}
+static void stop_tco(const TestData *d) +{
- uint32_t val;
- val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT);
- val |= TCO_TMR_HLT;
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val);
+}
+static void start_tco(const TestData *d) +{
- uint32_t val;
- val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT);
- val &= ~TCO_TMR_HLT;
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val);
+}
+static void load_tco(const TestData *d) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4);
+}
+static void set_tco_timeout(const TestData *d, uint16_t ticks) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks);
+}
+static void clear_tco_status(const TestData *d) +{
- qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008);
- qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002);
- qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004);
+}
+static void reset_on_second_timeout(bool enable) +{
- uint32_t val;
- val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS);
- if (enable) {
val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT;
- } else {
val |= ICH9_LPC_RCBA_GCS_NO_REBOOT;
- }
- writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val);
+}
+static void test_tco_defaults(void) +{
- TestData d;
- d.args = NULL;
- test_init(&d);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==,
TCO_RLD_DEFAULT);
- /* TCO_DAT_IN & TCO_DAT_OUT */
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==,
(TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT);
- /* TCO1_STS & TCO2_STS */
- g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==,
(TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT);
- /* TCO1_CNT & TCO2_CNT */
- g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==,
(TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT);
- /* TCO_MESSAGE1 & TCO_MESSAGE2 */
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==,
(TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT);
- g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==,
TCO_WDCNT_DEFAULT);
- g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==,
SW_IRQ_GEN_DEFAULT);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==,
TCO_TMR_DEFAULT);
- qtest_end();
+}
+static void test_tco_timeout(void) +{
- TestData d;
- const uint16_t ticks = TCO_SECS_TO_TICKS(4);
- uint32_t val;
- int ret;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(ticks * TCO_TICK_NSEC);
- /* test first timeout */
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- /* test clearing timeout bit */
- val |= TCO_TIMEOUT;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 0);
- /* test second timeout */
- clock_step(ticks * TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS);
- ret = val & TCO_SECOND_TO_STS ? 1 : 0;
- g_assert(ret == 1);
- stop_tco(&d);
- qtest_end();
+}
+static void test_tco_max_timeout(void) +{
- TestData d;
- const uint16_t ticks = 0xffff;
- uint32_t val;
- int ret;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD);
- g_assert_cmpint(val & TCO_RLD_MASK, ==, 1);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 0);
- clock_step(TCO_TICK_NSEC);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & TCO_TIMEOUT ? 1 : 0;
- g_assert(ret == 1);
- stop_tco(&d);
- qtest_end();
+}
+static QDict *get_watchdog_action(void) +{
- QDict *ev = qmp("");
- QDict *data;
- g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG"));
- data = qdict_get_qdict(ev, "data");
- QINCREF(data);
- QDECREF(ev);
- return data;
+}
+static void test_tco_second_timeout_pause(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(32);
- QDict *ad;
- td.args = "-watchdog-action pause";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "pause"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_reset(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(16);
- QDict *ad;
- td.args = "-watchdog-action reset";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, TCO_SECS_TO_TICKS(16));
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "reset"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_shutdown(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(128);
- QDict *ad;
- td.args = "-watchdog-action shutdown";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, ticks);
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_second_timeout_none(void) +{
- TestData td;
- const uint16_t ticks = TCO_SECS_TO_TICKS(256);
- QDict *ad;
- td.args = "-watchdog-action none";
- test_init(&td);
- stop_tco(&td);
- clear_tco_status(&td);
- reset_on_second_timeout(true);
- set_tco_timeout(&td, ticks);
- load_tco(&td);
- start_tco(&td);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- ad = get_watchdog_action();
- g_assert(!strcmp(qdict_get_str(ad, "action"), "none"));
- QDECREF(ad);
- stop_tco(&td);
- qtest_end();
+}
+static void test_tco_ticks_counter(void) +{
- TestData d;
- uint16_t ticks = TCO_SECS_TO_TICKS(8);
- uint16_t rld;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- do {
rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK;
g_assert_cmpint(rld, ==, ticks);
clock_step(TCO_TICK_NSEC);
ticks--;
- } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT));
- stop_tco(&d);
- qtest_end();
+}
+static void test_tco1_control_bits(void) +{
- TestData d;
- uint16_t val;
- d.args = NULL;
- test_init(&d);
- val = TCO_LOCK;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val);
- val &= ~TCO_LOCK;
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==,
TCO_LOCK);
- qtest_end();
+}
+static void test_tco1_status_bits(void) +{
- TestData d;
- uint16_t ticks = 8;
- uint16_t val;
- int ret;
- d.args = NULL;
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(false);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(ticks * TCO_TICK_NSEC);
- qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0);
- qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS);
- ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0;
- g_assert(ret == 1);
- qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0);
- qtest_end();
+}
+static void test_tco2_status_bits(void) +{
- TestData d;
- uint16_t ticks = 8;
- uint16_t val;
- int ret;
- d.args = "-watchdog-action none";
- test_init(&d);
- stop_tco(&d);
- clear_tco_status(&d);
- reset_on_second_timeout(true);
- set_tco_timeout(&d, ticks);
- load_tco(&d);
- start_tco(&d);
- clock_step(ticks * TCO_TICK_NSEC * 2);
- val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS);
- ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0;
- g_assert(ret == 1);
- qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val);
- g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0);
- qtest_end();
+}
+int main(int argc, char **argv) +{
- g_test_init(&argc, &argv, NULL);
- qtest_add_func("tco/defaults", test_tco_defaults);
- qtest_add_func("tco/timeout/no_action", test_tco_timeout);
- qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout);
- qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause);
- qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset);
- qtest_add_func("tco/second_timeout/shutdown",
test_tco_second_timeout_shutdown);
- qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none);
- qtest_add_func("tco/counter", test_tco_ticks_counter);
- qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits);
- qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits);
- qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits);
- return g_test_run();
+}
2.1.0
On Wed, 17 Jun 2015 15:37:49 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Mon, Jun 01, 2015 at 08:48:41PM -0300, Paulo Alcantara wrote:
v1 -> v2:
- some cleanup
- add test for TCO_LOCK bit
v2 -> v3:
- add tests for TCO control & status bits
- fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
Changelog after -- please. Pls add a bit of description here.
Ok.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
tests/Makefile | 2 + tests/tco-test.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 729b969..43950d0 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -363,6 +364,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..b9acf43 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,475 @@ +/*
- QEMU ICH9 TCO emulation tests
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h>
+#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h"
+#define PM_IO_BASE_ADDR 0xb000 +#define RCBA_BASE_ADDR 0xfed1c000
This is duplicated in DSL. Add a macro?
Ok. I will add a macro for RCBA address.
Thanks,
Paulo
On Mon, June 1, 2015 8:48 pm, Paulo Alcantara wrote:
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.
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
Michael, ping? :-)
(looks like there's a lot of ICH9 changes since this patchset was sent, so you want me to rebase it against master and resend, please let me know)
Thanks,
Paulo
On Mon, Jun 01, 2015 at 08:48:39PM -0300, Paulo Alcantara wrote:
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.
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
changelog must come after --- so that git am ignores it.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
for some reason v3 was sent as reply to v2. Don't do that please.
hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 59 ++++++++++++ hw/acpi/tco.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/isa/lpc_ich9.c | 10 ++ include/hw/acpi/ich9.h | 4 + include/hw/acpi/tco.h | 98 +++++++++++++++++++ include/hw/i386/ich9.h | 8 ++ 7 files changed, 434 insertions(+), 1 deletion(-) 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 84e5bb8..10959fa 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); @@ -157,6 +188,24 @@ static const VMStateDescription vmstate_memhp_state = { } };
+static bool vmstate_test_use_tco(void *opaque) +{
- ICH9LPCPMRegs *s = opaque;
- return s->tco_regs.use_tco;
+}
+static const VMStateDescription vmstate_tco_io_state = {
- .name = "ich9_pm/tco",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
- .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 +228,10 @@ const VMStateDescription vmstate_ich9_pm = { .vmsd = &vmstate_memhp_state, .needed = vmstate_test_use_memhp, },
{
.vmsd = &vmstate_tco_io_state,
.needed = vmstate_test_use_tco,
}}, VMSTATE_END_OF_LIST()
}; @@ -230,6 +283,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;
@@ -357,6 +415,7 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) pm->disable_s3 = 0; pm->disable_s4 = 0; pm->s4_val = 2;
- pm->tco_regs.use_tco = true;
Would be safer to add a property, and not to enable this for old machine types.
object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE, &pm->pm_io_base, errp);
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..c159281 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,254 @@ +/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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;
So this is a virtual clock - not running when VM is not running. Doesn't this mean if you stop VM for a while, it's almost sure to fire when you resume?
Maybe it's a useful feature, but maybe it's best to keep it off by default?
}
- }
- 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);
+}
+void acpi_pm_tco_init(TCOIORegs *tr) +{
- *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,
- };
+}
+/* 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;
+}
+uint32_t acpi_pm_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;
+}
+void acpi_pm_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;
- }
+}
+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_BOOL(use_tco, TCOIORegs),
So this field is only migrated if it is true. Doesn't seem to make sense.
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/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..7240522 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,98 @@ +/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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;
+} TCOIORegs;
+/* 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);
+extern const VMStateDescription vmstate_tco_io_sts;
+#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
On Wed, 17 Jun 2015 15:27:53 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Mon, Jun 01, 2015 at 08:48:39PM -0300, Paulo Alcantara wrote:
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.
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
changelog must come after --- so that git am ignores it.
Ok.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
for some reason v3 was sent as reply to v2. Don't do that please.
Indeed. Sorry about that.
hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 59 ++++++++++++ hw/acpi/tco.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/isa/lpc_ich9.c | 10 ++ include/hw/acpi/ich9.h | 4 + include/hw/acpi/tco.h | 98 +++++++++++++++++++ include/hw/i386/ich9.h | 8 ++ 7 files changed, 434 insertions(+), 1 deletion(-) 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 84e5bb8..10959fa 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); @@ -157,6 +188,24 @@ static const VMStateDescription vmstate_memhp_state = { } };
+static bool vmstate_test_use_tco(void *opaque) +{
- ICH9LPCPMRegs *s = opaque;
- return s->tco_regs.use_tco;
+}
+static const VMStateDescription vmstate_tco_io_state = {
- .name = "ich9_pm/tco",
- .version_id = 1,
- .minimum_version_id = 1,
- .minimum_version_id_old = 1,
- .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 +228,10 @@ const VMStateDescription vmstate_ich9_pm = { .vmsd = &vmstate_memhp_state, .needed = vmstate_test_use_memhp, },
{
.vmsd = &vmstate_tco_io_state,
.needed = vmstate_test_use_tco,
}}, VMSTATE_END_OF_LIST()
}; @@ -230,6 +283,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; @@ -357,6 +415,7 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm, Error **errp) pm->disable_s3 = 0; pm->disable_s4 = 0; pm->s4_val = 2;
- pm->tco_regs.use_tco = true;
Would be safer to add a property, and not to enable this for old machine types.
This use_tco field is *really* confusing. My initial idea of using this field was to determine whether migrate TCO registers or not, but now that doesn't make sense at all. On Q35 machine type all TCO registers should be migrated and initialised.
Since this is ICH9-specific code, doesn't this mean other machine types (e.g. i440FX) wouldn't be supported? would we need to add a property to it yet?
object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE, &pm->pm_io_base, errp);
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c new file mode 100644 index 0000000..c159281 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,254 @@ +/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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;
So this is a virtual clock - not running when VM is not running. Doesn't this mean if you stop VM for a while, it's almost sure to fire when you resume?
Yes, it will.
Maybe it's a useful feature, but maybe it's best to keep it off by default?
The timer is already halted by default. So you might ask me why it's halted if TCO_TMR_HLT bit is 0 by default (e.g. timer running)? It will only start counting down if someone reloads it through TCO_RLD register.
I would just keep it running when resuming VM because ICH9 spec says that it will _only_ count down in the S0 state. Although, I think it's worth mentioning that the firmware should be able to stop it (or restore its default values) while in the S3 resume path.
}
- }
- 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);
+}
+void acpi_pm_tco_init(TCOIORegs *tr) +{
- *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,
- };
+}
+/* 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;
+}
+uint32_t acpi_pm_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;
+}
+void acpi_pm_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;
- }
+}
+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_BOOL(use_tco, TCOIORegs),
So this field is only migrated if it is true. Doesn't seem to make sense.
Yeah, indeed. I should remove this field and always migrate TCO registers on Q35 machine type.
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 pcacjr@zytor.com --- 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 pcacjr@zytor.com + * + * 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 pcacjr@zytor.com + * + * 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;
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * s/PDRC/CCR/ for clarity and match ICH9 spec * remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4: * quote RCRB description from ICH9 spec to commit log * fix indentation issue in _CRS() method declaration * create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL --- hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h" + +/**************************************************************** + * Chipset Configuration Registers + ****************************************************************/ +Scope(_SB.PCI0) { + Device (CCR) { + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + Name (_CRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE) + }) + } +} + #include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/* + * QEMU ICH9 Chipset Configuration Registers + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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_ICH9_CC_H +#define HW_ICH9_CC_H + +#define RCBA_BASE_ADDR 0xfed1c000 +#define RCRB_SIZE 0x00004000 + +#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
Name (_UID, 1)
s/1/"TCO watchdog resources"/
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, Jun 23, 2015 at 12:38:59PM +0200, Igor Mammedov wrote:
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
Name (_UID, 1)
s/1/"TCO watchdog resources"/
I think we switched all UID values to integers, you can look it up in git history. Let's keep this consistent?
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, 23 Jun 2015 12:58:13 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Tue, Jun 23, 2015 at 12:38:59PM +0200, Igor Mammedov wrote:
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register
values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
****************************************************************/ +Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
Name (_UID, 1)
s/1/"TCO watchdog resources"/
I think we switched all UID values to integers, you can look it up in git history. Let's keep this consistent?
in ssdt for container devices we use strings which says what container is for. so using description string is consistent with what we are doing now.
With numbers it's easy to create UID collision, which makes some Windows versions BSOD.
as side effect: It makes container device self describing if one looks in device-manager.
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, Jun 23, 2015 at 02:29:37PM +0200, Igor Mammedov wrote:
On Tue, 23 Jun 2015 12:58:13 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Tue, Jun 23, 2015 at 12:38:59PM +0200, Igor Mammedov wrote:
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register
values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
****************************************************************/ +Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
Name (_UID, 1)
s/1/"TCO watchdog resources"/
I think we switched all UID values to integers, you can look it up in git history. Let's keep this consistent?
in ssdt for container devices we use strings which says what container is for. so using description string is consistent with what we are doing now.
With numbers it's easy to create UID collision, which makes some Windows versions BSOD.
as side effect: It makes container device self describing if one looks in device-manager.
But see commit c96d9286a6d70452e5fa4f1e3f840715e325be95 i386/acpi-build: more traditional _UID and _HID for PXB root buses
ARM also seems to have #s as UIDs for CPUs.
I don't think I'll block this patch on this issue, we can tweak the UID later easily.
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
----- Original Message -----
From: "Igor Mammedov" imammedo@redhat.com To: "Paulo Alcantara" pcacjr@gmail.com Cc: qemu-devel@nongnu.org, pbonzini@redhat.com, seabios@seabios.org, mst@redhat.com Sent: Tuesday, June 23, 2015 12:38:59 PM Subject: Re: [SeaBIOS] [PATCH v4 2/3] target-i386: reserve RCRB mmio space in ACPI DSDT table
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Guys, can you *please* read the reviews to v1/2/3 and even to v4 itself?!?!?
Paolo
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
Name (_UID, 1)
s/1/"TCO watchdog resources"/
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
----- Original Message -----
From: "Igor Mammedov" imammedo@redhat.com To: "Paulo Alcantara" pcacjr@gmail.com Cc: qemu-devel@nongnu.org, pbonzini@redhat.com, seabios@seabios.org, mst@redhat.com Sent: Tuesday, June 23, 2015 12:38:59 PM Subject: Re: [SeaBIOS] [PATCH v4 2/3] target-i386: reserve RCRB mmio space in ACPI DSDT table
On Sun, 21 Jun 2015 21:37:02 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 31 +++++++++++++++++++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 47 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..512c220 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
I'd name it something like: TCOW
Name (_HID, EISAID("PNP0C02"))
PNP0C02 not in ACPI spec, pls use PNP0A06 like we do in other places for reserving IO space.
The ACPI spec says "Note: Plug and Play IDs that are not defined by the ACPI specification are defined and described in the following document [URL]" where PNP0C02 is the "General ID for reserving resources required by Plug and Play system board registers (not specific to a particular device)".
PNP0A06 is the "ACPI bus", whatever that means.
Paolo
Name (_UID, 1)
s/1/"TCO watchdog resources"/
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Also like Michael suggested create this device dynamically in build_ssdt(), for example look at "if (misc->applesmc_io_base) {" block in hw/i386/acpi-build.c and add this device only if it's present (i.e. for version from which it's supported)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..675fb7f --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,31 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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_ICH9_CC_H +#define HW_ICH9_CC_H
+#define RCBA_BASE_ADDR 0xfed1c000
where does this value come from?
+#define RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, Jun 23, 2015 at 12:38:59PM +0200, Igor Mammedov wrote:
Name (_UID, 1)
s/1/"TCO watchdog resources"/
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, RCBA_BASE_ADDR, RCRB_SIZE)
since this values are dynamically programmed by BIOS it's incorrect to put them here statically. Take RCBA_BASE_ADDR from respective ICH9 register, which should be programmed by BIOS before ACPI tables are read by it.
Hmm this is true, isn't it?
static void ich9_lpc_rcba_update(ICH9LPCState *lpc, uint32_t rbca_old) { uint32_t rbca = pci_get_long(lpc->d.config + ICH9_LPC_RCBA);
if (rbca_old & ICH9_LPC_RCBA_EN) { memory_region_del_subregion(get_system_memory(), &lpc->rbca_mem); } if (rbca & ICH9_LPC_RCBA_EN) { memory_region_add_subregion_overlap(get_system_memory(), rbca & ICH9_LPC_RCBA_BA_MASK, &lpc->rbca_mem, 1); } }
So the RBCA base must come from device config, and in particular, this means it must be in the SSDT since the DSDT is static.
We already have aml_memory32_fixed, e.g.
if (misc->tpm_version != TPM_VERSION_UNSPEC) { dev = aml_device("ISA.TPM"); aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C31"))); aml_append(dev, aml_name_decl("_STA", aml_int(0xF))); crs = aml_resource_template(); aml_append(crs, aml_memory32_fixed(TPM_TIS_ADDR_BASE, TPM_TIS_ADDR_SIZE, AML_READ_WRITE)); aml_append(crs, aml_irq_no_flags(TPM_TIS_IRQ)); aml_append(dev, aml_name_decl("_CRS", crs)); aml_append(scope, dev); }
so it won't be hard to do.
This patch adds a testcase that covers the following: 1) TCO default values 2) first and second TCO timeout 3) watch and validate ticks counter through TCO_RLD register 4) maximum supported TCO timeout (0x3ff) 5) watchdog actions (pause/reset/shutdown/none) upon second TCO timeout 6) set and get of TCO control and status bits
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * some cleanup * add test for TCO_LOCK bit
v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
v3 -> v4: * add more description to commit log * use RCBA_BASE_ADDR macro defintion from hw/i386/ich9-cc.h instead --- tests/Makefile | 2 + tests/tco-test.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 477 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index 4de40de..2b26ae7 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -150,6 +150,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -367,6 +368,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..e48dfe2 --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,475 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * 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 <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/i386/ich9-cc.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define PM_IO_BASE_ADDR 0xb000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS); + if (enable) { + val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT; + } else { + val |= ICH9_LPC_RCBA_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
On Sun, Jun 21, 2015 at 09:37:01PM -0300, Paulo Alcantara wrote:
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 pcacjr@zytor.com
- 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.
Please make new original code GPLv2+. If you have copied from another file, then you should follow that file's licensing, but in that case you should also acknowledge the original copyright.
- */
On Mon, June 22, 2015 5:39 am, Michael S. Tsirkin wrote:
On Sun, Jun 21, 2015 at 09:37:01PM -0300, Paulo Alcantara wrote:
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 pcacjr@zytor.com
- 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.
Please make new original code GPLv2+. If you have copied from another file, then you should follow that file's licensing, but in that case you should also acknowledge the original copyright.
OK.
Thanks,
Paulo
On 22/06/2015 14:30, Paulo Alcantara wrote:
+/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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.
Please make new original code GPLv2+. If you have copied from another file, then you should follow that file's licensing, but in that case you should also acknowledge the original copyright.
OK.
Why? The only "forbidden" license for new code is GPLv2.
If you want to make things more permissive, that's accepted.
Paolo
On Mon, Jun 22, 2015 at 02:32:17PM +0200, Paolo Bonzini wrote:
On 22/06/2015 14:30, Paulo Alcantara wrote:
+/*
- QEMU ICH9 TCO emulation
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- 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.
Please make new original code GPLv2+. If you have copied from another file, then you should follow that file's licensing, but in that case you should also acknowledge the original copyright.
OK.
Why? The only "forbidden" license for new code is GPLv2.
If you want to make things more permissive, that's accepted.
Paolo
Because it's a pain if I need to move code between files with different licenses. MIT is GPL compatible but mixing licenses at random is still not a good idea.
On 22/06/2015 14:47, Michael S. Tsirkin wrote:
Because it's a pain if I need to move code between files with different licenses. MIT is GPL compatible but mixing licenses at random is still not a good idea.
This is a non-problem. How often does it happen that code is moved between files (as opposed to extracted to a new file)?
Besides, the rule you want is not enforceable because Fabrice Bellard explicitly wanted TCG-related files to be BSD.
Paolo
On Mon, Jun 22, 2015 at 03:04:13PM +0200, Paolo Bonzini wrote:
On 22/06/2015 14:47, Michael S. Tsirkin wrote:
Because it's a pain if I need to move code between files with different licenses. MIT is GPL compatible but mixing licenses at random is still not a good idea.
This is a non-problem. How often does it happen that code is moved between files (as opposed to extracted to a new file)?
Besides, the rule you want is not enforceable because Fabrice Bellard explicitly wanted TCG-related files to be BSD.
Paolo
It's not a big deal, but it's preferable if possible. Paulo do you have a problem switching to GPLv2+?
On Mon, June 22, 2015 10:07 am, Michael S. Tsirkin wrote:
On Mon, Jun 22, 2015 at 03:04:13PM +0200, Paolo Bonzini wrote:
On 22/06/2015 14:47, Michael S. Tsirkin wrote:
Because it's a pain if I need to move code between files with
different
licenses. MIT is GPL compatible but mixing licenses at random is
still
not a good idea.
This is a non-problem. How often does it happen that code is moved between files (as opposed to extracted to a new file)?
Besides, the rule you want is not enforceable because Fabrice Bellard explicitly wanted TCG-related files to be BSD.
Paolo
It's not a big deal, but it's preferable if possible. Paulo do you have a problem switching to GPLv2+?
Hi Michael,
No, I really don't. If it's really preferable and brings advantage to the project, then I see no problem at all.
Thanks,
Paulo
"Michael S. Tsirkin" mst@redhat.com writes:
On Mon, Jun 22, 2015 at 02:32:17PM +0200, Paolo Bonzini wrote:
On 22/06/2015 14:30, Paulo Alcantara wrote:
> +/* > + * QEMU ICH9 TCO emulation > + * > + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com > + * > + * 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.
Please make new original code GPLv2+. If you have copied from another file, then you should follow that file's licensing, but in that case you should also acknowledge the original copyright.
OK.
Why? The only "forbidden" license for new code is GPLv2.
If you want to make things more permissive, that's accepted.
Paolo
Because it's a pain if I need to move code between files with different licenses. MIT is GPL compatible but mixing licenses at random is still not a good idea.
Seconded. New code should be GPLv2+ unless you have a really good reason for something else.
Keeping the original license in a derivative work is a really good reason (assuming it's compatible to GPLv2; if it's not, we can't use the derivative work anyway).
LGPLv2+ license for code meant to be linked into differently licensed other projects may be a good reason.
Other reasons exist.
Whatever your reason is, you need to explain it.
On Sun, June 21, 2015 9:37 pm, Paulo Alcantara wrote:
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 pcacjr@zytor.com
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 pcacjr@zytor.com
- 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
*/
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,
ich9_lpc_reset(&lpc->d.qdev);qemu_allocate_irq(ich9_set_sci, lpc, 0));
}
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 pcacjr@zytor.com
- 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;
This field is no longer used and should be removed. When I get home I'll send a v5 with this fix and the license stuff. Thank you all for reviewing it. That's really appreciable.
Thanks,
Paulo
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 pcacjr@zytor.com --- 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
v4 -> v5: * remove unused field (use_tco) in TCOIORegs structure * move license to GPLv2+ --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 55 ++++++++++- hw/acpi/tco.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/i386/pc_q35.c | 4 +- hw/isa/lpc_ich9.c | 15 ++- include/hw/acpi/ich9.h | 7 +- include/hw/acpi/tco.h | 82 +++++++++++++++ include/hw/boards.h | 3 +- include/hw/i386/ich9.h | 10 +- include/hw/i386/pc.h | 1 + 10 files changed, 435 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..b01aeda --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,264 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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..c63afc8 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,82 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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 { + 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;
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * s/PDRC/CCR/ for clarity and match ICH9 spec * remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4: * quote RCRB description from ICH9 spec to commit log * fix indentation issue in _CRS() method declaration * create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
v4 -> v5: * prefix macros in ich9-cc.h with "ICH9_" for better readability and make use of them in CCR device definition --- hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h" + +/**************************************************************** + * Chipset Configuration Registers + ****************************************************************/ +Scope(_SB.PCI0) { + Device (CCR) { + Name (_HID, EISAID("PNP0C02")) + Name (_UID, 1) + + Name (_CRS, ResourceTemplate() { + Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR, ICH9_RCRB_SIZE) + }) + } +} + #include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/* + * QEMU ICH9 Chipset Configuration Registers + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H + +#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000 + +#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Do we really need to describe this block in ASL? PCI0 bus _CRS doesn't include 0xfed1c000 address and stops at 0xfec00000 (w32.end - end of 32-bit PCI window), so adding a device that consumes not provided by parent bus resources probably is not correct.
Taking in account that address is beyond of dynamically used PCI memory resources, I'd just drop patch.
If the goal of it to make sure that it provides resource conflict check then Windows wasn't doing a good job with PNP0C02 used, that's why we ended up with PNP0A06 (between PNP0C02, PNP0A06, ACPI0004) for this task in cases of absence of a defined device HID. And it should be moved out of PCI0 bus scope anyway.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR,
ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2
or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, Jun 23, 2015 at 04:29:40PM +0200, Igor Mammedov wrote:
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Do we really need to describe this block in ASL? PCI0 bus _CRS doesn't include 0xfed1c000 address and stops at 0xfec00000 (w32.end - end of 32-bit PCI window), so adding a device that consumes not provided by parent bus resources probably is not correct.
Taking in account that address is beyond of dynamically used PCI memory resources, I'd just drop patch.
If the goal of it to make sure that it provides resource conflict check then Windows wasn't doing a good job with PNP0C02 used, that's why we ended up with PNP0A06 (between PNP0C02, PNP0A06, ACPI0004) for this task in cases of absence of a defined device HID. And it should be moved out of PCI0 bus scope anyway.
Paulo, does some guest need this patch (2/3)?
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR,
ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2
or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
Also I've stumbled upon http://download.intel.com/design/chipsets/applnots/29227301.pdf don't we need to implement WDDT ACPI table as well?
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR,
ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2
or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
On Tue, Jun 23, 2015 at 04:39:31PM +0200, Igor Mammedov wrote:
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
Also I've stumbled upon http://download.intel.com/design/chipsets/applnots/29227301.pdf don't we need to implement WDDT ACPI table as well?
Maybe but linux does not seem to care.
On Tue, 23 Jun 2015 17:06:46 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Tue, Jun 23, 2015 at 04:39:31PM +0200, Igor Mammedov wrote:
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
Also I've stumbled upon http://download.intel.com/design/chipsets/applnots/29227301.pdf don't we need to implement WDDT ACPI table as well?
Maybe but linux does not seem to care.
What about Windows?
On Tue, Jun 23, 2015 at 05:12:07PM +0200, Igor Mammedov wrote:
On Tue, 23 Jun 2015 17:06:46 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Tue, Jun 23, 2015 at 04:39:31PM +0200, Igor Mammedov wrote:
On Mon, 22 Jun 2015 20:10:28 -0300 Paulo Alcantara pcacjr@gmail.com wrote:
Also I've stumbled upon http://download.intel.com/design/chipsets/applnots/29227301.pdf don't we need to implement WDDT ACPI table as well?
Maybe but linux does not seem to care.
What about Windows?
Needs testing - I tried existing guests and they do not touch TCO but maybe a new install will. Possibly windows does not touch TCO since there's no WDDT ACPI table, but it's not a regression so does not have to block this patchset.
On Mon, Jun 22, 2015 at 08:10:28PM -0300, Paulo Alcantara wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
Can you confirm things just work for you if you drop this patch from the series?
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR, ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2 or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
-- 2.1.0
On Wed, 24 Jun 2015 17:11:26 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Mon, Jun 22, 2015 at 08:10:28PM -0300, Paulo Alcantara wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
Can you confirm things just work for you if you drop this patch from the series?
Hi Michael,
Yes, everything works. I still see no point of adding it to SSDT because OVMF is already setting RCBA to that fixed address and so SeaBIOS once my patch is applied. Therefore, there is no no such thing like "dynamically programmed by BIOS" either. Although, my host and probably yours define the same address in DSDT.
Given that UEFI and a BIOS implementations are using the same fixed address, this patch was done in a hope of not letting the ACPI-aware OS using the same address for another BAR or something else thus avoiding conflicts.
Please, let me know if I'm missing something here. If you want, I can send v6 with this patch removed completely.
(BTW, sorry for the delay. It's holiday here in Brazil and, you know, real life happens sometimes :-) )
Thanks,
Paulo
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register
values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability
and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR,
ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2
or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
-- 2.1.0
On Wed, Jun 24, 2015 at 01:00:49PM -0300, Paulo Alcantara wrote:
On Wed, 24 Jun 2015 17:11:26 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Mon, Jun 22, 2015 at 08:10:28PM -0300, Paulo Alcantara wrote:
This block is mapped into memory space, using the Root Complex Base Address (RCBA) register of the PCI-to-LPC bridge. Accesses in this space must be limited to 32-(DW) bit quantities. Burst accesses are not allowed.
All Chipset Configuration Registers are located in this 16KiB space.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
Can you confirm things just work for you if you drop this patch from the series?
Hi Michael,
Yes, everything works. I still see no point of adding it to SSDT because OVMF is already setting RCBA to that fixed address and so SeaBIOS once my patch is applied. Therefore, there is no no such thing like "dynamically programmed by BIOS" either. Although, my host and probably yours define the same address in DSDT.
Given that UEFI and a BIOS implementations are using the same fixed address, this patch was done in a hope of not letting the ACPI-aware OS using the same address for another BAR or something else thus avoiding conflicts.
But does PCI root CRS cover this range? If not - an ACPI aware OS can't assign PCI BARs in this range really.
Please, let me know if I'm missing something here. If you want, I can send v6 with this patch removed completely.
(BTW, sorry for the delay. It's holiday here in Brazil and, you know, real life happens sometimes :-) )
Thanks,
Paulo
No rush, we have a couple of days until the hard freeze still
v1 -> v2:
- s/PDRC/CCR/ for clarity and match ICH9 spec
- remove unnecessary OperationRegion for RCRB
v2 -> v3: (no changes)
v3 -> v4:
- quote RCRB description from ICH9 spec to commit log
- fix indentation issue in _CRS() method declaration
- create hw/i386/ich9-cc.h for chipset configuration register
values and use them in ASL
v4 -> v5:
- prefix macros in ich9-cc.h with "ICH9_" for better readability
and make use of them in CCR device definition
hw/i386/q35-acpi-dsdt.dsl | 16 ++++++++++++++++ include/hw/i386/ich9-cc.h | 15 +++++++++++++++ tests/acpi-test-data/q35/DSDT | Bin 7666 -> 7723 bytes 3 files changed, 31 insertions(+) create mode 100644 include/hw/i386/ich9-cc.h
diff --git a/hw/i386/q35-acpi-dsdt.dsl b/hw/i386/q35-acpi-dsdt.dsl index 16eaca3..8f4bb6a 100644 --- a/hw/i386/q35-acpi-dsdt.dsl +++ b/hw/i386/q35-acpi-dsdt.dsl @@ -114,6 +114,22 @@ DefinitionBlock ( } }
+#include "hw/i386/ich9-cc.h"
+/****************************************************************
- Chipset Configuration Registers
- ****************************************************************/
+Scope(_SB.PCI0) {
- Device (CCR) {
Name (_HID, EISAID("PNP0C02"))
Name (_UID, 1)
Name (_CRS, ResourceTemplate() {
Memory32Fixed(ReadWrite, ICH9_RCBA_BASE_ADDR,
ICH9_RCRB_SIZE)
})
- }
+}
#include "acpi-dsdt-hpet.dsl"
diff --git a/include/hw/i386/ich9-cc.h b/include/hw/i386/ich9-cc.h new file mode 100644 index 0000000..d4918ff --- /dev/null +++ b/include/hw/i386/ich9-cc.h @@ -0,0 +1,15 @@ +/*
- QEMU ICH9 Chipset Configuration Registers
- Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com
- This work is licensed under the terms of the GNU GPL, version 2
or later.
- See the COPYING file in the top-level directory.
- */
+#ifndef HW_ICH9_CC_H +#define HW_ICH9_CC_H
+#define ICH9_RCBA_BASE_ADDR 0xfed1c000 +#define ICH9_RCRB_SIZE 0x00004000
+#endif /* HW_ICH9_CC_H */ diff --git a/tests/acpi-test-data/q35/DSDT b/tests/acpi-test-data/q35/DSDT index 4723e5954dccb00995ccaf521b7daf6bf15cf1d4..f3bda7b54ea6d669b1498d9380e7781207fb6e49 100644 GIT binary patch delta 81 zcmexlz1oJ$CD<iITaJN&F>xbTJfnq$UVN}qe1Nm3L3ERjvvW{9N4$rp3y<Rk9wv_X lP)`>|j(F#wU_n7HzBWz<Mur0y|1mf)FjO*Z&S3140RVI`6(s-w
delta 24 gcmZ2&^U0daCD<k8lPm)R<DrdQ@r;`nF?PxT0Bl$YHUIzs
-- 2.1.0
-- Paulo Alcantara, C.E.S.A.R Speaking for myself only.
This patch adds a testcase that covers the following: 1) TCO default values 2) first and second TCO timeout 3) watch and validate ticks counter through TCO_RLD register 4) maximum supported TCO timeout (0x3ff) 5) watchdog actions (pause/reset/shutdown/none) upon second TCO timeout 6) set and get of TCO control and status bits
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * some cleanup * add test for TCO_LOCK bit
v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
v3 -> v4: * add more description to commit log * use RCBA_BASE_ADDR macro defintion from hw/i386/ich9-cc.h instead
v4 -> v5: * use modified macros (now prefixed with ICH9_) from ich9-cc.h * move license to GPLv2+ --- tests/Makefile | 2 + tests/tco-test.c | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index eff5e11..ef1e981 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -152,6 +152,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -370,6 +371,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..79a673e --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,460 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/i386/ich9-cc.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define PM_IO_BASE_ADDR 0xb000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + ICH9_RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(ICH9_RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS); + if (enable) { + val &= ~ICH9_LPC_RCBA_GCS_NO_REBOOT; + } else { + val |= ICH9_LPC_RCBA_GCS_NO_REBOOT; + } + writel(ICH9_RCBA_BASE_ADDR + ICH9_LPC_RCBA_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
On Mon, Jun 22, 2015 at 08:10:27PM -0300, Paulo Alcantara wrote:
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 pcacjr@zytor.com
One useful feature to implement could be ability to set the NO_REBOOT strapping pin to 1. And maybe it's even a better default - will have to experiment with this feature some more. Does not have to block this patchset, can be done by a patch on top.
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 pcacjr@zytor.com --- 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
v4 -> v5: * remove unused field (use_tco) in TCOIORegs structure * move license to GPLv2+
v5 -> v6: * remove "io_tco" field from ICH9LPCPMRegs structure since it's no longer used * set ICH9_CC_GCS_NO_REBOOT bit by default in ICH9's LPC initialisation --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 55 ++++++++++- hw/acpi/tco.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/i386/pc_q35.c | 4 +- hw/isa/lpc_ich9.c | 16 ++- include/hw/acpi/ich9.h | 6 +- include/hw/acpi/tco.h | 82 +++++++++++++++ include/hw/boards.h | 3 +- include/hw/i386/ich9.h | 11 ++- include/hw/i386/pc.h | 1 + 10 files changed, 436 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..1794a54 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,264 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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_CC_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_CC_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..b547002 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -138,6 +138,7 @@ static void ich9_cc_reset(ICH9LPCState *lpc) 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); } @@ -313,6 +314,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 +368,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..bc3daec 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 { /* @@ -54,9 +55,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..c63afc8 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,82 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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 { + 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..f5681a3 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" @@ -90,6 +93,9 @@ Object *ich9_lpc_find(void); #define ICH9_CC_DIR_MASK 0x7 #define ICH9_CC_OIC 0x31FF #define ICH9_CC_OIC_AEN 0x1 +#define ICH9_CC_GCS 0x3410 +#define ICH9_CC_GCS_DEFAULT 0x00000020 +#define ICH9_CC_GCS_NO_REBOOT (1 << 5)
/* D28:F[0-5] */ #define ICH9_PCIE_DEV 28 @@ -186,7 +192,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;
This patch adds a testcase that covers the following: 1) TCO default values 2) first and second TCO timeout 3) watch and validate ticks counter through TCO_RLD register 4) maximum supported TCO timeout (0x3ff) 5) watchdog actions (pause/reset/shutdown/none) upon second TCO timeout 6) set and get of TCO control and status bits
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * some cleanup * add test for TCO_LOCK bit
v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
v3 -> v4: * add more description to commit log * use RCBA_BASE_ADDR macro defintion from hw/i386/ich9-cc.h instead
v4 -> v5: * use modified macros (now prefixed with ICH9_) from ich9-cc.h * move license to GPLv2+
v5 -> v6: * remove include of "hw/i386/ich9-cc.h" since it's no longer exist --- tests/Makefile | 2 + tests/tco-test.c | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index eff5e11..ef1e981 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -152,6 +152,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -370,6 +371,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..1a2fe3d --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,460 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define RCBA_BASE_ADDR 0xfed1c000 +#define PM_IO_BASE_ADDR 0xb000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_CC_GCS); + if (enable) { + val &= ~ICH9_CC_GCS_NO_REBOOT; + } else { + val |= ICH9_CC_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_CC_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
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 pcacjr@zytor.com --- 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
v4 -> v5: * remove unused field (use_tco) in TCOIORegs structure * move license to GPLv2+
v5 -> v6: * remove "io_tco" field from ICH9LPCPMRegs structure since it's no longer used * set ICH9_CC_GCS_NO_REBOOT bit by default in ICH9's LPC initialisation
v6 -> v7: (no changes) --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 55 ++++++++++- hw/acpi/tco.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/i386/pc_q35.c | 4 +- hw/isa/lpc_ich9.c | 16 ++- include/hw/acpi/ich9.h | 6 +- include/hw/acpi/tco.h | 82 +++++++++++++++ include/hw/boards.h | 3 +- include/hw/i386/ich9.h | 11 ++- include/hw/i386/pc.h | 1 + 10 files changed, 436 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..1794a54 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,264 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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_CC_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_CC_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..b547002 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -138,6 +138,7 @@ static void ich9_cc_reset(ICH9LPCState *lpc) 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); } @@ -313,6 +314,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 +368,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..bc3daec 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 { /* @@ -54,9 +55,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..c63afc8 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,82 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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 { + 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..f5681a3 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" @@ -90,6 +93,9 @@ Object *ich9_lpc_find(void); #define ICH9_CC_DIR_MASK 0x7 #define ICH9_CC_OIC 0x31FF #define ICH9_CC_OIC_AEN 0x1 +#define ICH9_CC_GCS 0x3410 +#define ICH9_CC_GCS_DEFAULT 0x00000020 +#define ICH9_CC_GCS_NO_REBOOT (1 << 5)
/* D28:F[0-5] */ #define ICH9_PCIE_DEV 28 @@ -186,7 +192,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;
This patch adds a testcase that covers the following: 1) TCO default values 2) first and second TCO timeout 3) watch and validate ticks counter through TCO_RLD register 4) maximum supported TCO timeout (0x3ff) 5) watchdog actions (pause/reset/shutdown/none) upon second TCO timeout 6) set and get of TCO control and status bits
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * some cleanup * add test for TCO_LOCK bit
v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
v3 -> v4: * add more description to commit log * use RCBA_BASE_ADDR macro defintion from hw/i386/ich9-cc.h instead
v4 -> v5: * use modified macros (now prefixed with ICH9_) from ich9-cc.h * move license to GPLv2+
v5 -> v6: * remove include of "hw/i386/ich9-cc.h" since it's no longer exist
v6 -> v7: (no changes) --- tests/Makefile | 2 + tests/tco-test.c | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index eff5e11..ef1e981 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -152,6 +152,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -370,6 +371,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..1a2fe3d --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,460 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define RCBA_BASE_ADDR 0xfed1c000 +#define PM_IO_BASE_ADDR 0xb000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_CC_GCS); + if (enable) { + val &= ~ICH9_CC_GCS_NO_REBOOT; + } else { + val |= ICH9_CC_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_CC_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "pin-spkr" and it's sampled low by default.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- hw/acpi/tco.c | 3 ++- hw/isa/lpc_ich9.c | 38 ++++++++++++++++++++++++++++++++++++++ include/hw/i386/ich9.h | 11 +++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..c1f5739 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,8 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
- if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) { + if ((lpc->pin_strap.spkr & ICH9_PS_SPKR_PIN_LOW) && + !(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return; diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..49d1f30 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -575,11 +575,49 @@ static void ich9_lpc_get_sci_int(Object *obj, Visitor *v, visit_type_uint32(v, &value, name, errp); }
+static void ich9_lpc_get_spkr_pin(Object *obj, Visitor *v, + void *opaque, const char *name, + Error **errp) +{ + ICH9LPCState *lpc = opaque; + uint8_t value = lpc->pin_strap.spkr; + + visit_type_uint8(v, &value, name, errp); +} + +static void ich9_lpc_set_spkr_pin(Object *obj, Visitor *v, + void *opaque, const char *name, + Error **errp) +{ + ICH9LPCState *lpc = opaque; + Error *local_err = NULL; + uint8_t value; + uint32_t *gcs; + + visit_type_uint8(v, &value, name, &local_err); + if (local_err) { + goto out; + } + value &= ICH9_PS_SPKR_PIN_MASK; + if (value & ICH9_PS_SPKR_PIN_HIGH) { + gcs = (uint32_t *)&lpc->chip_config[ICH9_CC_GCS]; + *gcs |= ICH9_CC_GCS_NO_REBOOT; + } + lpc->pin_strap.spkr = value; +out: + error_propagate(errp, local_err); +} + static void ich9_lpc_add_properties(ICH9LPCState *lpc) { static const uint8_t acpi_enable_cmd = ICH9_APM_ACPI_ENABLE; static const uint8_t acpi_disable_cmd = ICH9_APM_ACPI_DISABLE; + lpc->pin_strap.spkr = ICH9_PS_SPKR_PIN_DEFAULT;
+ object_property_add(OBJECT(lpc), "pin-spkr", "uint8", + ich9_lpc_get_spkr_pin, + ich9_lpc_set_spkr_pin, + NULL, lpc, NULL); object_property_add(OBJECT(lpc), ACPI_PM_PROP_SCI_INT, "uint32", ich9_lpc_get_sci_int, NULL, NULL, NULL, NULL); diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..aafc43f 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
+ /* 2.24 Pin Straps */ + struct { + uint8_t spkr; + } pin_strap; + /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE]; @@ -72,6 +77,12 @@ Object *ich9_lpc_find(void); #define Q35_MASK(bit, ms_bit, ls_bit) \ ((uint##bit##_t)(((1ULL << ((ms_bit) + 1)) - 1) & ~((1ULL << ls_bit) - 1)))
+/* ICH9: Pin Straps */ +#define ICH9_PS_SPKR_PIN_LOW 0x01 +#define ICH9_PS_SPKR_PIN_HIGH 0x02 +#define ICH9_PS_SPKR_PIN_MASK 0x03 +#define ICH9_PS_SPKR_PIN_DEFAULT ICH9_PS_SPKR_PIN_LOW + /* ICH9: Chipset Configuration Registers */ #define ICH9_CC_ADDR_MASK (ICH9_CC_SIZE - 1)
On Sat, Jun 27, 2015 at 02:56:33PM -0300, Paulo Alcantara wrote:
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "pin-spkr"
I would call it "noreboot" and not pin-spkr since that's what it does in the end.
and it's sampled low by default.
I think sample high is a safer default.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/acpi/tco.c | 3 ++- hw/isa/lpc_ich9.c | 38 ++++++++++++++++++++++++++++++++++++++ include/hw/i386/ich9.h | 11 +++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..c1f5739 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,8 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) {
if ((lpc->pin_strap.spkr & ICH9_PS_SPKR_PIN_LOW) &&
!(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return;
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..49d1f30 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -575,11 +575,49 @@ static void ich9_lpc_get_sci_int(Object *obj, Visitor *v, visit_type_uint32(v, &value, name, errp); }
+static void ich9_lpc_get_spkr_pin(Object *obj, Visitor *v,
void *opaque, const char *name,
Error **errp)
+{
- ICH9LPCState *lpc = opaque;
- uint8_t value = lpc->pin_strap.spkr;
- visit_type_uint8(v, &value, name, errp);
+}
+static void ich9_lpc_set_spkr_pin(Object *obj, Visitor *v,
void *opaque, const char *name,
Error **errp)
+{
- ICH9LPCState *lpc = opaque;
- Error *local_err = NULL;
- uint8_t value;
- uint32_t *gcs;
- visit_type_uint8(v, &value, name, &local_err);
- if (local_err) {
goto out;
- }
- value &= ICH9_PS_SPKR_PIN_MASK;
- if (value & ICH9_PS_SPKR_PIN_HIGH) {
gcs = (uint32_t *)&lpc->chip_config[ICH9_CC_GCS];
*gcs |= ICH9_CC_GCS_NO_REBOOT;
- }
- lpc->pin_strap.spkr = value;
+out:
- error_propagate(errp, local_err);
+}
static void ich9_lpc_add_properties(ICH9LPCState *lpc) { static const uint8_t acpi_enable_cmd = ICH9_APM_ACPI_ENABLE; static const uint8_t acpi_disable_cmd = ICH9_APM_ACPI_DISABLE;
lpc->pin_strap.spkr = ICH9_PS_SPKR_PIN_DEFAULT;
object_property_add(OBJECT(lpc), "pin-spkr", "uint8",
ich9_lpc_get_spkr_pin,
ich9_lpc_set_spkr_pin,
NULL, lpc, NULL);
object_property_add(OBJECT(lpc), ACPI_PM_PROP_SCI_INT, "uint32", ich9_lpc_get_sci_int, NULL, NULL, NULL, NULL);
BTW it's easier to add simple properties in dc->props then you don't need all the error propagate code etc.
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..aafc43f 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
- /* 2.24 Pin Straps */
- struct {
uint8_t spkr;
- } pin_strap;
- /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE];
@@ -72,6 +77,12 @@ Object *ich9_lpc_find(void); #define Q35_MASK(bit, ms_bit, ls_bit) \ ((uint##bit##_t)(((1ULL << ((ms_bit) + 1)) - 1) & ~((1ULL << ls_bit) - 1)))
+/* ICH9: Pin Straps */ +#define ICH9_PS_SPKR_PIN_LOW 0x01 +#define ICH9_PS_SPKR_PIN_HIGH 0x02 +#define ICH9_PS_SPKR_PIN_MASK 0x03 +#define ICH9_PS_SPKR_PIN_DEFAULT ICH9_PS_SPKR_PIN_LOW
The interface seems a bit inconvenient to me. Why not make it a simple boolean property?
/* ICH9: Chipset Configuration Registers */ #define ICH9_CC_ADDR_MASK (ICH9_CC_SIZE - 1)
-- 2.1.0
On Sun, 28 Jun 2015 10:37:58 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Sat, Jun 27, 2015 at 02:56:33PM -0300, Paulo Alcantara wrote:
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "pin-spkr"
I would call it "noreboot" and not pin-spkr since that's what it does in the end.
Right. That's also more user intuitive, indeed.
and it's sampled low by default.
I think sample high is a safer default.
OK. I'll default it to high.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
hw/acpi/tco.c | 3 ++- hw/isa/lpc_ich9.c | 38 ++++++++++++++++++++++++++++++++++++++ include/hw/i386/ich9.h | 11 +++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..c1f5739 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,8 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) {
if ((lpc->pin_strap.spkr & ICH9_PS_SPKR_PIN_LOW) &&
!(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return;
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..49d1f30 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -575,11 +575,49 @@ static void ich9_lpc_get_sci_int(Object *obj, Visitor *v, visit_type_uint32(v, &value, name, errp); }
+static void ich9_lpc_get_spkr_pin(Object *obj, Visitor *v,
void *opaque, const char *name,
Error **errp)
+{
- ICH9LPCState *lpc = opaque;
- uint8_t value = lpc->pin_strap.spkr;
- visit_type_uint8(v, &value, name, errp);
+}
+static void ich9_lpc_set_spkr_pin(Object *obj, Visitor *v,
void *opaque, const char *name,
Error **errp)
+{
- ICH9LPCState *lpc = opaque;
- Error *local_err = NULL;
- uint8_t value;
- uint32_t *gcs;
- visit_type_uint8(v, &value, name, &local_err);
- if (local_err) {
goto out;
- }
- value &= ICH9_PS_SPKR_PIN_MASK;
- if (value & ICH9_PS_SPKR_PIN_HIGH) {
gcs = (uint32_t *)&lpc->chip_config[ICH9_CC_GCS];
*gcs |= ICH9_CC_GCS_NO_REBOOT;
- }
- lpc->pin_strap.spkr = value;
+out:
- error_propagate(errp, local_err);
+}
static void ich9_lpc_add_properties(ICH9LPCState *lpc) { static const uint8_t acpi_enable_cmd = ICH9_APM_ACPI_ENABLE; static const uint8_t acpi_disable_cmd = ICH9_APM_ACPI_DISABLE;
lpc->pin_strap.spkr = ICH9_PS_SPKR_PIN_DEFAULT;
object_property_add(OBJECT(lpc), "pin-spkr", "uint8",
ich9_lpc_get_spkr_pin,
ich9_lpc_set_spkr_pin,
NULL, lpc, NULL);
object_property_add(OBJECT(lpc), ACPI_PM_PROP_SCI_INT,
"uint32", ich9_lpc_get_sci_int, NULL, NULL, NULL, NULL);
BTW it's easier to add simple properties in dc->props then you don't need all the error propagate code etc.
Hrm - good to know. I'll take a look at it. Thanks.
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..aafc43f 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
- /* 2.24 Pin Straps */
- struct {
uint8_t spkr;
- } pin_strap;
- /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE];
@@ -72,6 +77,12 @@ Object *ich9_lpc_find(void); #define Q35_MASK(bit, ms_bit, ls_bit) \ ((uint##bit##_t)(((1ULL << ((ms_bit) + 1)) - 1) & ~((1ULL << ls_bit) - 1))) +/* ICH9: Pin Straps */ +#define ICH9_PS_SPKR_PIN_LOW 0x01 +#define ICH9_PS_SPKR_PIN_HIGH 0x02 +#define ICH9_PS_SPKR_PIN_MASK 0x03 +#define ICH9_PS_SPKR_PIN_DEFAULT ICH9_PS_SPKR_PIN_LOW
The interface seems a bit inconvenient to me. Why not make it a simple boolean property?
No real reason, actually. Since it has no more than 2 states (high and low), a boolean property would be appropriate. I'll make it boolean.
Thanks,
Paulo
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 pcacjr@zytor.com --- 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
v4 -> v5: * remove unused field (use_tco) in TCOIORegs structure * move license to GPLv2+
v5 -> v6: * remove "io_tco" field from ICH9LPCPMRegs structure since it's no longer used * set ICH9_CC_GCS_NO_REBOOT bit by default in ICH9's LPC initialisation
v6 -> v7: (no changes) --- hw/acpi/Makefile.objs | 2 +- hw/acpi/ich9.c | 55 ++++++++++- hw/acpi/tco.c | 264 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/i386/pc_q35.c | 4 +- hw/isa/lpc_ich9.c | 16 ++- include/hw/acpi/ich9.h | 6 +- include/hw/acpi/tco.h | 82 +++++++++++++++ include/hw/boards.h | 3 +- include/hw/i386/ich9.h | 11 ++- include/hw/i386/pc.h | 1 + 10 files changed, 436 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..1794a54 --- /dev/null +++ b/hw/acpi/tco.c @@ -0,0 +1,264 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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_CC_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_CC_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..b547002 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -138,6 +138,7 @@ static void ich9_cc_reset(ICH9LPCState *lpc) 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); } @@ -313,6 +314,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 +368,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..bc3daec 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 { /* @@ -54,9 +55,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..c63afc8 --- /dev/null +++ b/include/hw/acpi/tco.h @@ -0,0 +1,82 @@ +/* + * QEMU ICH9 TCO emulation + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#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 { + 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..f5681a3 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" @@ -90,6 +93,9 @@ Object *ich9_lpc_find(void); #define ICH9_CC_DIR_MASK 0x7 #define ICH9_CC_OIC 0x31FF #define ICH9_CC_OIC_AEN 0x1 +#define ICH9_CC_GCS 0x3410 +#define ICH9_CC_GCS_DEFAULT 0x00000020 +#define ICH9_CC_GCS_NO_REBOOT (1 << 5)
/* D28:F[0-5] */ #define ICH9_PCIE_DEV 28 @@ -186,7 +192,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;
This patch adds a testcase that covers the following: 1) TCO default values 2) first and second TCO timeout 3) watch and validate ticks counter through TCO_RLD register 4) maximum supported TCO timeout (0x3ff) 5) watchdog actions (pause/reset/shutdown/none) upon second TCO timeout 6) set and get of TCO control and status bits
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v1 -> v2: * some cleanup * add test for TCO_LOCK bit
v2 -> v3: * add tests for TCO control & status bits * fix check of SECOND_TO_STS bit (it's set in TCO2_STS reg)
v3 -> v4: * add more description to commit log * use RCBA_BASE_ADDR macro defintion from hw/i386/ich9-cc.h instead
v4 -> v5: * use modified macros (now prefixed with ICH9_) from ich9-cc.h * move license to GPLv2+
v5 -> v6: * remove include of "hw/i386/ich9-cc.h" since it's no longer exist
v6 -> v7: (no changes) --- tests/Makefile | 2 + tests/tco-test.c | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 tests/tco-test.c
diff --git a/tests/Makefile b/tests/Makefile index eff5e11..ef1e981 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -152,6 +152,7 @@ check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) check-qtest-i386-y += tests/wdt_ib700-test$(EXESUF) +check-qtest-i386-y += tests/tco-test$(EXESUF) gcov-files-i386-y += hw/watchdog/watchdog.c hw/watchdog/wdt_ib700.c check-qtest-i386-y += $(check-qtest-pci-y) gcov-files-i386-y += $(gcov-files-pci-y) @@ -370,6 +371,7 @@ tests/eepro100-test$(EXESUF): tests/eepro100-test.o tests/vmxnet3-test$(EXESUF): tests/vmxnet3-test.o tests/ne2000-test$(EXESUF): tests/ne2000-test.o tests/wdt_ib700-test$(EXESUF): tests/wdt_ib700-test.o +tests/tco-test$(EXESUF): tests/tco-test.o $(libqos-pc-obj-y) tests/virtio-balloon-test$(EXESUF): tests/virtio-balloon-test.o tests/virtio-blk-test$(EXESUF): tests/virtio-blk-test.o $(libqos-virtio-obj-y) tests/virtio-net-test$(EXESUF): tests/virtio-net-test.o $(libqos-pc-obj-y) diff --git a/tests/tco-test.c b/tests/tco-test.c new file mode 100644 index 0000000..1a2fe3d --- /dev/null +++ b/tests/tco-test.c @@ -0,0 +1,460 @@ +/* + * QEMU ICH9 TCO emulation tests + * + * Copyright (c) 2015 Paulo Alcantara pcacjr@zytor.com + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ +#include <glib.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/pci-pc.h" +#include "hw/pci/pci_regs.h" +#include "hw/i386/ich9.h" +#include "hw/acpi/ich9.h" +#include "hw/acpi/tco.h" + +#define RCBA_BASE_ADDR 0xfed1c000 +#define PM_IO_BASE_ADDR 0xb000 + +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, +}; + +#define TCO_SECS_TO_TICKS(secs) (((secs) * 10) / 6) +#define TCO_TICKS_TO_SECS(ticks) (((ticks) * 6) / 10) + +typedef struct { + const char *args; + QPCIDevice *dev; + void *lpc_base; + void *tco_io_base; +} TestData; + +static void test_init(TestData *d) +{ + QPCIBus *bus; + QTestState *qs; + char *s; + + s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + qs = qtest_start(s); + qtest_irq_intercept_in(qs, "ioapic"); + g_free(s); + + bus = qpci_init_pc(); + d->dev = qpci_device_find(bus, QPCI_DEVFN(0x1f, 0x00)); + g_assert(d->dev != NULL); + + /* map PCI-to-LPC bridge interface BAR */ + d->lpc_base = qpci_iomap(d->dev, 0, NULL); + + qpci_device_enable(d->dev); + + g_assert(d->lpc_base != NULL); + + /* set ACPI PM I/O space base address */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_PMBASE, + PM_IO_BASE_ADDR | 0x1); + /* enable ACPI I/O */ + qpci_config_writeb(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_ACPI_CTRL, + 0x80); + /* set Root Complex BAR */ + qpci_config_writel(d->dev, (uintptr_t)d->lpc_base + ICH9_LPC_RCBA, + RCBA_BASE_ADDR | 0x1); + + d->tco_io_base = (void *)((uintptr_t)PM_IO_BASE_ADDR + 0x60); +} + +static void stop_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val |= TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void start_tco(const TestData *d) +{ + uint32_t val; + + val = qpci_io_readw(d->dev, d->tco_io_base + TCO1_CNT); + val &= ~TCO_TMR_HLT; + qpci_io_writew(d->dev, d->tco_io_base + TCO1_CNT, val); +} + +static void load_tco(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_RLD, 4); +} + +static void set_tco_timeout(const TestData *d, uint16_t ticks) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO_TMR, ticks); +} + +static void clear_tco_status(const TestData *d) +{ + qpci_io_writew(d->dev, d->tco_io_base + TCO1_STS, 0x0008); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0002); + qpci_io_writew(d->dev, d->tco_io_base + TCO2_STS, 0x0004); +} + +static void reset_on_second_timeout(bool enable) +{ + uint32_t val; + + val = readl(RCBA_BASE_ADDR + ICH9_CC_GCS); + if (enable) { + val &= ~ICH9_CC_GCS_NO_REBOOT; + } else { + val |= ICH9_CC_GCS_NO_REBOOT; + } + writel(RCBA_BASE_ADDR + ICH9_CC_GCS, val); +} + +static void test_tco_defaults(void) +{ + TestData d; + + d.args = NULL; + test_init(&d); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, + TCO_RLD_DEFAULT); + /* TCO_DAT_IN & TCO_DAT_OUT */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_DAT_IN), ==, + (TCO_DAT_OUT_DEFAULT << 8) | TCO_DAT_IN_DEFAULT); + /* TCO1_STS & TCO2_STS */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_STS), ==, + (TCO2_STS_DEFAULT << 16) | TCO1_STS_DEFAULT); + /* TCO1_CNT & TCO2_CNT */ + g_assert_cmpint(qpci_io_readl(d.dev, d.tco_io_base + TCO1_CNT), ==, + (TCO2_CNT_DEFAULT << 16) | TCO1_CNT_DEFAULT); + /* TCO_MESSAGE1 & TCO_MESSAGE2 */ + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_MESSAGE1), ==, + (TCO_MESSAGE2_DEFAULT << 8) | TCO_MESSAGE1_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + TCO_WDCNT), ==, + TCO_WDCNT_DEFAULT); + g_assert_cmpint(qpci_io_readb(d.dev, d.tco_io_base + SW_IRQ_GEN), ==, + SW_IRQ_GEN_DEFAULT); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_TMR), ==, + TCO_TMR_DEFAULT); + qtest_end(); +} + +static void test_tco_timeout(void) +{ + TestData d; + const uint16_t ticks = TCO_SECS_TO_TICKS(4); + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + /* test first timeout */ + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + /* test clearing timeout bit */ + val |= TCO_TIMEOUT; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + + /* test second timeout */ + clock_step(ticks * TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & TCO_SECOND_TO_STS ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco_max_timeout(void) +{ + TestData d; + const uint16_t ticks = 0xffff; + uint32_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(((ticks & TCO_TMR_MASK) - 1) * TCO_TICK_NSEC); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD); + g_assert_cmpint(val & TCO_RLD_MASK, ==, 1); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 0); + clock_step(TCO_TICK_NSEC); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & TCO_TIMEOUT ? 1 : 0; + g_assert(ret == 1); + + stop_tco(&d); + qtest_end(); +} + +static QDict *get_watchdog_action(void) +{ + QDict *ev = qmp(""); + QDict *data; + g_assert(!strcmp(qdict_get_str(ev, "event"), "WATCHDOG")); + + data = qdict_get_qdict(ev, "data"); + QINCREF(data); + QDECREF(ev); + return data; +} + +static void test_tco_second_timeout_pause(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(32); + QDict *ad; + + td.args = "-watchdog-action pause"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "pause")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_reset(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(16); + QDict *ad; + + td.args = "-watchdog-action reset"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, TCO_SECS_TO_TICKS(16)); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "reset")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_shutdown(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(128); + QDict *ad; + + td.args = "-watchdog-action shutdown"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "shutdown")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_second_timeout_none(void) +{ + TestData td; + const uint16_t ticks = TCO_SECS_TO_TICKS(256); + QDict *ad; + + td.args = "-watchdog-action none"; + test_init(&td); + + stop_tco(&td); + clear_tco_status(&td); + reset_on_second_timeout(true); + set_tco_timeout(&td, ticks); + load_tco(&td); + start_tco(&td); + clock_step(ticks * TCO_TICK_NSEC * 2); + ad = get_watchdog_action(); + g_assert(!strcmp(qdict_get_str(ad, "action"), "none")); + QDECREF(ad); + + stop_tco(&td); + qtest_end(); +} + +static void test_tco_ticks_counter(void) +{ + TestData d; + uint16_t ticks = TCO_SECS_TO_TICKS(8); + uint16_t rld; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + + do { + rld = qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD) & TCO_RLD_MASK; + g_assert_cmpint(rld, ==, ticks); + clock_step(TCO_TICK_NSEC); + ticks--; + } while (!(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS) & TCO_TIMEOUT)); + + stop_tco(&d); + qtest_end(); +} + +static void test_tco1_control_bits(void) +{ + TestData d; + uint16_t val; + + d.args = NULL; + test_init(&d); + + val = TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + val &= ~TCO_LOCK; + qpci_io_writew(d.dev, d.tco_io_base + TCO1_CNT, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_CNT), ==, + TCO_LOCK); + qtest_end(); +} + +static void test_tco1_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = NULL; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(false); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC); + + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_IN, 0); + qpci_io_writeb(d.dev, d.tco_io_base + TCO_DAT_OUT, 0); + val = qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS); + ret = val & (TCO_TIMEOUT | SW_TCO_SMI | TCO_INT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO1_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO1_STS), ==, 0); + qtest_end(); +} + +static void test_tco2_status_bits(void) +{ + TestData d; + uint16_t ticks = 8; + uint16_t val; + int ret; + + d.args = "-watchdog-action none"; + test_init(&d); + + stop_tco(&d); + clear_tco_status(&d); + reset_on_second_timeout(true); + set_tco_timeout(&d, ticks); + load_tco(&d); + start_tco(&d); + clock_step(ticks * TCO_TICK_NSEC * 2); + + val = qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS); + ret = val & (TCO_SECOND_TO_STS | TCO_BOOT_STS) ? 1 : 0; + g_assert(ret == 1); + qpci_io_writew(d.dev, d.tco_io_base + TCO2_STS, val); + g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO2_STS), ==, 0); + qtest_end(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("tco/defaults", test_tco_defaults); + qtest_add_func("tco/timeout/no_action", test_tco_timeout); + qtest_add_func("tco/timeout/no_action/max", test_tco_max_timeout); + qtest_add_func("tco/second_timeout/pause", test_tco_second_timeout_pause); + qtest_add_func("tco/second_timeout/reset", test_tco_second_timeout_reset); + qtest_add_func("tco/second_timeout/shutdown", + test_tco_second_timeout_shutdown); + qtest_add_func("tco/second_timeout/none", test_tco_second_timeout_none); + qtest_add_func("tco/counter", test_tco_ticks_counter); + qtest_add_func("tco/tco1_control/bits", test_tco1_control_bits); + qtest_add_func("tco/tco1_status/bits", test_tco1_status_bits); + qtest_add_func("tco/tco2_status/bits", test_tco2_status_bits); + return g_test_run(); +}
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "noreboot" and it's sampled high by default.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com --- v7 -> v8: * change property name to "noreboot" * default "noreboot" property to high * define property in dc->props * update tco tests to support and exercise "noreboot" property --- hw/acpi/tco.c | 2 +- hw/isa/lpc_ich9.c | 6 ++++++ include/hw/i386/ich9.h | 5 +++++ tests/tco-test.c | 18 ++++++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..7a026c2 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,7 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
- if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) { + if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return; diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..3b460d4 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -688,6 +688,11 @@ static const VMStateDescription vmstate_ich9_lpc = { } };
+static Property ich9_lpc_properties[] = { + DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true), + DEFINE_PROP_END_OF_LIST(), +}; + static void ich9_lpc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -699,6 +704,7 @@ static void ich9_lpc_class_init(ObjectClass *klass, void *data) dc->reset = ich9_lpc_reset; k->init = ich9_lpc_init; dc->vmsd = &vmstate_ich9_lpc; + dc->props = ich9_lpc_properties; k->config_write = ich9_lpc_config_write; dc->desc = "ICH9 LPC bridge"; k->vendor_id = PCI_VENDOR_ID_INTEL; diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..63c5cd8 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
+ /* 2.24 Pin Straps */ + struct { + bool spkr_hi; + } pin_strap; + /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE]; diff --git a/tests/tco-test.c b/tests/tco-test.c index 1a2fe3d..6a48188 100644 --- a/tests/tco-test.c +++ b/tests/tco-test.c @@ -42,6 +42,7 @@ enum {
typedef struct { const char *args; + bool noreboot; QPCIDevice *dev; void *lpc_base; void *tco_io_base; @@ -53,7 +54,9 @@ static void test_init(TestData *d) QTestState *qs; char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args); + s = g_strdup_printf("-machine q35 %s %s", + d->noreboot ? "" : "-global ICH9-LPC.noreboot=false", + !d->args ? "" : d->args); qs = qtest_start(s); qtest_irq_intercept_in(qs, "ioapic"); g_free(s); @@ -135,6 +138,7 @@ static void test_tco_defaults(void) TestData d;
d.args = NULL; + d.noreboot = true; test_init(&d); g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, TCO_RLD_DEFAULT); @@ -167,6 +171,7 @@ static void test_tco_timeout(void) int ret;
d.args = NULL; + d.noreboot = true; test_init(&d);
stop_tco(&d); @@ -210,6 +215,7 @@ static void test_tco_max_timeout(void) int ret;
d.args = NULL; + d.noreboot = true; test_init(&d);
stop_tco(&d); @@ -253,6 +259,7 @@ static void test_tco_second_timeout_pause(void) QDict *ad;
td.args = "-watchdog-action pause"; + td.noreboot = false; test_init(&td);
stop_tco(&td); @@ -277,6 +284,7 @@ static void test_tco_second_timeout_reset(void) QDict *ad;
td.args = "-watchdog-action reset"; + td.noreboot = false; test_init(&td);
stop_tco(&td); @@ -301,6 +309,7 @@ static void test_tco_second_timeout_shutdown(void) QDict *ad;
td.args = "-watchdog-action shutdown"; + td.noreboot = false; test_init(&td);
stop_tco(&td); @@ -325,6 +334,7 @@ static void test_tco_second_timeout_none(void) QDict *ad;
td.args = "-watchdog-action none"; + td.noreboot = false; test_init(&td);
stop_tco(&td); @@ -349,6 +359,7 @@ static void test_tco_ticks_counter(void) uint16_t rld;
d.args = NULL; + d.noreboot = true; test_init(&d);
stop_tco(&d); @@ -375,6 +386,7 @@ static void test_tco1_control_bits(void) uint16_t val;
d.args = NULL; + d.noreboot = true; test_init(&d);
val = TCO_LOCK; @@ -394,6 +406,7 @@ static void test_tco1_status_bits(void) int ret;
d.args = NULL; + d.noreboot = true; test_init(&d);
stop_tco(&d); @@ -421,7 +434,8 @@ static void test_tco2_status_bits(void) uint16_t val; int ret;
- d.args = "-watchdog-action none"; + d.args = NULL; + d.noreboot = true; test_init(&d);
stop_tco(&d);
On 28/06/2015 19:58, Paulo Alcantara wrote:
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "noreboot" and it's sampled high by default.
I know Michael suggested this, but I think default high is a worse default. It does not allow recovering from a hard lockup where you cannot process an NMI, unlike all other watchdogs implemented by QEMU. In fact, the Linux driver fails to start if the strap is high.
My theory is that hardware manufacturers should only set the strap high if they want the firmware to have total control of the watchdog via SMIs (TCO_EN).
If it is just a matter of being late in 2.4, just delay everything to 2.5. It doesn't require any more work from Paulo, as you can just flip the default yourself without adding a new machine type (in fact I'm still not sure why machine types for Q35 are versioned, since migration is not supported...).
Paolo
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v7 -> v8:
- change property name to "noreboot"
- default "noreboot" property to high
- define property in dc->props
- update tco tests to support and exercise "noreboot" property
hw/acpi/tco.c | 2 +- hw/isa/lpc_ich9.c | 6 ++++++ include/hw/i386/ich9.h | 5 +++++ tests/tco-test.c | 18 ++++++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..7a026c2 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,7 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) {
if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return;
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..3b460d4 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -688,6 +688,11 @@ static const VMStateDescription vmstate_ich9_lpc = { } };
+static Property ich9_lpc_properties[] = {
- DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true),
- DEFINE_PROP_END_OF_LIST(),
+};
static void ich9_lpc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -699,6 +704,7 @@ static void ich9_lpc_class_init(ObjectClass *klass, void *data) dc->reset = ich9_lpc_reset; k->init = ich9_lpc_init; dc->vmsd = &vmstate_ich9_lpc;
- dc->props = ich9_lpc_properties; k->config_write = ich9_lpc_config_write; dc->desc = "ICH9 LPC bridge"; k->vendor_id = PCI_VENDOR_ID_INTEL;
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..63c5cd8 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
- /* 2.24 Pin Straps */
- struct {
bool spkr_hi;
- } pin_strap;
- /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE];
diff --git a/tests/tco-test.c b/tests/tco-test.c index 1a2fe3d..6a48188 100644 --- a/tests/tco-test.c +++ b/tests/tco-test.c @@ -42,6 +42,7 @@ enum {
typedef struct { const char *args;
- bool noreboot; QPCIDevice *dev; void *lpc_base; void *tco_io_base;
@@ -53,7 +54,9 @@ static void test_init(TestData *d) QTestState *qs; char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args);
- s = g_strdup_printf("-machine q35 %s %s",
d->noreboot ? "" : "-global ICH9-LPC.noreboot=false",
qs = qtest_start(s); qtest_irq_intercept_in(qs, "ioapic"); g_free(s);!d->args ? "" : d->args);
@@ -135,6 +138,7 @@ static void test_tco_defaults(void) TestData d;
d.args = NULL;
- d.noreboot = true; test_init(&d); g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, TCO_RLD_DEFAULT);
@@ -167,6 +171,7 @@ static void test_tco_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -210,6 +215,7 @@ static void test_tco_max_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -253,6 +259,7 @@ static void test_tco_second_timeout_pause(void) QDict *ad;
td.args = "-watchdog-action pause";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -277,6 +284,7 @@ static void test_tco_second_timeout_reset(void) QDict *ad;
td.args = "-watchdog-action reset";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -301,6 +309,7 @@ static void test_tco_second_timeout_shutdown(void) QDict *ad;
td.args = "-watchdog-action shutdown";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -325,6 +334,7 @@ static void test_tco_second_timeout_none(void) QDict *ad;
td.args = "-watchdog-action none";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -349,6 +359,7 @@ static void test_tco_ticks_counter(void) uint16_t rld;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -375,6 +386,7 @@ static void test_tco1_control_bits(void) uint16_t val;
d.args = NULL;
d.noreboot = true; test_init(&d);
val = TCO_LOCK;
@@ -394,6 +406,7 @@ static void test_tco1_status_bits(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -421,7 +434,8 @@ static void test_tco2_status_bits(void) uint16_t val; int ret;
- d.args = "-watchdog-action none";
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
On Wed, Jul 01, 2015 at 03:18:41PM +0200, Paolo Bonzini wrote:
On 28/06/2015 19:58, Paulo Alcantara wrote:
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "noreboot" and it's sampled high by default.
I know Michael suggested this, but I think default high is a worse default. It does not allow recovering from a hard lockup where you cannot process an NMI, unlike all other watchdogs implemented by QEMU. In fact, the Linux driver fails to start if the strap is high.
My theory is that hardware manufacturers should only set the strap high if they want the firmware to have total control of the watchdog via SMIs (TCO_EN).
If it is just a matter of being late in 2.4, just delay everything to 2.5. It doesn't require any more work from Paulo, as you can just flip the default yourself without adding a new machine type (in fact I'm still not sure why machine types for Q35 are versioned, since migration is not supported...).
Paolo
I don't think we should defer the whole series because of the argument about the default. I've merged these patches, I you like, pls send a one-line patch on top to flip the default with some info on how it was tested, and we can discuss it separately.
Makes sense?
BTW 2.4 makes qemu versioned because ahci finally supports migration so yes, we'll have to version from now on.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v7 -> v8:
- change property name to "noreboot"
- default "noreboot" property to high
- define property in dc->props
- update tco tests to support and exercise "noreboot" property
hw/acpi/tco.c | 2 +- hw/isa/lpc_ich9.c | 6 ++++++ include/hw/i386/ich9.h | 5 +++++ tests/tco-test.c | 18 ++++++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..7a026c2 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,7 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) {
if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return;
diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..3b460d4 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -688,6 +688,11 @@ static const VMStateDescription vmstate_ich9_lpc = { } };
+static Property ich9_lpc_properties[] = {
- DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true),
- DEFINE_PROP_END_OF_LIST(),
+};
static void ich9_lpc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -699,6 +704,7 @@ static void ich9_lpc_class_init(ObjectClass *klass, void *data) dc->reset = ich9_lpc_reset; k->init = ich9_lpc_init; dc->vmsd = &vmstate_ich9_lpc;
- dc->props = ich9_lpc_properties; k->config_write = ich9_lpc_config_write; dc->desc = "ICH9 LPC bridge"; k->vendor_id = PCI_VENDOR_ID_INTEL;
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..63c5cd8 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
- /* 2.24 Pin Straps */
- struct {
bool spkr_hi;
- } pin_strap;
- /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE];
diff --git a/tests/tco-test.c b/tests/tco-test.c index 1a2fe3d..6a48188 100644 --- a/tests/tco-test.c +++ b/tests/tco-test.c @@ -42,6 +42,7 @@ enum {
typedef struct { const char *args;
- bool noreboot; QPCIDevice *dev; void *lpc_base; void *tco_io_base;
@@ -53,7 +54,9 @@ static void test_init(TestData *d) QTestState *qs; char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" : d->args);
- s = g_strdup_printf("-machine q35 %s %s",
d->noreboot ? "" : "-global ICH9-LPC.noreboot=false",
qs = qtest_start(s); qtest_irq_intercept_in(qs, "ioapic"); g_free(s);!d->args ? "" : d->args);
@@ -135,6 +138,7 @@ static void test_tco_defaults(void) TestData d;
d.args = NULL;
- d.noreboot = true; test_init(&d); g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base + TCO_RLD), ==, TCO_RLD_DEFAULT);
@@ -167,6 +171,7 @@ static void test_tco_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -210,6 +215,7 @@ static void test_tco_max_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -253,6 +259,7 @@ static void test_tco_second_timeout_pause(void) QDict *ad;
td.args = "-watchdog-action pause";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -277,6 +284,7 @@ static void test_tco_second_timeout_reset(void) QDict *ad;
td.args = "-watchdog-action reset";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -301,6 +309,7 @@ static void test_tco_second_timeout_shutdown(void) QDict *ad;
td.args = "-watchdog-action shutdown";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -325,6 +334,7 @@ static void test_tco_second_timeout_none(void) QDict *ad;
td.args = "-watchdog-action none";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -349,6 +359,7 @@ static void test_tco_ticks_counter(void) uint16_t rld;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -375,6 +386,7 @@ static void test_tco1_control_bits(void) uint16_t val;
d.args = NULL;
d.noreboot = true; test_init(&d);
val = TCO_LOCK;
@@ -394,6 +406,7 @@ static void test_tco1_status_bits(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -421,7 +434,8 @@ static void test_tco2_status_bits(void) uint16_t val; int ret;
- d.args = "-watchdog-action none";
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
On 01/07/2015 15:31, Michael S. Tsirkin wrote:
I don't think we should defer the whole series because of the argument about the default. I've merged these patches, I you like, pls send a one-line patch on top to flip the default with some info on how it was tested, and we can discuss it separately.
Makes sense?
Perfect.
Paolo
BTW 2.4 makes qemu versioned because ahci finally supports migration so yes, we'll have to version from now on.
On Wed, 1 Jul 2015 15:31:31 +0200 "Michael S. Tsirkin" mst@redhat.com wrote:
On Wed, Jul 01, 2015 at 03:18:41PM +0200, Paolo Bonzini wrote:
On 28/06/2015 19:58, Paulo Alcantara wrote:
If the signal is sampled high, this indicates that the system is strapped to the "No Reboot" mode (ICH9 will disable the TCO Timer system reboot feature). The status of this strap is readable via the NO_REBOOT bit (CC: offset 0x3410:bit 5).
The NO_REBOOT bit is set when SPKR pin on ICH9 is sampled high. This bit may be set or cleared by software if the strap is sampled low but may not override the strap when it indicates "No Reboot".
This patch implements the logic where hardware has ability to set SPKR pin through a property named "noreboot" and it's sampled high by default.
I know Michael suggested this, but I think default high is a worse default. It does not allow recovering from a hard lockup where you cannot process an NMI, unlike all other watchdogs implemented by QEMU. In fact, the Linux driver fails to start if the strap is high.
No, the iTCO_wdt driver won't fail to start. It actually depends on whether RCBA BAR is set. However, ICH9 will just ignore whether NO_REBOOT is set or unset when SPKR pin is high.
I think you forgot to apply my SeaBIOS patch that sets RCBA BAR so it failed to start. We should also make sure it's applied once TCO patches get upstream.
Thanks,
Paulo
My theory is that hardware manufacturers should only set the strap high if they want the firmware to have total control of the watchdog via SMIs (TCO_EN).
If it is just a matter of being late in 2.4, just delay everything to 2.5. It doesn't require any more work from Paulo, as you can just flip the default yourself without adding a new machine type (in fact I'm still not sure why machine types for Q35 are versioned, since migration is not supported...).
Paolo
I don't think we should defer the whole series because of the argument about the default. I've merged these patches, I you like, pls send a one-line patch on top to flip the default with some info on how it was tested, and we can discuss it separately.
Makes sense?
BTW 2.4 makes qemu versioned because ahci finally supports migration so yes, we'll have to version from now on.
Signed-off-by: Paulo Alcantara pcacjr@zytor.com
v7 -> v8:
- change property name to "noreboot"
- default "noreboot" property to high
- define property in dc->props
- update tco tests to support and exercise "noreboot" property
hw/acpi/tco.c | 2 +- hw/isa/lpc_ich9.c | 6 ++++++ include/hw/i386/ich9.h | 5 +++++ tests/tco-test.c | 18 ++++++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c index 1794a54..7a026c2 100644 --- a/hw/acpi/tco.c +++ b/hw/acpi/tco.c @@ -64,7 +64,7 @@ static void tco_timer_expired(void *opaque) tr->tco.sts2 |= TCO_BOOT_STS; tr->timeouts_no = 0;
if (!(gcs & ICH9_CC_GCS_NO_REBOOT)) {
if (!lpc->pin_strap.spkr_hi && !(gcs &
ICH9_CC_GCS_NO_REBOOT)) { watchdog_perform_action(); tco_timer_stop(tr); return; diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c index b547002..3b460d4 100644 --- a/hw/isa/lpc_ich9.c +++ b/hw/isa/lpc_ich9.c @@ -688,6 +688,11 @@ static const VMStateDescription vmstate_ich9_lpc = { } };
+static Property ich9_lpc_properties[] = {
- DEFINE_PROP_BOOL("noreboot", ICH9LPCState,
pin_strap.spkr_hi, true),
- DEFINE_PROP_END_OF_LIST(),
+};
static void ich9_lpc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -699,6 +704,7 @@ static void ich9_lpc_class_init(ObjectClass *klass, void *data) dc->reset = ich9_lpc_reset; k->init = ich9_lpc_init; dc->vmsd = &vmstate_ich9_lpc;
- dc->props = ich9_lpc_properties; k->config_write = ich9_lpc_config_write; dc->desc = "ICH9 LPC bridge"; k->vendor_id = PCI_VENDOR_ID_INTEL;
diff --git a/include/hw/i386/ich9.h b/include/hw/i386/ich9.h index f5681a3..63c5cd8 100644 --- a/include/hw/i386/ich9.h +++ b/include/hw/i386/ich9.h @@ -46,6 +46,11 @@ typedef struct ICH9LPCState { ICH9LPCPMRegs pm; uint32_t sci_level; /* track sci level */
- /* 2.24 Pin Straps */
- struct {
bool spkr_hi;
- } pin_strap;
- /* 10.1 Chipset Configuration registers(Memory Space) which is pointed by RCBA */ uint8_t chip_config[ICH9_CC_SIZE];
diff --git a/tests/tco-test.c b/tests/tco-test.c index 1a2fe3d..6a48188 100644 --- a/tests/tco-test.c +++ b/tests/tco-test.c @@ -42,6 +42,7 @@ enum {
typedef struct { const char *args;
- bool noreboot; QPCIDevice *dev; void *lpc_base; void *tco_io_base;
@@ -53,7 +54,9 @@ static void test_init(TestData *d) QTestState *qs; char *s;
- s = g_strdup_printf("-machine q35 %s", !d->args ? "" :
d->args);
- s = g_strdup_printf("-machine q35 %s %s",
d->noreboot ? "" : "-global
ICH9-LPC.noreboot=false",
qs = qtest_start(s); qtest_irq_intercept_in(qs, "ioapic"); g_free(s);!d->args ? "" : d->args);
@@ -135,6 +138,7 @@ static void test_tco_defaults(void) TestData d;
d.args = NULL;
- d.noreboot = true; test_init(&d); g_assert_cmpint(qpci_io_readw(d.dev, d.tco_io_base +
TCO_RLD), ==, TCO_RLD_DEFAULT); @@ -167,6 +171,7 @@ static void test_tco_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -210,6 +215,7 @@ static void test_tco_max_timeout(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -253,6 +259,7 @@ static void test_tco_second_timeout_pause(void) QDict *ad;
td.args = "-watchdog-action pause";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -277,6 +284,7 @@ static void test_tco_second_timeout_reset(void) QDict *ad;
td.args = "-watchdog-action reset";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -301,6 +309,7 @@ static void test_tco_second_timeout_shutdown(void) QDict *ad;
td.args = "-watchdog-action shutdown";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -325,6 +334,7 @@ static void test_tco_second_timeout_none(void) QDict *ad;
td.args = "-watchdog-action none";
td.noreboot = false; test_init(&td);
stop_tco(&td);
@@ -349,6 +359,7 @@ static void test_tco_ticks_counter(void) uint16_t rld;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -375,6 +386,7 @@ static void test_tco1_control_bits(void) uint16_t val;
d.args = NULL;
d.noreboot = true; test_init(&d);
val = TCO_LOCK;
@@ -394,6 +406,7 @@ static void test_tco1_status_bits(void) int ret;
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
@@ -421,7 +434,8 @@ static void test_tco2_status_bits(void) uint16_t val; int ret;
- d.args = "-watchdog-action none";
d.args = NULL;
d.noreboot = true; test_init(&d);
stop_tco(&d);
On 02/07/2015 03:30, Paulo Alcantara wrote:
No, the iTCO_wdt driver won't fail to start. It actually depends on whether RCBA BAR is set. However, ICH9 will just ignore whether NO_REBOOT is set or unset when SPKR pin is high.
This is the code I was referring to, in iTCO_wdt_start:
/* disable chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit()) { spin_unlock(&iTCO_wdt_private.io_lock); pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n"); return -EIO; }
Paolo