Hardware watchdog

Here is an updated hardware watchdog patch, which should fix
everything that was raised about the previous version ...

Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
Richard W.M. Jones 2009-04-25 13:56:19 +01:00 committed by Anthony Liguori
parent ffad4116b9
commit 9dd986ccf6
9 changed files with 865 additions and 0 deletions

View File

@ -582,6 +582,10 @@ OBJS += pcnet.o
OBJS += rtl8139.o OBJS += rtl8139.o
OBJS += e1000.o OBJS += e1000.o
# Generic watchdog support and some watchdog devices
OBJS += watchdog.o
OBJS += wdt_ib700.o wdt_i6300esb.o
ifeq ($(TARGET_BASE_ARCH), i386) ifeq ($(TARGET_BASE_ARCH), i386)
# Hardware support # Hardware support
OBJS+= ide.o pckbd.o vga.o $(SOUND_HW) dma.o OBJS+= ide.o pckbd.o vga.o $(SOUND_HW) dma.o

View File

@ -37,6 +37,7 @@
#include "virtio-balloon.h" #include "virtio-balloon.h"
#include "virtio-console.h" #include "virtio-console.h"
#include "hpet_emul.h" #include "hpet_emul.h"
#include "watchdog.h"
#include "smbios.h" #include "smbios.h"
/* output Bochs bios info messages */ /* output Bochs bios info messages */
@ -1023,6 +1024,8 @@ static void pc_init1(ram_addr_t ram_size, int vga_ram_size,
} }
} }
watchdog_pc_init(pci_bus);
for(i = 0; i < nb_nics; i++) { for(i = 0; i < nb_nics; i++) {
NICInfo *nd = &nd_table[i]; NICInfo *nd = &nd_table[i];

136
hw/watchdog.c Normal file
View File

@ -0,0 +1,136 @@
/*
* Virtual hardware watchdog.
*
* Copyright (C) 2009 Red Hat Inc.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* By Richard W.M. Jones (rjones@redhat.com).
*/
#include "qemu-common.h"
#include "sys-queue.h"
#include "sysemu.h"
#include "hw/watchdog.h"
static LIST_HEAD(watchdog_list, WatchdogTimerModel) watchdog_list;
void watchdog_add_model(WatchdogTimerModel *model)
{
LIST_INSERT_HEAD(&watchdog_list, model, entry);
}
/* Returns:
* 0 = continue
* 1 = exit program with error
* 2 = exit program without error
*/
int select_watchdog(const char *p)
{
WatchdogTimerModel *model;
if (watchdog) {
fprintf(stderr,
"qemu: only one watchdog option may be given\n");
return 1;
}
/* -watchdog ? lists available devices and exits cleanly. */
if (strcmp(p, "?") == 0) {
LIST_FOREACH(model, &watchdog_list, entry) {
fprintf(stderr, "\t%s\t%s\n",
model->wdt_name, model->wdt_description);
}
return 2;
}
LIST_FOREACH(model, &watchdog_list, entry) {
if (strcasecmp(model->wdt_name, p) == 0) {
watchdog = model;
return 0;
}
}
fprintf(stderr, "Unknown -watchdog device. Supported devices are:\n");
LIST_FOREACH(model, &watchdog_list, entry) {
fprintf(stderr, "\t%s\t%s\n",
model->wdt_name, model->wdt_description);
}
return 1;
}
int select_watchdog_action(const char *p)
{
if (strcasecmp(p, "reset") == 0)
watchdog_action = WDT_RESET;
else if (strcasecmp(p, "shutdown") == 0)
watchdog_action = WDT_SHUTDOWN;
else if (strcasecmp(p, "poweroff") == 0)
watchdog_action = WDT_POWEROFF;
else if (strcasecmp(p, "pause") == 0)
watchdog_action = WDT_PAUSE;
else if (strcasecmp(p, "debug") == 0)
watchdog_action = WDT_DEBUG;
else if (strcasecmp(p, "none") == 0)
watchdog_action = WDT_NONE;
else
return -1;
return 0;
}
/* This actually performs the "action" once a watchdog has expired,
* ie. reboot, shutdown, exit, etc.
*/
void watchdog_perform_action(void)
{
switch(watchdog_action) {
case WDT_RESET: /* same as 'system_reset' in monitor */
qemu_system_reset_request();
break;
case WDT_SHUTDOWN: /* same as 'system_powerdown' in monitor */
qemu_system_powerdown_request();
break;
case WDT_POWEROFF: /* same as 'quit' command in monitor */
exit(0);
break;
case WDT_PAUSE: /* same as 'stop' command in monitor */
vm_stop(0);
break;
case WDT_DEBUG:
fprintf(stderr, "watchdog: timer fired\n");
break;
case WDT_NONE:
break;
}
}
void watchdog_pc_init(PCIBus *pci_bus)
{
if (watchdog)
watchdog->wdt_pc_init(pci_bus);
}
void register_watchdogs(void)
{
wdt_ib700_init();
wdt_i6300esb_init();
}

65
hw/watchdog.h Normal file
View File

@ -0,0 +1,65 @@
/*
* Virtual hardware watchdog.
*
* Copyright (C) 2009 Red Hat Inc.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* By Richard W.M. Jones (rjones@redhat.com).
*/
#ifndef QEMU_WATCHDOG_H
#define QEMU_WATCHDOG_H
extern void wdt_i6300esb_init(void);
extern void wdt_ib700_init(void);
/* Possible values for action parameter. */
#define WDT_RESET 1 /* Hard reset. */
#define WDT_SHUTDOWN 2 /* Shutdown. */
#define WDT_POWEROFF 3 /* Quit. */
#define WDT_PAUSE 4 /* Pause. */
#define WDT_DEBUG 5 /* Prints a message and continues running. */
#define WDT_NONE 6 /* Do nothing. */
struct WatchdogTimerModel {
LIST_ENTRY(WatchdogTimerModel) entry;
/* Short name of the device - used to select it on the command line. */
const char *wdt_name;
/* Longer description (eg. manufacturer and full model number). */
const char *wdt_description;
/* This callback should create/register the device. It is called
* indirectly from hw/pc.c when the virtual PC is being set up.
*/
void (*wdt_pc_init)(PCIBus *pci_bus);
};
typedef struct WatchdogTimerModel WatchdogTimerModel;
/* in vl.c */
extern WatchdogTimerModel *watchdog;
extern int watchdog_action;
/* in hw/watchdog.c */
extern int select_watchdog(const char *p);
extern int select_watchdog_action(const char *action);
extern void watchdog_add_model(WatchdogTimerModel *model);
extern void watchdog_perform_action(void);
extern void watchdog_pc_init(PCIBus *pci_bus);
extern void register_watchdogs(void);
#endif /* QEMU_WATCHDOG_H */

470
hw/wdt_i6300esb.c Normal file
View File

@ -0,0 +1,470 @@
/*
* Virtual hardware watchdog.
*
* Copyright (C) 2009 Red Hat Inc.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* By Richard W.M. Jones (rjones@redhat.com).
*/
#include <inttypes.h>
#include "qemu-common.h"
#include "qemu-timer.h"
#include "watchdog.h"
#include "hw.h"
#include "isa.h"
#include "pc.h"
#include "pci.h"
/*#define I6300ESB_DEBUG 1*/
#ifdef I6300ESB_DEBUG
#define i6300esb_debug(fs,...) \
fprintf(stderr,"i6300esb: %s: "fs,__func__,##__VA_ARGS__)
#else
#define i6300esb_debug(fs,...)
#endif
#ifndef PCI_DEVICE_ID_INTEL_ESB_9
#define PCI_DEVICE_ID_INTEL_ESB_9 0x25ab
#endif
/* PCI configuration registers */
#define ESB_CONFIG_REG 0x60 /* Config register */
#define ESB_LOCK_REG 0x68 /* WDT lock register */
/* Memory mapped registers (offset from base address) */
#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */
#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */
#define ESB_GINTSR_REG 0x08 /* General Interrupt Status Register */
#define ESB_RELOAD_REG 0x0c /* Reload register */
/* Lock register bits */
#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
/* Config register bits */
#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
#define ESB_WDT_INTTYPE (0x11 << 0) /* Interrupt type on timer1 timeout */
/* Reload register bits */
#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
/* Magic constants */
#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
/* Device state. */
struct I6300State {
PCIDevice dev; /* PCI device state, must be first field. */
int reboot_enabled; /* "Reboot" on timer expiry. The real action
* performed depends on the -watchdog-action
* param passed on QEMU command line.
*/
int clock_scale; /* Clock scale. */
#define CLOCK_SCALE_1KHZ 0
#define CLOCK_SCALE_1MHZ 1
int int_type; /* Interrupt type generated. */
#define INT_TYPE_IRQ 0 /* APIC 1, INT 10 */
#define INT_TYPE_SMI 2
#define INT_TYPE_DISABLED 3
int free_run; /* If true, reload timer on expiry. */
int locked; /* If true, enabled field cannot be changed. */
int enabled; /* If true, watchdog is enabled. */
QEMUTimer *timer; /* The actual watchdog timer. */
uint32_t timer1_preload; /* Values preloaded into timer1, timer2. */
uint32_t timer2_preload;
int stage; /* Stage (1 or 2). */
int unlock_state; /* Guest writes 0x80, 0x86 to unlock the
* registers, and we transition through
* states 0 -> 1 -> 2 when this happens.
*/
int previous_reboot_flag; /* If the watchdog caused the previous
* reboot, this flag will be set.
*/
};
typedef struct I6300State I6300State;
/* This function is called when the watchdog has either been enabled
* (hence it starts counting down) or has been keep-alived.
*/
static void i6300esb_restart_timer(I6300State *d, int stage)
{
int64_t timeout;
if (!d->enabled)
return;
d->stage = stage;
if (d->stage <= 1)
timeout = d->timer1_preload;
else
timeout = d->timer2_preload;
if (d->clock_scale == CLOCK_SCALE_1KHZ)
timeout <<= 15;
else
timeout <<= 5;
/* Get the timeout in units of ticks_per_sec. */
timeout = ticks_per_sec * timeout / 33000000;
i6300esb_debug("stage %d, timeout %" PRIi64 "\n", d->stage, timeout);
qemu_mod_timer(d->timer, qemu_get_clock(vm_clock) + timeout);
}
/* This is called when the guest disables the watchdog. */
static void i6300esb_disable_timer(I6300State *d)
{
i6300esb_debug("timer disabled\n");
qemu_del_timer(d->timer);
}
static void i6300esb_reset(I6300State *d)
{
/* XXX We should probably reset other parts of the state here,
* but we should also reset our state on general machine reset
* too. For now just disable the timer so it doesn't fire
* again after the reboot.
*/
i6300esb_disable_timer(d);
}
/* This function is called when the watchdog expires. Note that
* the hardware has two timers, and so expiry happens in two stages.
* If d->stage == 1 then we perform the first stage action (usually,
* sending an interrupt) and then restart the timer again for the
* second stage. If the second stage expires then the watchdog
* really has run out.
*/
static void i6300esb_timer_expired(void *vp)
{
I6300State *d = (I6300State *) vp;
i6300esb_debug("stage %d\n", d->stage);
if (d->stage == 1) {
/* What to do at the end of stage 1? */
switch (d->int_type) {
case INT_TYPE_IRQ:
fprintf(stderr, "i6300esb_timer_expired: I would send APIC 1 INT 10 here if I knew how (XXX)\n");
break;
case INT_TYPE_SMI:
fprintf(stderr, "i6300esb_timer_expired: I would send SMI here if I knew how (XXX)\n");
break;
}
/* Start the second stage. */
i6300esb_restart_timer(d, 2);
} else {
/* Second stage expired, reboot for real. */
if (d->reboot_enabled) {
d->previous_reboot_flag = 1;
watchdog_perform_action(); /* This reboots, exits, etc */
i6300esb_reset(d);
}
/* In "free running mode" we start stage 1 again. */
if (d->free_run)
i6300esb_restart_timer(d, 1);
}
}
static void i6300esb_config_write(PCIDevice *dev, uint32_t addr,
uint32_t data, int len)
{
I6300State *d = (I6300State *) dev;
int old;
i6300esb_debug("addr = %x, data = %x, len = %d\n", addr, data, len);
if (addr == ESB_CONFIG_REG && len == 2) {
d->reboot_enabled = (data & ESB_WDT_REBOOT) == 0;
d->clock_scale =
(data & ESB_WDT_FREQ) != 0 ? CLOCK_SCALE_1MHZ : CLOCK_SCALE_1KHZ;
d->int_type = (data & ESB_WDT_INTTYPE);
} else if (addr == ESB_LOCK_REG && len == 1) {
if (!d->locked) {
d->locked = (data & ESB_WDT_LOCK) != 0;
d->free_run = (data & ESB_WDT_FUNC) != 0;
old = d->enabled;
d->enabled = (data & ESB_WDT_ENABLE) != 0;
if (!old && d->enabled) /* Enabled transitioned from 0 -> 1 */
i6300esb_restart_timer(d, 1);
else if (!d->enabled)
i6300esb_disable_timer(d);
}
} else {
pci_default_write_config(dev, addr, data, len);
}
}
static uint32_t i6300esb_config_read(PCIDevice *dev, uint32_t addr, int len)
{
I6300State *d = (I6300State *) dev;
uint32_t data;
i6300esb_debug ("addr = %x, len = %d\n", addr, len);
if (addr == ESB_CONFIG_REG && len == 2) {
data =
(d->reboot_enabled ? 0 : ESB_WDT_REBOOT) |
(d->clock_scale == CLOCK_SCALE_1MHZ ? ESB_WDT_FREQ : 0) |
d->int_type;
return data;
} else if (addr == ESB_LOCK_REG && len == 1) {
data =
(d->free_run ? ESB_WDT_FUNC : 0) |
(d->locked ? ESB_WDT_LOCK : 0) |
(d->enabled ? ESB_WDT_ENABLE : 0);
return data;
} else {
return pci_default_read_config(dev, addr, len);
}
}
static uint32_t i6300esb_mem_readb(void *vp, target_phys_addr_t addr)
{
i6300esb_debug ("addr = %x\n", (int) addr);
return 0;
}
static uint32_t i6300esb_mem_readw(void *vp, target_phys_addr_t addr)
{
uint32_t data = 0;
I6300State *d = (I6300State *) vp;
i6300esb_debug("addr = %x\n", (int) addr);
if (addr == 0xc) {
/* The previous reboot flag is really bit 9, but there is
* a bug in the Linux driver where it thinks it's bit 12.
* Set both.
*/
data = d->previous_reboot_flag ? 0x1200 : 0;
}
return data;
}
static uint32_t i6300esb_mem_readl(void *vp, target_phys_addr_t addr)
{
i6300esb_debug("addr = %x\n", (int) addr);
return 0;
}
static void i6300esb_mem_writeb(void *vp, target_phys_addr_t addr, uint32_t val)
{
I6300State *d = (I6300State *) vp;
i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
if (addr == 0xc && val == 0x80)
d->unlock_state = 1;
else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
d->unlock_state = 2;
}
static void i6300esb_mem_writew(void *vp, target_phys_addr_t addr, uint32_t val)
{
I6300State *d = (I6300State *) vp;
i6300esb_debug("addr = %x, val = %x\n", (int) addr, val);
if (addr == 0xc && val == 0x80)
d->unlock_state = 1;
else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
d->unlock_state = 2;
else {
if (d->unlock_state == 2) {
if (addr == 0xc) {
if ((val & 0x100) != 0)
/* This is the "ping" from the userspace watchdog in
* the guest ...
*/
i6300esb_restart_timer(d, 1);
/* Setting bit 9 resets the previous reboot flag.
* There's a bug in the Linux driver where it sets
* bit 12 instead.
*/
if ((val & 0x200) != 0 || (val & 0x1000) != 0) {
d->previous_reboot_flag = 0;
}
}
d->unlock_state = 0;
}
}
}
static void i6300esb_mem_writel(void *vp, target_phys_addr_t addr, uint32_t val)
{
I6300State *d = (I6300State *) vp;
i6300esb_debug ("addr = %x, val = %x\n", (int) addr, val);
if (addr == 0xc && val == 0x80)
d->unlock_state = 1;
else if (addr == 0xc && val == 0x86 && d->unlock_state == 1)
d->unlock_state = 2;
else {
if (d->unlock_state == 2) {
if (addr == 0)
d->timer1_preload = val & 0xfffff;
else if (addr == 4)
d->timer2_preload = val & 0xfffff;
d->unlock_state = 0;
}
}
}
static void i6300esb_map(PCIDevice *dev, int region_num,
uint32_t addr, uint32_t size, int type)
{
static CPUReadMemoryFunc *mem_read[3] = {
i6300esb_mem_readb,
i6300esb_mem_readw,
i6300esb_mem_readl,
};
static CPUWriteMemoryFunc *mem_write[3] = {
i6300esb_mem_writeb,
i6300esb_mem_writew,
i6300esb_mem_writel,
};
I6300State *d = (I6300State *) dev;
int io_mem;
i6300esb_debug("addr = %x, size = %x, type = %d\n", addr, size, type);
io_mem = cpu_register_io_memory (0, mem_read, mem_write, d);
cpu_register_physical_memory (addr, 0x10, io_mem);
/* qemu_register_coalesced_mmio (addr, 0x10); ? */
}
static void i6300esb_save(QEMUFile *f, void *vp)
{
I6300State *d = (I6300State *) vp;
pci_device_save(&d->dev, f);
qemu_put_be32(f, d->reboot_enabled);
qemu_put_be32(f, d->clock_scale);
qemu_put_be32(f, d->int_type);
qemu_put_be32(f, d->free_run);
qemu_put_be32(f, d->locked);
qemu_put_be32(f, d->enabled);
qemu_put_timer(f, d->timer);
qemu_put_be32(f, d->timer1_preload);
qemu_put_be32(f, d->timer2_preload);
qemu_put_be32(f, d->stage);
qemu_put_be32(f, d->unlock_state);
qemu_put_be32(f, d->previous_reboot_flag);
}
static int i6300esb_load(QEMUFile *f, void *vp, int version)
{
I6300State *d = (I6300State *) vp;
if (version != sizeof (I6300State))
return -EINVAL;
pci_device_load(&d->dev, f);
d->reboot_enabled = qemu_get_be32(f);
d->clock_scale = qemu_get_be32(f);
d->int_type = qemu_get_be32(f);
d->free_run = qemu_get_be32(f);
d->locked = qemu_get_be32(f);
d->enabled = qemu_get_be32(f);
qemu_get_timer(f, d->timer);
d->timer1_preload = qemu_get_be32(f);
d->timer2_preload = qemu_get_be32(f);
d->stage = qemu_get_be32(f);
d->unlock_state = qemu_get_be32(f);
d->previous_reboot_flag = qemu_get_be32(f);
return 0;
}
/* Create and initialize a virtual Intel 6300ESB during PC creation. */
static void i6300esb_pc_init(PCIBus *pci_bus)
{
I6300State *d;
uint8_t *pci_conf;
if (!pci_bus) {
fprintf(stderr, "wdt_i6300esb: no PCI bus in this machine\n");
return;
}
d = (I6300State *)
pci_register_device (pci_bus, "i6300esb_wdt", sizeof (I6300State),
-1,
i6300esb_config_read, i6300esb_config_write);
d->reboot_enabled = 1;
d->clock_scale = CLOCK_SCALE_1KHZ;
d->int_type = INT_TYPE_IRQ;
d->free_run = 0;
d->locked = 0;
d->enabled = 0;
d->timer = qemu_new_timer(vm_clock, i6300esb_timer_expired, d);
d->timer1_preload = 0xfffff;
d->timer2_preload = 0xfffff;
d->stage = 1;
d->unlock_state = 0;
d->previous_reboot_flag = 0;
pci_conf = d->dev.config;
pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_ESB_9);
pci_config_set_class(pci_conf, PCI_CLASS_SYSTEM_OTHER);
pci_conf[0x0e] = 0x00;
pci_register_io_region(&d->dev, 0, 0x10,
PCI_ADDRESS_SPACE_MEM, i6300esb_map);
register_savevm("i6300esb_wdt", -1, sizeof(I6300State),
i6300esb_save, i6300esb_load, d);
}
static WatchdogTimerModel model = {
.wdt_name = "i6300esb",
.wdt_description = "Intel 6300ESB",
.wdt_pc_init = i6300esb_pc_init,
};
void wdt_i6300esb_init(void)
{
watchdog_add_model(&model);
}

112
hw/wdt_ib700.c Normal file
View File

@ -0,0 +1,112 @@
/*
* Virtual hardware watchdog.
*
* Copyright (C) 2009 Red Hat Inc.
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* By Richard W.M. Jones (rjones@redhat.com).
*/
#include "qemu-common.h"
#include "qemu-timer.h"
#include "watchdog.h"
#include "hw.h"
#include "isa.h"
#include "pc.h"
/*#define IB700_DEBUG 1*/
#ifdef IB700_DEBUG
#define ib700_debug(fs,...) \
fprintf(stderr,"ib700: %s: "fs,__func__,##__VA_ARGS__)
#else
#define ib700_debug(fs,...)
#endif
/* This is the timer. We use a global here because the watchdog
* code ensures there is only one watchdog (it is located at a fixed,
* unchangable IO port, so there could only ever be one anyway).
*/
static QEMUTimer *timer = NULL;
/* A write to this register enables the timer. */
static void ib700_write_enable_reg(void *vp, uint32_t addr, uint32_t data)
{
static int time_map[] = {
30, 28, 26, 24, 22, 20, 18, 16,
14, 12, 10, 8, 6, 4, 2, 0
};
int64 timeout;
ib700_debug("addr = %x, data = %x\n", addr, data);
timeout = (int64_t) time_map[data & 0xF] * ticks_per_sec;
qemu_mod_timer(timer, qemu_get_clock (vm_clock) + timeout);
}
/* A write (of any value) to this register disables the timer. */
static void ib700_write_disable_reg(void *vp, uint32_t addr, uint32_t data)
{
ib700_debug("addr = %x, data = %x\n", addr, data);
qemu_del_timer(timer);
}
/* This is called when the watchdog expires. */
static void ib700_timer_expired(void *vp)
{
ib700_debug("watchdog expired\n");
watchdog_perform_action();
qemu_del_timer(timer);
}
static void ib700_save(QEMUFile *f, void *vp)
{
qemu_put_timer(f, timer);
}
static int ib700_load(QEMUFile *f, void *vp, int version)
{
if (version != 0)
return -EINVAL;
qemu_get_timer(f, timer);
return 0;
}
/* Create and initialize a virtual IB700 during PC creation. */
static void ib700_pc_init(PCIBus *unused)
{
register_savevm("ib700_wdt", -1, 0, ib700_save, ib700_load, NULL);
register_ioport_write(0x441, 2, 1, ib700_write_disable_reg, NULL);
register_ioport_write(0x443, 2, 1, ib700_write_enable_reg, NULL);
}
static WatchdogTimerModel model = {
.wdt_name = "ib700",
.wdt_description = "iBASE 700",
.wdt_pc_init = ib700_pc_init,
};
void wdt_ib700_init(void)
{
watchdog_add_model(&model);
timer = qemu_new_timer(vm_clock, ib700_timer_expired, NULL);
}

View File

@ -27,6 +27,7 @@
#include "hw/pcmcia.h" #include "hw/pcmcia.h"
#include "hw/pc.h" #include "hw/pc.h"
#include "hw/pci.h" #include "hw/pci.h"
#include "hw/watchdog.h"
#include "gdbstub.h" #include "gdbstub.h"
#include "net.h" #include "net.h"
#include "qemu-char.h" #include "qemu-char.h"
@ -597,6 +598,13 @@ static void do_gdbserver(Monitor *mon, const char *device)
} }
#endif #endif
static void do_watchdog_action(Monitor *mon, const char *action)
{
if (select_watchdog_action(action) == -1) {
monitor_printf(mon, "Unknown watchdog action '%s'\n", action);
}
}
static void monitor_printc(Monitor *mon, int c) static void monitor_printc(Monitor *mon, int c)
{ {
monitor_printf(mon, "'"); monitor_printf(mon, "'");
@ -1762,6 +1770,8 @@ static const mon_cmd_t mon_cmds[] = {
"target", "request VM to change it's memory allocation (in MB)" }, "target", "request VM to change it's memory allocation (in MB)" },
{ "set_link", "ss", do_set_link, { "set_link", "ss", do_set_link,
"name up|down", "change the link status of a network adapter" }, "name up|down", "change the link status of a network adapter" },
{ "watchdog_action", "s", do_watchdog_action,
"[reset|shutdown|poweroff|pause|debug|none]", "change watchdog action" },
{ "acl", "sss?i?", do_acl, "<command> <aclname> [<match> [<index>]]\n", { "acl", "sss?i?", do_acl, "<command> <aclname> [<match> [<index>]]\n",
"acl show vnc.username\n" "acl show vnc.username\n"
"acl policy vnc.username deny\n" "acl policy vnc.username deny\n"

View File

@ -1448,6 +1448,55 @@ order cores with complex cache hierarchies. The number of instructions
executed often has little or no correlation with actual performance. executed often has little or no correlation with actual performance.
ETEXI ETEXI
DEF("watchdog", HAS_ARG, QEMU_OPTION_watchdog, \
"-watchdog i6300esb|ib700\n" \
" enable virtual hardware watchdog [default=none]\n")
STEXI
@item -watchdog @var{model}
Create a virtual hardware watchdog device. Once enabled (by a guest
action), the watchdog must be periodically polled by an agent inside
the guest or else the guest will be restarted.
The @var{model} is the model of hardware watchdog to emulate. Choices
for model are: @code{ib700} (iBASE 700) which is a very simple ISA
watchdog with a single timer, or @code{i6300esb} (Intel 6300ESB I/O
controller hub) which is a much more featureful PCI-based dual-timer
watchdog. Choose a model for which your guest has drivers.
Use @code{-watchdog ?} to list available hardware models. Only one
watchdog can be enabled for a guest.
ETEXI
DEF("watchdog-action", HAS_ARG, QEMU_OPTION_watchdog_action, \
"-watchdog-action reset|shutdown|poweroff|pause|debug|none\n" \
" action when watchdog fires [default=reset]\n")
STEXI
@item -watchdog-action @var{action}
The @var{action} controls what QEMU will do when the watchdog timer
expires.
The default is
@code{reset} (forcefully reset the guest).
Other possible actions are:
@code{shutdown} (attempt to gracefully shutdown the guest),
@code{poweroff} (forcefully poweroff the guest),
@code{pause} (pause the guest),
@code{debug} (print a debug message and continue), or
@code{none} (do nothing).
Note that the @code{shutdown} action requires that the guest responds
to ACPI signals, which it may not be able to do in the sort of
situations where the watchdog would have expired, and thus
@code{-watchdog-action shutdown} is not recommended for production use.
Examples:
@table @code
@item -watchdog i6300esb -watchdog-action pause
@item -watchdog ib700
@end table
ETEXI
DEF("echr", HAS_ARG, QEMU_OPTION_echr, \ DEF("echr", HAS_ARG, QEMU_OPTION_echr, \
"-echr chr set terminal escape character instead of ctrl-a\n") "-echr chr set terminal escape character instead of ctrl-a\n")
STEXI STEXI

16
vl.c
View File

@ -138,6 +138,7 @@ int main(int argc, char **argv)
#include "hw/isa.h" #include "hw/isa.h"
#include "hw/baum.h" #include "hw/baum.h"
#include "hw/bt.h" #include "hw/bt.h"
#include "hw/watchdog.h"
#include "hw/smbios.h" #include "hw/smbios.h"
#include "hw/xen.h" #include "hw/xen.h"
#include "bt-host.h" #include "bt-host.h"
@ -252,6 +253,8 @@ int graphic_rotate = 0;
#ifndef _WIN32 #ifndef _WIN32
int daemonize = 0; int daemonize = 0;
#endif #endif
WatchdogTimerModel *watchdog = NULL;
int watchdog_action = WDT_RESET;
const char *option_rom[MAX_OPTION_ROMS]; const char *option_rom[MAX_OPTION_ROMS];
int nb_option_roms; int nb_option_roms;
int semihosting_enabled = 0; int semihosting_enabled = 0;
@ -4917,6 +4920,8 @@ int main(int argc, char **argv, char **envp)
tb_size = 0; tb_size = 0;
autostart= 1; autostart= 1;
register_watchdogs();
optind = 1; optind = 1;
for(;;) { for(;;) {
if (optind >= argc) if (optind >= argc)
@ -5308,6 +5313,17 @@ int main(int argc, char **argv, char **envp)
serial_devices[serial_device_index] = optarg; serial_devices[serial_device_index] = optarg;
serial_device_index++; serial_device_index++;
break; break;
case QEMU_OPTION_watchdog:
i = select_watchdog(optarg);
if (i > 0)
exit (i == 1 ? 1 : 0);
break;
case QEMU_OPTION_watchdog_action:
if (select_watchdog_action(optarg) == -1) {
fprintf(stderr, "Unknown -watchdog-action parameter\n");
exit(1);
}
break;
case QEMU_OPTION_virtiocon: case QEMU_OPTION_virtiocon:
if (virtio_console_index >= MAX_VIRTIO_CONSOLES) { if (virtio_console_index >= MAX_VIRTIO_CONSOLES) {
fprintf(stderr, "qemu: too many virtio consoles\n"); fprintf(stderr, "qemu: too many virtio consoles\n");