qemu/hw/watchdog/spapr_watchdog.c
<<
>>
Prefs
   1/*
   2 * This library is free software; you can redistribute it and/or
   3 * modify it under the terms of the GNU Lesser General Public
   4 * License as published by the Free Software Foundation; either
   5 * version 2.1 of the License, or (at your option) any later version.
   6 *
   7 * This library is distributed in the hope that it will be useful,
   8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  10 * Lesser General Public License for more details.
  11 *
  12 * You should have received a copy of the GNU Lesser General Public
  13 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  14 */
  15
  16#include "qemu/osdep.h"
  17#include "qapi/error.h"
  18#include "target/ppc/cpu.h"
  19#include "migration/vmstate.h"
  20#include "trace.h"
  21
  22#include "hw/ppc/spapr.h"
  23
  24#define FIELD_BE(reg, field, start, len) \
  25    FIELD(reg, field, 64 - (start + len), len)
  26
  27/*
  28 * Bits 47: "leaveOtherWatchdogsRunningOnTimeout", specified on
  29 * the "Start watchdog" operation,
  30 * 0 - stop out-standing watchdogs on timeout,
  31 * 1 - leave outstanding watchdogs running on timeout
  32 */
  33FIELD_BE(PSERIES_WDTF, LEAVE_OTHER, 47, 1)
  34
  35/*    Bits 48-55: "operation" */
  36FIELD_BE(PSERIES_WDTF, OP, 48, 8)
  37#define PSERIES_WDTF_OP_START           0x1
  38#define PSERIES_WDTF_OP_STOP            0x2
  39#define PSERIES_WDTF_OP_QUERY           0x3
  40#define PSERIES_WDTF_OP_QUERY_LPM       0x4
  41
  42/*    Bits 56-63: "timeoutAction" */
  43FIELD_BE(PSERIES_WDTF, ACTION, 56, 8)
  44#define PSERIES_WDTF_ACTION_HARD_POWER_OFF  0x1
  45#define PSERIES_WDTF_ACTION_HARD_RESTART    0x2
  46#define PSERIES_WDTF_ACTION_DUMP_RESTART    0x3
  47
  48FIELD_BE(PSERIES_WDTF, RESERVED, 0, 47)
  49
  50/* Special watchdogNumber for the "stop all watchdogs" operation */
  51#define PSERIES_WDT_STOP_ALL            ((uint64_t)~0)
  52
  53/*
  54 * For the "Query watchdog capabilities" operation, a uint64 structure
  55 * defined as:
  56 * Bits 0-15: The minimum supported timeout in milliseconds
  57 * Bits 16-31: The number of watchdogs supported
  58 * Bits 32-63: Reserved
  59 */
  60FIELD_BE(PSERIES_WDTQ, MIN_TIMEOUT, 0, 16)
  61FIELD_BE(PSERIES_WDTQ, NUM, 16, 16)
  62
  63/*
  64 * For the "Query watchdog LPM requirement" operation:
  65 * 1 = The given "watchdogNumber" must be stopped prior to suspending
  66 * 2 = The given "watchdogNumber" does not have to be stopped prior to
  67 * suspending
  68 */
  69#define PSERIES_WDTQL_STOPPED               1
  70#define PSERIES_WDTQL_QUERY_NOT_STOPPED     2
  71
  72#define WDT_MIN_TIMEOUT 1 /* 1ms */
  73
  74static target_ulong watchdog_stop(unsigned watchdogNumber, SpaprWatchdog *w)
  75{
  76    target_ulong ret = H_NOOP;
  77
  78    if (timer_pending(&w->timer)) {
  79        timer_del(&w->timer);
  80        ret = H_SUCCESS;
  81    }
  82    trace_spapr_watchdog_stop(watchdogNumber, ret);
  83
  84    return ret;
  85}
  86
  87static target_ulong watchdog_stop_all(SpaprMachineState *spapr)
  88{
  89    target_ulong ret = H_NOOP;
  90    int i;
  91
  92    for (i = 1; i <= ARRAY_SIZE(spapr->wds); ++i) {
  93        target_ulong r = watchdog_stop(i, &spapr->wds[i - 1]);
  94
  95        if (r != H_NOOP && r != H_SUCCESS) {
  96            ret = r;
  97        }
  98    }
  99
 100    return ret;
 101}
 102
 103static void watchdog_expired(void *pw)
 104{
 105    SpaprWatchdog *w = pw;
 106    CPUState *cs;
 107    SpaprMachineState *spapr = SPAPR_MACHINE(qdev_get_machine());
 108    unsigned num = w - spapr->wds;
 109
 110    g_assert(num < ARRAY_SIZE(spapr->wds));
 111    trace_spapr_watchdog_expired(num, w->action);
 112    switch (w->action) {
 113    case PSERIES_WDTF_ACTION_HARD_POWER_OFF:
 114        qemu_system_vmstop_request(RUN_STATE_SHUTDOWN);
 115        break;
 116    case PSERIES_WDTF_ACTION_HARD_RESTART:
 117        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
 118        break;
 119    case PSERIES_WDTF_ACTION_DUMP_RESTART:
 120        CPU_FOREACH(cs) {
 121            async_run_on_cpu(cs, spapr_do_system_reset_on_cpu, RUN_ON_CPU_NULL);
 122        }
 123        break;
 124    }
 125    if (!w->leave_others) {
 126        watchdog_stop_all(spapr);
 127    }
 128}
 129
 130static target_ulong h_watchdog(PowerPCCPU *cpu,
 131                               SpaprMachineState *spapr,
 132                               target_ulong opcode, target_ulong *args)
 133{
 134    target_ulong ret = H_SUCCESS;
 135    target_ulong flags = args[0];
 136    target_ulong watchdogNumber = args[1]; /* 1-Based per PAPR */
 137    target_ulong timeoutInMs = args[2];
 138    unsigned operation = FIELD_EX64(flags, PSERIES_WDTF, OP);
 139    unsigned timeoutAction = FIELD_EX64(flags, PSERIES_WDTF, ACTION);
 140    SpaprWatchdog *w;
 141
 142    if (FIELD_EX64(flags, PSERIES_WDTF, RESERVED)) {
 143        return H_PARAMETER;
 144    }
 145
 146    switch (operation) {
 147    case PSERIES_WDTF_OP_START:
 148        if (watchdogNumber > ARRAY_SIZE(spapr->wds)) {
 149            return H_P2;
 150        }
 151        if (timeoutInMs <= WDT_MIN_TIMEOUT) {
 152            return H_P3;
 153        }
 154
 155        w = &spapr->wds[watchdogNumber - 1];
 156        switch (timeoutAction) {
 157        case PSERIES_WDTF_ACTION_HARD_POWER_OFF:
 158        case PSERIES_WDTF_ACTION_HARD_RESTART:
 159        case PSERIES_WDTF_ACTION_DUMP_RESTART:
 160            w->action = timeoutAction;
 161            break;
 162        default:
 163            return H_PARAMETER;
 164        }
 165        w->leave_others = FIELD_EX64(flags, PSERIES_WDTF, LEAVE_OTHER);
 166        timer_mod(&w->timer,
 167                  qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + timeoutInMs);
 168        trace_spapr_watchdog_start(flags, watchdogNumber, timeoutInMs);
 169        break;
 170    case PSERIES_WDTF_OP_STOP:
 171        if (watchdogNumber == PSERIES_WDT_STOP_ALL) {
 172            ret = watchdog_stop_all(spapr);
 173        } else if (watchdogNumber <= ARRAY_SIZE(spapr->wds)) {
 174            ret = watchdog_stop(watchdogNumber,
 175                                &spapr->wds[watchdogNumber - 1]);
 176        } else {
 177            return H_P2;
 178        }
 179        break;
 180    case PSERIES_WDTF_OP_QUERY:
 181        args[0] = FIELD_DP64(0, PSERIES_WDTQ, MIN_TIMEOUT, WDT_MIN_TIMEOUT);
 182        args[0] = FIELD_DP64(args[0], PSERIES_WDTQ, NUM,
 183                             ARRAY_SIZE(spapr->wds));
 184        trace_spapr_watchdog_query(args[0]);
 185        break;
 186    case PSERIES_WDTF_OP_QUERY_LPM:
 187        if (watchdogNumber > ARRAY_SIZE(spapr->wds)) {
 188            return H_P2;
 189        }
 190        args[0] = PSERIES_WDTQL_QUERY_NOT_STOPPED;
 191        trace_spapr_watchdog_query_lpm(args[0]);
 192        break;
 193    default:
 194        return H_PARAMETER;
 195    }
 196
 197    return ret;
 198}
 199
 200void spapr_watchdog_init(SpaprMachineState *spapr)
 201{
 202    int i;
 203
 204    for (i = 0; i < ARRAY_SIZE(spapr->wds); ++i) {
 205        char name[16];
 206        SpaprWatchdog *w = &spapr->wds[i];
 207
 208        snprintf(name, sizeof(name) - 1, "wdt%d", i + 1);
 209        object_initialize_child_with_props(OBJECT(spapr), name, w,
 210                                           sizeof(SpaprWatchdog),
 211                                           TYPE_SPAPR_WDT,
 212                                           &error_fatal, NULL);
 213        qdev_realize(DEVICE(w), NULL, &error_fatal);
 214    }
 215}
 216
 217static bool watchdog_needed(void *opaque)
 218{
 219    SpaprWatchdog *w = opaque;
 220
 221    return timer_pending(&w->timer);
 222}
 223
 224static const VMStateDescription vmstate_wdt = {
 225    .name = "spapr_watchdog",
 226    .version_id = 1,
 227    .minimum_version_id = 1,
 228    .needed = watchdog_needed,
 229    .fields = (VMStateField[]) {
 230        VMSTATE_TIMER(timer, SpaprWatchdog),
 231        VMSTATE_UINT8(action, SpaprWatchdog),
 232        VMSTATE_UINT8(leave_others, SpaprWatchdog),
 233        VMSTATE_END_OF_LIST()
 234    }
 235};
 236
 237static void spapr_wdt_realize(DeviceState *dev, Error **errp)
 238{
 239    SpaprWatchdog *w = SPAPR_WDT(dev);
 240    Object *o = OBJECT(dev);
 241
 242    timer_init_ms(&w->timer, QEMU_CLOCK_VIRTUAL, watchdog_expired, w);
 243
 244    object_property_add_uint64_ptr(o, "expire",
 245                                   (uint64_t *)&w->timer.expire_time,
 246                                   OBJ_PROP_FLAG_READ);
 247    object_property_add_uint8_ptr(o, "action", &w->action, OBJ_PROP_FLAG_READ);
 248    object_property_add_uint8_ptr(o, "leaveOtherWatchdogsRunningOnTimeout",
 249                                  &w->leave_others, OBJ_PROP_FLAG_READ);
 250}
 251
 252static void spapr_wdt_class_init(ObjectClass *oc, void *data)
 253{
 254    DeviceClass *dc = DEVICE_CLASS(oc);
 255
 256    dc->realize = spapr_wdt_realize;
 257    dc->vmsd = &vmstate_wdt;
 258    dc->user_creatable = false;
 259}
 260
 261static const TypeInfo spapr_wdt_info = {
 262    .name          = TYPE_SPAPR_WDT,
 263    .parent        = TYPE_DEVICE,
 264    .instance_size = sizeof(SpaprWatchdog),
 265    .class_init    = spapr_wdt_class_init,
 266};
 267
 268static void spapr_watchdog_register_types(void)
 269{
 270    spapr_register_hypercall(H_WATCHDOG, h_watchdog);
 271    type_register_static(&spapr_wdt_info);
 272}
 273
 274type_init(spapr_watchdog_register_types)
 275