Index: Makefile.target =================================================================== --- Makefile.target (revision 6658) +++ Makefile.target (working copy) @@ -584,6 +584,7 @@ OBJS+= cirrus_vga.o apic.o parallel.o acpi.o piix_pci.o OBJS+= usb-uhci.o vmmouse.o vmport.o vmware_vga.o hpet.o OBJS += device-hotplug.o pci-hotplug.o +OBJS+= wdt_ib700.o wdt_i6300esb.o CPPFLAGS += -DHAS_AUDIO -DHAS_AUDIO_CHOICE endif ifeq ($(TARGET_BASE_ARCH), ppc) Index: vl.c =================================================================== --- vl.c (revision 6658) +++ vl.c (working copy) @@ -42,6 +42,7 @@ #include "migration.h" #include "kvm.h" #include "balloon.h" +#include "watchdog.h" #include #include @@ -4075,6 +4076,8 @@ "-old-param old param mode\n" #endif "-tb-size n set TB size\n" + "-watchdog ib700|i6300esb[,action=reset|shutdown|poweroff|pause]\n" + " enable virtual hardware watchdog [default=none]\n" "-incoming p prepare for incoming migration, listen on port p\n" #ifndef _WIN32 "-chroot dir Chroot to dir just before starting the VM.\n" @@ -4191,6 +4194,7 @@ QEMU_OPTION_startdate, QEMU_OPTION_icount, QEMU_OPTION_echr, + QEMU_OPTION_watchdog, QEMU_OPTION_virtiocon, QEMU_OPTION_show_cursor, QEMU_OPTION_semihosting, @@ -4319,6 +4323,7 @@ { "startdate", HAS_ARG, QEMU_OPTION_startdate }, { "icount", HAS_ARG, QEMU_OPTION_icount }, { "echr", HAS_ARG, QEMU_OPTION_echr }, + { "watchdog", HAS_ARG, QEMU_OPTION_watchdog }, { "virtioconsole", HAS_ARG, QEMU_OPTION_virtiocon }, { "show-cursor", 0, QEMU_OPTION_show_cursor }, #if defined(TARGET_ARM) || defined(TARGET_M68K) @@ -5098,6 +5103,14 @@ serial_devices[serial_device_index] = optarg; serial_device_index++; break; + case QEMU_OPTION_watchdog: + if (wdt_option) { + fprintf (stderr, + "qemu: only one watchdog option may be given\n"); + exit (1); + } + wdt_option = optarg; + break; case QEMU_OPTION_virtiocon: if (virtio_console_index >= MAX_VIRTIO_CONSOLES) { fprintf(stderr, "qemu: too many virtio consoles\n"); Index: Makefile =================================================================== --- Makefile (revision 6658) +++ Makefile (working copy) @@ -85,6 +85,7 @@ OBJS+=bt.o bt-host.o bt-vhci.o bt-l2cap.o bt-sdp.o bt-hci.o bt-hid.o usb-bt.o OBJS+=buffered_file.o migration.o migration-tcp.o net.o qemu-sockets.o OBJS+=qemu-char.o aio.o net-checksum.o savevm.o cache-utils.o +OBJS+=watchdog.o ifdef CONFIG_BRLAPI OBJS+= baum.o Index: watchdog.c =================================================================== --- watchdog.c (revision 0) +++ watchdog.c (revision 0) @@ -0,0 +1,161 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#include "qemu-common.h" +#include "sysemu.h" +#include "hw/wdt_ib700.h" +#include "hw/wdt_i6300esb.h" +#include "watchdog.h" + +/* Possible values for action parameter. */ +#define WDT_RESET 1 +#define WDT_SHUTDOWN 2 +#define WDT_POWEROFF 3 +#define WDT_PAUSE 4 + +static void parse_option (const char *arg, int *action_r); +static void parse_rest (const char *rest, int *action_r); + +/* Linked list of models - virtual watchdog devices add themselves + * to this list. + */ +wdt_model *wdt_models = NULL; + +/* The raw -watchdog option specified on the command line. */ +const char *wdt_option = NULL; + +/* Currently enabled watchdog device. Only one may be enabled from + * the command line. + */ +static wdt_model *wdt = NULL; + +/* Called from the PC code to parse the option and finally configure + * the device. + */ +void +watchdog_pc_init (PCIBus *pci_bus) +{ + int action; + + if (!wdt_option) return; + parse_option (wdt_option, &action); + if (!wdt) return; /* No watchdog configured. */ + wdt->wdt_methods->wdt_pc_init (pci_bus, action); +} + +/* This actually performs the "action" once a watchdog has expired, + * ie. reboot, shutdown, exit, etc. + */ +void +watchdog_perform_action (int action) +{ + switch (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; + } +} + +/* This function parses the command line parameter of the form: + * <>[,action=<>] + */ +static void +parse_option (const char *arg, int *action_r) +{ + wdt_model *p; + int len; + + if (wdt) { + fprintf (stderr, "Only one -watchdog device can be enabled\n"); + exit (1); + } + + /* -watchdog ? lists available devices and exits cleanly. */ + if (strcmp (arg, "?") == 0) { + for (p = wdt_models; p; p = p->wdt_next) { + fprintf (stderr, "\t%s\t%s\n", + p->wdt_name, p->wdt_description); + } + exit (0); + } + + for (p = wdt_models; p; p = p->wdt_next) { + len = strlen (p->wdt_name); + if (strncasecmp (arg, p->wdt_name, len) == 0 && + (arg[len] == '\0' || arg[len] == ',')) { + parse_rest (&arg[len], action_r); + if (p->wdt_methods->wdt_option) + p->wdt_methods->wdt_option (&arg[len]); + wdt = p; + return; + } + } + + fprintf (stderr, "Unknown -watchdog device. Supported devices are:\n"); + for (p = wdt_models; p; p = p->wdt_next) { + fprintf (stderr, "\t%s\t%s\n", + p->wdt_name, p->wdt_description); + } + exit (1); +} + +/* Parse the remainder of the command line parameter, either: + * ,action=<> + * or empty string. For forwards compatibility, ignore other + * parameters which may appear in the string. + */ +static void +parse_rest (const char *arg, int *action_r) +{ + char buf[64]; + + *action_r = WDT_RESET; /* Default action. */ + if (*arg == '\0') return; + if (*arg == ',') arg++; + + if (get_param_value (buf, sizeof (buf), "action", arg)) { + if (strcasecmp (buf, "reset") == 0) + *action_r = WDT_RESET; + else if (strcasecmp (buf, "shutdown") == 0) + *action_r = WDT_SHUTDOWN; + else if (strcasecmp (buf, "poweroff") == 0) + *action_r = WDT_POWEROFF; + else if (strcasecmp (buf, "pause") == 0) + *action_r = WDT_PAUSE; + else { + fprintf (stderr, "Unknown -watchdog action parameter: %s\n", buf); + exit (1); + } + } +} Index: qemu-doc.texi =================================================================== --- qemu-doc.texi (revision 6658) +++ qemu-doc.texi (working copy) @@ -1161,6 +1161,38 @@ @item -echr 20 @end table address@hidden -watchdog @var{model}[,address@hidden +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. + +The @var{action} controls what QEMU will do when the timer expires. +The default is address@hidden (forcefully reset the guest). +Other possible actions are: address@hidden (attempt to gracefully shutdown the guest), address@hidden (forcefully poweroff the guest), or address@hidden (pause the guest). + +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 address@hidden is not recommended for production use. + +Use @code{-watchdog ?} to list available hardware models. Only one +watchdog can be enabled for a guest. + address@hidden @code address@hidden -watchdog i6300esb,action=pause address@hidden -watchdog ib700 address@hidden table + @item -chroot dir Immediately before starting guest execution, chroot to the specified directory. Especially useful in combination with -runas. Index: watchdog.h =================================================================== --- watchdog.h (revision 0) +++ watchdog.h (revision 0) @@ -0,0 +1,56 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#ifndef _QEMU_WATCHDOG_H +#define _QEMU_WATCHDOG_H + +extern const char *wdt_option; + +extern void watchdog_pc_init (PCIBus *pci_bus); +extern void watchdog_perform_action (int action); + +struct wdt_methods { + /* If further option parsing is needed, do it here. */ + void (*wdt_option) (const char *arg); + /* 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, int action); +}; +typedef struct wdt_methods wdt_methods; + +struct wdt_model { + struct wdt_model *wdt_next; + + /* 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; + /* Callbacks for this device. */ + wdt_methods *wdt_methods; +}; +typedef struct wdt_model wdt_model; + +/* Watchdog virtual devices add themselves to this linked list. */ +extern wdt_model *wdt_models; + +#endif /* _QEMU_WATCHDOG_H */ Index: hw/wdt_i6300esb.c =================================================================== --- hw/wdt_i6300esb.c (revision 0) +++ hw/wdt_i6300esb.c (revision 0) @@ -0,0 +1,538 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#include + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "watchdog.h" +#include "wdt_i6300esb.h" +#include "hw.h" +#include "isa.h" +#include "pc.h" +#include "pci.h" + +/*#define I6300ESB_DEBUG 1*/ + +#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 state { + PCIDevice dev; /* PCI device state, must be first field. */ + + int action; /* Action on expiry. */ + + int reboot_enabled; /* "Reboot" on timer expiry. The real action + * performed depends on the 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 state state; + +static void i6300esb_pc_init (PCIBus *pci_bus, int action); +static void i6300esb_map (PCIDevice *dev, int region_num, uint32_t addr, uint32_t size, int type); +static void i6300esb_config_write (PCIDevice *dev, uint32_t addr, uint32_t data, int len); +static uint32_t i6300esb_config_read (PCIDevice *dev, uint32_t addr, int len); +static uint32_t i6300esb_mem_readb (void *vp, target_phys_addr_t addr); +static uint32_t i6300esb_mem_readw (void *vp, target_phys_addr_t addr); +static uint32_t i6300esb_mem_readl (void *vp, target_phys_addr_t addr); +static void i6300esb_mem_writeb (void *vp, target_phys_addr_t addr, uint32_t val); +static void i6300esb_mem_writew (void *vp, target_phys_addr_t addr, uint32_t val); +static void i6300esb_mem_writel (void *vp, target_phys_addr_t addr, uint32_t val); +static void i6300esb_restart_timer (state *, int stage); +static void i6300esb_disable_timer (state *); +static void i6300esb_timer_expired (void *vp); +static void i6300esb_reset (state *d); +static void i6300esb_save (QEMUFile *f, void *vp); +static int i6300esb_load (QEMUFile *f, void *vp, int version); + +static wdt_methods i6300esb_wdt = { + .wdt_pc_init = i6300esb_pc_init, +}; + +static wdt_model model = { + .wdt_name = "i6300esb", + .wdt_description = "Intel 6300ESB", + .wdt_methods = &i6300esb_wdt, +}; + +void +wdt_i6300esb_init (void) +{ + model.wdt_next = wdt_models; + wdt_models = &model; +} + +/* Create and initialize a virtual Intel 6300ESB during PC creation. */ +static void +i6300esb_pc_init (PCIBus *pci_bus, int action) +{ + state *d; + uint8_t *pci_conf; + + if (!pci_bus) { + fprintf (stderr, "wdt_i6300esb: no PCI bus in this machine\n"); + return; + } + + d = (state *) + pci_register_device (pci_bus, "i6300esb_wdt", sizeof (state), + -1, + i6300esb_config_read, i6300esb_config_write); + + d->action = action; + 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 (state), + i6300esb_save, i6300esb_load, d); +} + +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, + }; + state *d = (state *) dev; + int io_mem; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_map: addr = %x, size = %x, type = %d\n", + addr, size, type); +#endif + + 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_config_write (PCIDevice *dev, uint32_t addr, uint32_t data, int len) +{ + state *d = (state *) dev; + int old; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_config_write: addr = %x, data = %x, len = %d\n", + addr, data, len); +#endif + + 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) +{ + state *d = (state *) dev; + uint32_t data; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_config_read: addr = %x, len = %d\n", + addr, len); +#endif + + 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) +{ +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_readb: addr = %x\n", + (int) addr); +#endif + + return 0; +} + +static uint32_t +i6300esb_mem_readw (void *vp, target_phys_addr_t addr) +{ + uint32_t data = 0; + state *d = (state *) vp; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_readw: addr = %x\n", + (int) addr); +#endif + + 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) +{ +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_readl: addr = %x\n", + (int) addr); +#endif + + return 0; +} + +static void +i6300esb_mem_writeb (void *vp, target_phys_addr_t addr, uint32_t val) +{ + state *d = (state *) vp; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_writeb: addr = %x, val = %x\n", + (int) addr, val); +#endif + + 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) +{ + state *d = (state *) vp; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_writew: addr = %x, val = %x\n", + (int) addr, val); +#endif + + 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) +{ + state *d = (state *) vp; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_mem_writel: addr = %x, val = %x\n", + (int) addr, val); +#endif + + 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; + } + } +} + +/* 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 (state *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; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_restart_timer: stage %d, timeout %" PRIi64 "\n", + d->stage, timeout); +#endif + + 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 (state *d) +{ +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_disable_timer: timer disabled\n"); +#endif + + qemu_del_timer (d->timer); +} + +/* 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) +{ + state *d = (state *) vp; + +#ifdef I6300ESB_DEBUG + fprintf (stderr, "i6300esb_timer_expired: stage %d\n", d->stage); +#endif + + 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 (d->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_reset (state *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); +} + +static void +i6300esb_save (QEMUFile *f, void *vp) +{ + state *d = (state *) vp; + + pci_device_save (&d->dev, f); + qemu_put_be32 (f, d->action); + 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) +{ + state *d = (state *) vp; + + if (version != sizeof (state)) return -EINVAL; + + pci_device_load (&d->dev, f); + d->action = qemu_get_be32 (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; +} Index: hw/wdt_i6300esb.h =================================================================== --- hw/wdt_i6300esb.h (revision 0) +++ hw/wdt_i6300esb.h (revision 0) @@ -0,0 +1,28 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#ifndef _QEMU_WDT_I6300ESB_H +#define _QEMU_WDT_I6300ESB_H + +extern void wdt_i6300esb_init (void); + +#endif /* _QEMU_WDT_I6300ESB_H */ Index: hw/wdt_ib700.c =================================================================== --- hw/wdt_ib700.c (revision 0) +++ hw/wdt_ib700.c (revision 0) @@ -0,0 +1,137 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "watchdog.h" +#include "wdt_ib700.h" +#include "hw.h" +#include "isa.h" +#include "pc.h" + +/*#define IB700_DEBUG 1*/ + +static void ib700_pc_init (PCIBus *unused, int action); +static void ib700_write_enable_reg (void *vp, uint32_t addr, uint32_t data); +static void ib700_write_disable_reg (void *vp, uint32_t addr, uint32_t data); +static void ib700_timer_expired (void *vp); +static void ib700_save (QEMUFile *f, void *vp); +static int ib700_load (QEMUFile *f, void *vp, int version); + +/* 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; +static int action = 0; + +static wdt_methods ib700_wdt = { + .wdt_pc_init = ib700_pc_init, +}; + +static wdt_model model = { + .wdt_name = "ib700", + .wdt_description = "iBASE 700", + .wdt_methods = &ib700_wdt, +}; + +void +wdt_ib700_init (void) +{ + model.wdt_next = wdt_models; + wdt_models = &model; + + timer = qemu_new_timer (vm_clock, ib700_timer_expired, NULL); +} + +/* Create and initialize a virtual IB700 during PC creation. */ +static void +ib700_pc_init (PCIBus *unused, int _action) +{ + 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); + + action = _action; +} + +/* 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; + +#ifdef IB700_DEBUG + fprintf (stderr, "ib700_write_enable_reg: addr = %x, data = %x\n", + addr, data); +#endif + + 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) +{ +#ifdef IB700_DEBUG + fprintf (stderr, "ib700_write_disable_reg: addr = %x, data = %x\n", + addr, data); +#endif + + qemu_del_timer (timer); +} + +/* This is called when the watchdog expires. */ +static void +ib700_timer_expired (void *vp) +{ +#ifdef IB700_DEBUG + fprintf (stderr, "ib700_timer_expired: watchdog expired\n"); +#endif + + watchdog_perform_action (action); + qemu_del_timer (timer); +} + +static void +ib700_save (QEMUFile *f, void *vp) +{ + qemu_put_timer (f, timer); + qemu_put_be32 (f, action); +} + +static int +ib700_load (QEMUFile *f, void *vp, int version) +{ + if (version != 0) return -EINVAL; + + qemu_get_timer (f, timer); + action = qemu_get_be32 (f); + + return 0; +} Index: hw/wdt_ib700.h =================================================================== --- hw/wdt_ib700.h (revision 0) +++ hw/wdt_ib700.h (revision 0) @@ -0,0 +1,28 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * By Richard W.M. Jones (address@hidden). + */ + +#ifndef _QEMU_WDT_IB700_H +#define _QEMU_WDT_IB700_H + +extern void wdt_ib700_init (void); + +#endif /* _QEMU_WDT_IB700_H */ Index: hw/pc.c =================================================================== --- hw/pc.c (revision 6658) +++ hw/pc.c (working copy) @@ -37,6 +37,9 @@ #include "virtio-balloon.h" #include "virtio-console.h" #include "hpet_emul.h" +#include "wdt_ib700.h" +#include "wdt_i6300esb.h" +#include "watchdog.h" /* output Bochs bios info messages */ //#define DEBUG_BIOS @@ -1006,6 +1009,10 @@ } } + wdt_ib700_init (); + wdt_i6300esb_init (); + watchdog_pc_init (pci_bus); + for(i = 0; i < nb_nics; i++) { NICInfo *nd = &nd_table[i];