On 1/23/23 15:57, Martin Kletzander wrote: > This is already possible with qemu, and actually already happening with q35 > machines and a specified watchdog since q35 already includes a watchdog we do > not include in the XML. In order to express such posibility multiple watchdogs > need to be supported. > > Signed-off-by: Martin Kletzander <mkletzan@xxxxxxxxxx> > --- > src/conf/domain_conf.c | 82 +++++++++++++------ > src/conf/domain_conf.h | 8 +- > src/conf/schemas/domaincommon.rng | 4 +- > src/libvirt_private.syms | 1 + > src/qemu/qemu_alias.c | 26 ++++-- > src/qemu/qemu_alias.h | 5 +- > src/qemu/qemu_command.c | 24 ++++-- > src/qemu/qemu_domain_address.c | 8 +- > src/qemu/qemu_driver.c | 14 ++-- > src/qemu/qemu_hotplug.c | 61 ++++++-------- > src/qemu/qemu_process.c | 3 +- > src/qemu/qemu_validate.c | 25 ++++++ > .../watchdog-q35-multiple.x86_64-latest.args | 39 +++++++++ > .../watchdog-q35-multiple.xml | 25 ++++++ > tests/qemuxml2argvtest.c | 1 + > .../watchdog-q35-multiple.x86_64-latest.xml | 50 +++++++++++ > tests/qemuxml2xmltest.c | 1 + > 17 files changed, 287 insertions(+), 90 deletions(-) > create mode 100644 tests/qemuxml2argvdata/watchdog-q35-multiple.x86_64-latest.args > create mode 100644 tests/qemuxml2argvdata/watchdog-q35-multiple.xml > create mode 100644 tests/qemuxml2xmloutdata/watchdog-q35-multiple.x86_64-latest.xml > > diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c > index 733399e6da0d..7c61da1d765b 100644 > --- a/src/conf/domain_conf.c > +++ b/src/conf/domain_conf.c > @@ -3875,7 +3875,9 @@ void virDomainDefFree(virDomainDef *def) > def->blkio.ndevices); > g_free(def->blkio.devices); > > - virDomainWatchdogDefFree(def->watchdog); > + for (i = 0; i < def->nwatchdogs; i++) > + virDomainWatchdogDefFree(def->watchdogs[i]); > + g_free(def->watchdogs); > > virDomainMemballoonDefFree(def->memballoon); > virDomainNVRAMDefFree(def->nvram); > @@ -4647,10 +4649,10 @@ virDomainDeviceInfoIterateFlags(virDomainDef *def, > if ((rc = cb(def, &device, &def->fss[i]->info, opaque)) != 0) > return rc; > } > - if (def->watchdog) { > - device.type = VIR_DOMAIN_DEVICE_WATCHDOG; > - device.data.watchdog = def->watchdog; > - if ((rc = cb(def, &device, &def->watchdog->info, opaque)) != 0) > + device.type = VIR_DOMAIN_DEVICE_WATCHDOG; > + for (i = 0; i < def->nwatchdogs; i++) { > + device.data.watchdog = def->watchdogs[i]; > + if ((rc = cb(def, &device, &def->watchdogs[i]->info, opaque)) != 0) > return rc; > } > if (def->memballoon) { > @@ -18809,24 +18811,21 @@ virDomainDefParseXML(xmlXPathContextPtr ctxt, > VIR_FREE(nodes); > > /* analysis of the watchdog devices */ > - def->watchdog = NULL; > - if ((n = virXPathNodeSet("./devices/watchdog", ctxt, &nodes)) < 0) > + n = virXPathNodeSet("./devices/watchdog", ctxt, &nodes); > + if (n < 0) > return NULL; > - if (n > 1) { > - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", > - _("only a single watchdog device is supported")); > - return NULL; > - } > - if (n > 0) { > + if (n) > + def->watchdogs = g_new0(virDomainWatchdogDef *, n); > + for (i = 0; i < n; i++) { > virDomainWatchdogDef *watchdog; > > - watchdog = virDomainWatchdogDefParseXML(xmlopt, nodes[0], ctxt, flags); > + watchdog = virDomainWatchdogDefParseXML(xmlopt, nodes[i], ctxt, flags); > if (!watchdog) > return NULL; > > - def->watchdog = watchdog; > - VIR_FREE(nodes); > + def->watchdogs[def->nwatchdogs++] = watchdog; > } > + VIR_FREE(nodes); > > /* analysis of the memballoon devices */ > def->memballoon = NULL; > @@ -21255,18 +21254,19 @@ virDomainDefCheckABIStabilityFlags(virDomainDef *src, > dst->redirfilter)) > goto error; > > - if ((!src->watchdog && dst->watchdog) || > - (src->watchdog && !dst->watchdog)) { > + > + if (src->nwatchdogs != dst->nwatchdogs) { > virReportError(VIR_ERR_CONFIG_UNSUPPORTED, > - _("Target domain watchdog count %d " > - "does not match source %d"), > - dst->watchdog ? 1 : 0, src->watchdog ? 1 : 0); > + _("Target domain watchdog device count %zu " > + "does not match source %zu"), Since you're touching this commit message, put it onto a single line please. > + dst->nwatchdogs, src->nwatchdogs); > goto error; > } > > - if (src->watchdog && > - !virDomainWatchdogDefCheckABIStability(src->watchdog, dst->watchdog)) > - goto error; > + for (i = 0; i < src->nwatchdogs; i++) { > + if (!virDomainWatchdogDefCheckABIStability(src->watchdogs[i], dst->watchdogs[i])) > + goto error; > + } > > if ((!src->memballoon && dst->memballoon) || > (src->memballoon && !dst->memballoon)) { > @@ -27517,8 +27517,8 @@ virDomainDefFormatInternalSetRootName(virDomainDef *def, > return -1; > } > > - if (def->watchdog) > - virDomainWatchdogDefFormat(buf, def->watchdog, flags); > + for (n = 0; n < def->nwatchdogs; n++) > + virDomainWatchdogDefFormat(buf, def->watchdogs[n], flags); > > if (def->memballoon) > virDomainMemballoonDefFormat(buf, def->memballoon, flags); > @@ -30578,3 +30578,33 @@ virDomainDefHasSpiceGraphics(const virDomainDef *def) > > return false; > } > + > + > +ssize_t > +virDomainWatchdogDefFind(const virDomainDef *def, > + const virDomainWatchdogDef *watchdog) > +{ > + size_t i; > + > + for (i = 0; i < def->nwatchdogs; i++) { > + const virDomainWatchdogDef *tmp = def->watchdogs[i]; > + > + if (tmp->model != watchdog->model) > + continue; > + > + if (tmp->action != watchdog->action) > + continue; > + > + if (watchdog->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE && > + !virDomainDeviceInfoAddressIsEqual(&watchdog->info, &tmp->info)) > + continue; > + > + if (watchdog->info.alias && > + STRNEQ_NULLABLE(watchdog->info.alias, tmp->info.alias)) > + continue; > + > + return i; > + } > + > + return -1; > +} > diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h > index 3e4985a67da2..da785076151d 100644 > --- a/src/conf/domain_conf.h > +++ b/src/conf/domain_conf.h > @@ -3062,12 +3062,14 @@ struct _virDomainDef { > size_t nsysinfo; > virSysinfoDef **sysinfo; > > + size_t nwatchdogs; > + virDomainWatchdogDef **watchdogs; > + > /* At maximum 2 TPMs on the domain if a TPM Proxy is present. */ > size_t ntpms; > virDomainTPMDef **tpms; > > /* Only 1 */ > - virDomainWatchdogDef *watchdog; > virDomainMemballoonDef *memballoon; > virDomainNVRAMDef *nvram; > virCPUDef *cpu; > @@ -3526,6 +3528,10 @@ virDomainSoundDef *virDomainSoundDefRemove(virDomainDef *def, size_t idx); > void virDomainAudioDefFree(virDomainAudioDef *def); > void virDomainMemballoonDefFree(virDomainMemballoonDef *def); > void virDomainNVRAMDefFree(virDomainNVRAMDef *def); > +ssize_t > +virDomainWatchdogDefFind(const virDomainDef *def, > + const virDomainWatchdogDef *watchdog) > + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT; > void virDomainWatchdogDefFree(virDomainWatchdogDef *def); > virDomainVideoDef *virDomainVideoDefNew(virDomainXMLOption *xmlopt); > void virDomainVideoDefFree(virDomainVideoDef *def); > diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng > index 6cb0a20e1e10..a5d0505d9b9b 100644 > --- a/src/conf/schemas/domaincommon.rng > +++ b/src/conf/schemas/domaincommon.rng > @@ -6428,9 +6428,9 @@ > <ref name="memorydev"/> > </choice> > </zeroOrMore> > - <optional> > + <zeroOrMore> > <ref name="watchdog"/> > - </optional> > + </zeroOrMore> > <optional> > <ref name="memballoon"/> > </optional> > diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms > index 576ec8f95f17..2c5489b2b86e 100644 > --- a/src/libvirt_private.syms > +++ b/src/libvirt_private.syms > @@ -692,6 +692,7 @@ virDomainVsockDefFree; > virDomainVsockDefNew; > virDomainWatchdogActionTypeFromString; > virDomainWatchdogActionTypeToString; > +virDomainWatchdogDefFind; > virDomainWatchdogDefFree; > virDomainWatchdogModelTypeFromString; > virDomainWatchdogModelTypeToString; > diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c > index ef8e87ab58a2..809a6f03ed46 100644 > --- a/src/qemu/qemu_alias.c > +++ b/src/qemu/qemu_alias.c > @@ -560,12 +560,26 @@ qemuAssignDeviceShmemAlias(virDomainDef *def, > > > void > -qemuAssignDeviceWatchdogAlias(virDomainWatchdogDef *watchdog) > +qemuAssignDeviceWatchdogAlias(virDomainDef *def, > + virDomainWatchdogDef *watchdog, > + int idx) > { > - /* Currently, there's just one watchdog per domain */ > + ssize_t i = 0; > + > + if (watchdog->info.alias) > + return; > + > + if (idx == -1) { > + for (i = 0; i < def->nwatchdogs; i++) { > + idx = MAX(idx, > + qemuDomainDeviceAliasIndex(&def->watchdogs[i]->info, > + "watchdog")); The MAX() macro is a glibc-ism, and worse - it evaluates arguments twice. Please switch to pattern we use elsewhere, e.g. in qemuAssignDeviceShmemAlias() just above. > + } > + > + idx++; > + } > > - if (!watchdog->info.alias) > - watchdog->info.alias = g_strdup("watchdog0"); > + watchdog->info.alias = g_strdup_printf("watchdog%d", idx); > } > > > @@ -671,8 +685,8 @@ qemuAssignDeviceAliases(virDomainDef *def) > for (i = 0; i < def->nsmartcards; i++) { > qemuAssignDeviceSmartcardAlias(def->smartcards[i], i); > } > - if (def->watchdog) { > - qemuAssignDeviceWatchdogAlias(def->watchdog); > + for (i = 0; i < def->nwatchdogs; i++) { > + qemuAssignDeviceWatchdogAlias(def, def->watchdogs[i], i); > } > if (def->memballoon && > def->memballoon->model != VIR_DOMAIN_MEMBALLOON_MODEL_NONE) { > diff --git a/src/qemu/qemu_alias.h b/src/qemu/qemu_alias.h > index 6433ae4cecaf..d7f34489f570 100644 > --- a/src/qemu/qemu_alias.h > +++ b/src/qemu/qemu_alias.h > @@ -62,7 +62,10 @@ void qemuAssignDeviceShmemAlias(virDomainDef *def, > virDomainShmemDef *shmem, > int idx); > > -void qemuAssignDeviceWatchdogAlias(virDomainWatchdogDef *watchdog); > +void > +qemuAssignDeviceWatchdogAlias(virDomainDef *def, > + virDomainWatchdogDef *watchdog, > + int idx); > > void qemuAssignDeviceInputAlias(virDomainDef *def, > virDomainInputDef *input, > diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c > index b96f2d33c158..6e28f8b15e1c 100644 > --- a/src/qemu/qemu_command.c > +++ b/src/qemu/qemu_command.c > @@ -4044,22 +4044,28 @@ qemuBuildWatchdogCommandLine(virCommand *cmd, > const virDomainDef *def, > virQEMUCaps *qemuCaps) > { > - virDomainWatchdogDef *watchdog = def->watchdog; > - g_autoptr(virJSONValue) props = NULL; > + virDomainWatchdogDef *watchdog = NULL; > const char *action; > int actualAction; > + ssize_t i = 0; > > - if (!def->watchdog) > + if (def->nwatchdogs == 0) > return 0; > > - if (qemuCommandAddExtDevice(cmd, &def->watchdog->info, def, qemuCaps) < 0) > - return -1; > + for (i = 0; i < def->nwatchdogs; i++) { > + g_autoptr(virJSONValue) props = NULL; > > - if (!(props = qemuBuildWatchdogDevProps(def, watchdog))) > - return -1; > + watchdog = def->watchdogs[i]; > > - if (qemuBuildDeviceCommandlineFromJSON(cmd, props, def, qemuCaps)) > - return -1; > + if (qemuCommandAddExtDevice(cmd, &watchdog->info, def, qemuCaps) < 0) > + return -1; > + > + if (!(props = qemuBuildWatchdogDevProps(def, watchdog))) > + return -1; > + > + if (qemuBuildDeviceCommandlineFromJSON(cmd, props, def, qemuCaps)) > + return -1; > + } > > /* qemu doesn't have a 'dump' action; we tell qemu to 'pause', then > libvirt listens for the watchdog event, and we perform the dump Here, I'd mention in the comment that a validator made sure that ALL watchdogs have the same action. It's not obvious at the first glance. > diff --git a/src/qemu/qemu_domain_address.c b/src/qemu/qemu_domain_address.c > index b8d1969fbebd..db4e91501d3f 100644 > --- a/src/qemu/qemu_domain_address.c > +++ b/src/qemu/qemu_domain_address.c > @@ -2326,10 +2326,10 @@ qemuDomainAssignDevicePCISlots(virDomainDef *def, > } > > /* A watchdog - check if it is a PCI device */ > - if (def->watchdog && > - def->watchdog->model == VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB && > - virDeviceInfoPCIAddressIsWanted(&def->watchdog->info)) { > - if (qemuDomainPCIAddressReserveNextAddr(addrs, &def->watchdog->info) < 0) > + for (i = 0; i < def->nwatchdogs; i++) { > + if (def->watchdogs[i]->model == VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB && > + virDeviceInfoPCIAddressIsWanted(&def->watchdogs[i]->info) && > + qemuDomainPCIAddressReserveNextAddr(addrs, &def->watchdogs[i]->info) < 0) > return -1; > } > > diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c > index d6879175fece..dce3bef5b85b 100644 > --- a/src/qemu/qemu_driver.c > +++ b/src/qemu/qemu_driver.c > @@ -7258,12 +7258,13 @@ qemuDomainAttachDeviceConfig(virDomainDef *vmdef, > break; > > case VIR_DOMAIN_DEVICE_WATCHDOG: > - if (vmdef->watchdog) { > + if (virDomainWatchdogDefFind(vmdef, dev->data.watchdog) >= 0) { > virReportError(VIR_ERR_OPERATION_INVALID, "%s", > - _("domain already has a watchdog")); > + _("device is already in the domain configuration")); > return -1; > } > - vmdef->watchdog = g_steal_pointer(&dev->data.watchdog); > + > + VIR_APPEND_ELEMENT(vmdef->watchdogs, vmdef->nwatchdogs, dev->data.watchdog); > break; > > case VIR_DOMAIN_DEVICE_INPUT: > @@ -7457,12 +7458,13 @@ qemuDomainDetachDeviceConfig(virDomainDef *vmdef, > > > case VIR_DOMAIN_DEVICE_WATCHDOG: > - if (!vmdef->watchdog) { > + idx = virDomainWatchdogDefFind(vmdef, dev->data.watchdog); > + if (idx < 0) { > virReportError(VIR_ERR_DEVICE_MISSING, "%s", > - _("domain has no watchdog")); > + _("no matching watchdog was found")); > return -1; > } > - g_clear_pointer(&vmdef->watchdog, virDomainWatchdogDefFree); > + VIR_DELETE_ELEMENT(vmdef->watchdogs, idx, vmdef->nwatchdogs); > break; > > case VIR_DOMAIN_DEVICE_INPUT: > diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c > index 1fa3cc3ea9b1..b9832ea7815a 100644 > --- a/src/qemu/qemu_hotplug.c > +++ b/src/qemu/qemu_hotplug.c > @@ -2928,15 +2928,9 @@ qemuDomainAttachWatchdog(virDomainObj *vm, > virDomainDeviceDef dev = { VIR_DOMAIN_DEVICE_WATCHDOG, { .watchdog = watchdog } }; > g_autoptr(virJSONValue) props = NULL; > bool releaseAddress = false; > - int rv; > + int rv = 0; > > - if (vm->def->watchdog) { > - virReportError(VIR_ERR_OPERATION_INVALID, "%s", > - _("domain already has a watchdog")); > - return -1; > - } > - > - qemuAssignDeviceWatchdogAlias(watchdog); > + qemuAssignDeviceWatchdogAlias(vm->def, watchdog, -1); > > if (watchdog->model != VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB) { > virReportError(VIR_ERR_OPERATION_UNSUPPORTED, > @@ -2954,10 +2948,13 @@ qemuDomainAttachWatchdog(virDomainObj *vm, > > qemuDomainObjEnterMonitor(vm); > > - /* QEMU doesn't have a 'dump' action; we tell qemu to 'pause', then > - libvirt listens for the watchdog event, and we perform the dump > - ourselves. so convert 'dump' to 'pause' for the qemu cli */ > - if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_SET_ACTION)) { > + if (vm->def->nwatchdogs) { > + /* Domain already has a watchdog and all must have the same action. */ > + rv = 0; > + } else if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_SET_ACTION)) { > + /* QEMU doesn't have a 'dump' action; we tell qemu to 'pause', then > + libvirt listens for the watchdog event, and we perform the dump > + ourselves. so convert 'dump' to 'pause' for the qemu cli */ > qemuMonitorActionWatchdog watchdogaction = QEMU_MONITOR_ACTION_WATCHDOG_KEEP; > > switch (watchdog->action) { > @@ -3015,7 +3012,7 @@ qemuDomainAttachWatchdog(virDomainObj *vm, > goto cleanup; > > releaseAddress = false; > - vm->def->watchdog = watchdog; > + VIR_APPEND_ELEMENT_COPY(vm->def->watchdogs, vm->def->nwatchdogs, watchdog); > ret = 0; > > cleanup: > @@ -4848,11 +4845,20 @@ static int > qemuDomainRemoveWatchdog(virDomainObj *vm, > virDomainWatchdogDef *watchdog) > { > + size_t i = 0; > + > VIR_DEBUG("Removing watchdog %s from domain %p %s", > watchdog->info.alias, vm, vm->def->name); > > + for (i = 0; i < vm->def->nwatchdogs; i++) { > + if (vm->def->watchdogs[i] == watchdog) > + break; > + } > + > qemuDomainReleaseDeviceAddress(vm, &watchdog->info); > - g_clear_pointer(&vm->def->watchdog, virDomainWatchdogDefFree); > + virDomainWatchdogDefFree(watchdog); > + VIR_DELETE_ELEMENT(vm->def->watchdogs, i, vm->def->nwatchdogs); > + > return 0; > } > > @@ -5603,33 +5609,20 @@ qemuDomainDetachPrepWatchdog(virDomainObj *vm, > virDomainWatchdogDef *match, > virDomainWatchdogDef **detach) > { > - virDomainWatchdogDef *watchdog; > - > - *detach = watchdog = vm->def->watchdog; > + ssize_t idx = virDomainWatchdogDefFind(vm->def, match); > > - if (!watchdog) { > - virReportError(VIR_ERR_DEVICE_MISSING, "%s", > - _("watchdog device not present in domain configuration")); > + if (idx < 0) { > + virReportError(VIR_ERR_OPERATION_INVALID, "%s", > + _("no matching watchdog was found")); > return -1; > } > > - /* While domains can have up to one watchdog, the one supplied by the user > - * doesn't necessarily match the one domain has. Refuse to detach in such > - * case. */ > - if (!(watchdog->model == match->model && > - watchdog->action == match->action && > - virDomainDeviceInfoAddressIsEqual(&match->info, &watchdog->info))) { > - virReportError(VIR_ERR_DEVICE_MISSING, > - _("model '%s' watchdog device not present " > - "in domain configuration"), > - virDomainWatchdogModelTypeToString(watchdog->model)); > - return -1; > - } > + *detach = vm->def->watchdogs[idx]; > > - if (watchdog->model != VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB) { > + if ((*detach)->model != VIR_DOMAIN_WATCHDOG_MODEL_I6300ESB) { > virReportError(VIR_ERR_OPERATION_UNSUPPORTED, > _("hot unplug of watchdog of model %s is not supported"), > - virDomainWatchdogModelTypeToString(watchdog->model)); > + virDomainWatchdogModelTypeToString((*detach)->model)); > return -1; > } > > diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c > index ee9f0784d3a3..218231f3596c 100644 > --- a/src/qemu/qemu_process.c > +++ b/src/qemu/qemu_process.c > @@ -804,7 +804,8 @@ qemuProcessHandleWatchdog(qemuMonitor *mon G_GNUC_UNUSED, > qemuDomainSaveStatus(vm); > } > > - if (vm->def->watchdog->action == VIR_DOMAIN_WATCHDOG_ACTION_DUMP) { > + if (vm->def->nwatchdogs && > + vm->def->watchdogs[0]->action == VIR_DOMAIN_WATCHDOG_ACTION_DUMP) { > qemuProcessEventSubmit(vm, QEMU_PROCESS_EVENT_WATCHDOG, > VIR_DOMAIN_WATCHDOG_ACTION_DUMP, 0, NULL); > } > diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c > index 6e04b22da43f..cb002dee0eb9 100644 > --- a/src/qemu/qemu_validate.c > +++ b/src/qemu/qemu_validate.c > @@ -1188,6 +1188,28 @@ qemuValidateDomainDefTPMs(const virDomainDef *def) > } > > > +static int > +qemuValidateDomainDefWatchdogs(const virDomainDef *def) > +{ > + ssize_t i = 0; > + > + for (i = 0; i < def->nwatchdogs; i++) { Here, you can init i to 1, as ... > + /* We could theoretically support different watchdogs having dump and > + * pause, but let's be honest, we support multiple watchdogs only > + * because we need to be able to add a second, implicit one, not because > + * it is a brilliant idea to have multiple watchdogs. */ > + if (def->watchdogs[i]->action != def->watchdogs[0]->action) { ... for i=0 case, this is never ever gonna be true. > + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", > + _("watchdogs with different actions are not supported " > + "with this QEMU binary")); > + return -1; > + } > + } > + > + return 0; > +} > + > + Michal