Akihiko Odaki <[email protected]> writes:
> On 2026/09/24 6:35, Fabiano Rosas wrote:
>> Akihiko Odaki <[email protected]> writes:
>>
>>> On 2026/09/22 23:28, Fabiano Rosas wrote:
>>>> Akihiko Odaki <[email protected]> writes:
>>>>
>>>>> xen-save-devices-state saves device VMState outside QEMU's normal
>>>>> migration-mode machinery, so it currently ignores migration blockers.
>>>>> Add a separate blocker class for this command.
>>>>>
>>>>> Blockers registered with migrate_add_blocker() or
>>>>> migrate_add_blocker_internal() now include this class. Existing
>>>>> mode-specific blocker masks remain unchanged, except that vhost-scsi's
>>>>> default blocker now covers normal migration and Xen device-state
>>>>> saving. Its CPR exemption assumes that the replacement QEMU runs on
>>>>> the same host and not concurrently with the old QEMU; Xen save and
>>>>> restore do not guarantee those conditions. Users can set
>>>>> migratable=true when shared storage or orchestrator-migrated target
>>>>> state makes migration safe.
>>>>>
>>>>
>>>> So one thing is rejecting the xen-save-devices-state command if there
>>>> are blockers. This could very well just use the normal mode (default),
>>>> no? Or all modes, like qmp_dump_guest_memory() does?
>>>
>>> In general, blockers registered via migrate_add_blocker_normal() are
>>> specific to standard QEMU live migration and do not apply to Xen. For
>>> example, block formats like qcow or vhd register normal migration
>>> blockers, but Xen explicitly supports migration with them [1].
>>> Unconditionally applying normal-mode blockers to Xen would cause
>>> regressions. vhost-scsi is the exception here and is handled specifically.
>>>
>>> Checking every mode would lead to the same regression. Note that
>>> qmp_dump_guest_memory() registers an all-mode blocker during execution;
>>> it does not check all existing blocker lists when invoked.
>>>
>>> [1] https://xenbits.xen.org/docs/4.22-testing/SUPPORT.html#blkback
>>>
>>>>
>>>> Another thing (complementary, could be in the same patch) is rejecting
>>>> the command line if there are blocked devices, including any ones used
>>>> along with Xen acceleration. Again, why does this need a new blocker
>>>> category? I'm not seeing it.
>>> You can use -only-migratable.
>>>
>>>>
>>>>> Fixes: a7ae8355b446 ("Introduce "xen-save-devices-state"")
>>>>> Signed-off-by: Akihiko Odaki <[email protected]>
>>>>> ---
>>>>> include/migration/blocker.h | 10 ++++++-
>>>>> include/qemu/accel.h | 2 +-
>>>>> migration/migration.h | 2 ++
>>>>> accel/accel-system.c | 25 +++++++++++++-----
>>>>> hw/scsi/vhost-scsi.c | 5 +++-
>>>>> migration/migration.c | 63
>>>>> ++++++++++++++++++++++++++++++++++++++-------
>>>>> migration/savevm.c | 4 +++
>>>>> system/vl.c | 9 ++++---
>>>>> 8 files changed, 99 insertions(+), 21 deletions(-)
>>>>>
>>>>> diff --git a/include/migration/blocker.h b/include/migration/blocker.h
>>>>> index 80b75ad5cbdb..0fbaaac83ce8 100644
>>>>> --- a/include/migration/blocker.h
>>>>> +++ b/include/migration/blocker.h
>>>>> @@ -16,6 +16,14 @@
>>>>>
>>>>> #include "qapi/qapi-types-migration.h"
>>>>>
>>>>> +/**
>>>>> + * define MIG_XEN - identifier for Xen migration
>>>>
>>>> Hm.
>>>>
>>>>> + *
>>>>> + * Xen migration is distinct from those expressed with &typedef MigMode.
>>>>> + * Use this for migration blockers that affect Xen.
>>>>> + */
>>>>> +#define MIG_XEN MIG_MODE__MAX
>>>>
>>>> This could be fine, Xen migration is pre-existing and 'mode' is part of
>>>> QAPI, we don't want to create a new mode for Xen.
>>>>
>>>>> +
>>>>> /**
>>>>> * @migrate_add_blocker - prevent all modes of migration from
>>>>> proceeding
>>>>> *
>>>>> @@ -80,7 +88,7 @@ int migrate_add_blocker_normal(Error **reasonp, Error
>>>>> **errp);
>>>>> *
>>>>> * @reasonp - address of an error to be returned whenever migration is
>>>>> attempted
>>>>> *
>>>>> - * @modes - the migration modes to be blocked, a bit set of MigMode
>>>>> + * @modes - the migration modes to be blocked, a bit set of MigMode and
>>>>> %MIG_XEN
>>>>
>>>> But it's not a mode, so we shouldn't re-use @modes. We just want a place
>>>> to store the Xen blockers, let's not introduce the confusion that this
>>>> could be a mode somehow.
>>>>
>>>> We could just add migrate_add_blocker_xen and call add_blockers directly
>>>> since you're changing that function to use the array size instead of
>>>> MIG_MODE__MAX.
>>>
>>> I would prefer to keep a single function for adding blockers across all
>>> migration types so that the full scope of a blocker can be seen at a
>>> single call site (see the vhost-scsi changes for an example).
>>>
>>
>> Fair.
>>
>>> Practically speaking, I think the risk of reusing @modes is minimal
>>> because there is no alternative selective-blocking API to misuse. It
>>> would indeed be cleaner to have a parameter name that encompasses both
>>> MigMode and MIG_XEN, but I haven't come up with a better name yet;
>>> suggestions are welcome.
>>>
>>
>> Keep the @mode, but let's do away with any mentions of MigMode. This is
>> a "blocker mode", not "migration mode". wink wink
>
> Good idea. That will also fix the documentation comments, along with the
> variable and function names. I will include this change in the next version.
>
>>
>>>>
>>>>> *
>>>>> * @errp - [out] The reason (if any) we cannot block migration right
>>>>> now.
>>>>> *
>>>>> diff --git a/include/qemu/accel.h b/include/qemu/accel.h
>>>>> index 6f65f1cb89a0..b6b456a82fc3 100644
>>>>> --- a/include/qemu/accel.h
>>>>> +++ b/include/qemu/accel.h
>>>>> @@ -47,7 +47,7 @@ const char *current_accel_name(void);
>>>>>
>>>>> void accel_init_interfaces(AccelClass *ac);
>>>>>
>>>>> -int accel_init_machine(AccelState *accel, MachineState *ms);
>>>>> +int accel_init_machine(AccelState *accel, MachineState *ms, Error
>>>>> **errp);
>>>>>
>>>>> /* Called just before os_setup_post (ie just before drop OS privs) */
>>>>> void accel_setup_post(MachineState *ms);
>>>>> diff --git a/migration/migration.h b/migration/migration.h
>>>>> index 683bc7bdd521..f53fdb2df107 100644
>>>>> --- a/migration/migration.h
>>>>> +++ b/migration/migration.h
>>>>> @@ -568,7 +568,9 @@ void migration_start_incoming(void);
>>>>> int migration_call_notifiers(MigrationEventType type, Error **errp);
>>>>>
>>>>> int migrate_init(MigrationState *s, Error **errp);
>>>>> +bool enforce_only_migratable(Error **errp);
>>>>> bool migration_is_blocked(Error **errp);
>>>>> +bool xen_migration_is_blocked(Error **errp);
>>>>> /* True if outgoing migration has entered postcopy phase */
>>>>> bool migration_in_postcopy(void);
>>>>> bool migration_postcopy_is_alive(MigrationStatus state);
>>>>> diff --git a/accel/accel-system.c b/accel/accel-system.c
>>>>> index 1325b23864c8..75a691cb3cfe 100644
>>>>> --- a/accel/accel-system.c
>>>>> +++ b/accel/accel-system.c
>>>>> @@ -26,7 +26,9 @@
>>>>> #include "qemu/osdep.h"
>>>>> #include "qemu/accel.h"
>>>>> #include "qom/compat-properties.h"
>>>>> +#include "qapi/error.h"
>>>>> #include "qapi/qapi-commands-accelerator.h"
>>>>> +#include "migration/migration.h"
>>>>> #include "monitor/monitor.h"
>>>>> #include "monitor/hmp.h"
>>>>> #include "hw/core/boards.h"
>>>>> @@ -37,20 +39,31 @@
>>>>> #include "qemu/error-report.h"
>>>>> #include "accel-internal.h"
>>>>>
>>>>> -int accel_init_machine(AccelState *accel, MachineState *ms)
>>>>> +int accel_init_machine(AccelState *accel, MachineState *ms, Error **errp)
>>>>> {
>>>>> AccelClass *acc = ACCEL_GET_CLASS(accel);
>>>>> int ret;
>>>>> ms->accelerator = accel;
>>>>> *(acc->allowed) = true;
>>>>> +
>>>>> + if (!enforce_only_migratable(errp)) {
>>>>> + ret = -EACCES;
>>>>> + goto fail;
>>>>> + }
>>>>
>>>> Here I think you want to fail the initialization if the accelerator is
>>>> Xen, -only-migratable is set and there are blockers. At this point is
>>>> not clear whether these blockers really need a xen-specific category.
>>>
>>> The category reflects the specific requirements of the blocker itself.
>>> Looking at vhost-scsi (which registers a Xen-affecting blocker)
>>> clarifies why this distinction is necessary; this code merely enforces
>>> registered blockers.
>>>
>>>>
>>>> Isn't this too soon anyway? What blockers will be registered at this
>>>> point and if there are blockers on the list, why the
>>>> is_only_migratable() check at migrate_add_blocker_modes() hasn't already
>>>> rejected the cmdline?
>>>
>>> This check is placed here because qemu_create_early_backends() runs
>>> before configure_accelerators(). Command-line backends (such as qcow or
>>> vmdk) can register blockers before xen_enabled() accurately reflects the
>>> selected accelerator.
>>>
>>
>> Ok, so we want to validate that the blockers registered so far don't
>> affect the current accel. This information is important to have in a
>> comment somewhere.
>>
>> Now, in the bad case we're going to fail the startup anyway, so wouldn't
>> it be better to avoid all the error handling and just make this a
>> generic check after configure_accelerators?
>>
>> void qemu_init(int argc, char **argv)
>> {
>> configure_accelerators(argv[0]);
>> phase_advance(PHASE_ACCEL_CREATED);
>>
>> enforce_only_migratable();
>> ...
>
> I agree. We can also add a comment here explaining that
> -only-migratable needs to be enforced at this point.
>
See if you can structure these changes in a way that we first change the
behavior regarding when only_migratable is enforced, then later add the
Xen support. So we make it clear in the commit log how this affects the
normal mode.
>>
>>>>
>>>>> +
>>>>> ret = acc->init_machine(accel, ms);
>>>>> if (ret < 0) {
>>>>> - ms->accelerator = NULL;
>>>>> - *(acc->allowed) = false;
>>>>> - object_unref(OBJECT(accel));
>>>>> - } else {
>>>>> - object_set_accelerator_compat_props(acc->compat_props);
>>>>> + error_setg(errp, "%s", strerror(-ret));
>>>>> + goto fail;
>>>>> }
>>>>> +
>>>>> + object_set_accelerator_compat_props(acc->compat_props);
>>>>> + return ret;
>>>>> +
>>>>> +fail:
>>>>> + ms->accelerator = NULL;
>>>>> + *(acc->allowed) = false;
>>>>> + object_unref(OBJECT(accel));
>>>>> return ret;
>>>>> }
>>>>>
>>>>> diff --git a/hw/scsi/vhost-scsi.c b/hw/scsi/vhost-scsi.c
>>>>> index 657403cad011..f9847b5e7096 100644
>>>>> --- a/hw/scsi/vhost-scsi.c
>>>>> +++ b/hw/scsi/vhost-scsi.c
>>>>> @@ -262,12 +262,15 @@ static void vhost_scsi_realize(DeviceState *dev,
>>>>> Error **errp)
>>>>> }
>>>>>
>>>>> if (!vsc->migratable) {
>>>>> + unsigned int modes = BIT(MIG_MODE_NORMAL) | BIT(MIG_XEN);
>>>>> +
>>>>> error_setg(&vsc->migration_blocker,
>>>>> "vhost-scsi does not support migration in all cases. "
>>>>> "When external environment supports it (Orchestrator
>>>>> migrates "
>>>>> "target SCSI device state or use shared storage over
>>>>> network), "
>>>>> "set 'migratable' property to true to enable
>>>>> migration.");
>>>>> - if (migrate_add_blocker_normal(&vsc->migration_blocker, errp) <
>>>>> 0) {
>>>>> + if (migrate_add_blocker_modes(&vsc->migration_blocker,
>>>>> + modes, errp) < 0) {
>>>>> goto free_virtio;
>>>>> }
>>>>> }
>>>>> diff --git a/migration/migration.c b/migration/migration.c
>>>>> index d0b864a76066..c177dc52a1c1 100644
>>>>> --- a/migration/migration.c
>>>>> +++ b/migration/migration.c
>>>>> @@ -23,6 +23,7 @@
>>>>> #include "system/runstate.h"
>>>>> #include "system/system.h"
>>>>> #include "system/cpu-throttle.h"
>>>>> +#include "system/xen.h"
>>>>> #include "ram.h"
>>>>> #include "migration/cpr.h"
>>>>> #include "migration/global_state.h"
>>>>> @@ -94,7 +95,8 @@ enum mig_rp_message_type {
>>>>> static MigrationState *current_migration;
>>>>> static MigrationIncomingState *current_incoming;
>>>>>
>>>>> -static GSList *migration_blockers[MIG_MODE__MAX];
>>>>> +static GSList *migration_blockers[MIG_MODE__MAX + 1];
>>>>> +static bool enforcing_only_migratable;
>>
>> I think I missed the need for this variable. Isn't only_migratable
>> enough?
>
> enforcing_only_migratable is initially false, allowing blockers to be
> registered without immediate enforcement.
>
> Once enforce_only_migratable() is called, the existing blockers are
> validated, and this flag becomes true. Any blocker added later are then
> checked immediately. Together, this ensures all blockers are
> consistently enforced at runtime.
>
I see, otherwise we'll reject a blocker for MIG_MODE_NORMAL when in
reality the vm could be running Xen and therefore not want to reject
that blocker.
>>
>>>>>
>>>>> static bool migration_object_check(MigrationState *ms, Error **errp);
>>>>> static bool migration_switchover_start(MigrationState *s, Error
>>>>> **errp);
>>>>> @@ -1760,15 +1762,22 @@ static bool is_busy(Error **reasonp, Error **errp)
>>>>> return false;
>>>>> }
>>>>>
>>>>> +static void propagate_disallowed_blocker(Error **errp, Error **reasonp)
>>>>> +{
>>>>> + error_propagate_prepend(errp, *reasonp,
>>>>> + "disallowing migration blocker "
>>>>> + "(--only-migratable) for: ");
>>>>> + *reasonp = NULL;
>>>>> +}
>>>>> +
>>>>> static bool is_only_migratable(Error **reasonp, unsigned modes, Error
>>>>> **errp)
>>>>> {
>>>>> + unsigned mode = xen_enabled() ? MIG_XEN : MIG_MODE_NORMAL;
>>>>> +
>>
>> Hmm, the original code is actually special casing CPR isn't it? I'd
>> prefer to be explicit instead:
>>
>> unsigned int ignore_only_migratable = (BIT(MIG_MODE_CPR_TRANSFER) |
>> BIT(MIG_MODE_CPR_EXEC) |
>> BIT(MIG_MODE_CPR_REBOOT));
>> unsigned int cur_mode = blocker_mode() & ~ignore_only_migratable;
>
> blocker_mode() returns an index, whereas ignore_only_migratable is a
> bitmask, so we'll need to adapt the bitwise operations accordingly to
> handle them properly.
>
Well spotted.
>>
>> Where blocker_mode is:
>>
>> /*
>> * Migration blockers are scoped by migration mode. Nonetheless it's
>> * still possible for an accelerator to have particularities that
>> * require blocking migration, independently of mode. Use this helper
>> * when indexing into migration_blockers to ensure the accelerator is
>> * taken into account.
>> */
>> static unsigned int *blocker_mode(void)
>> {
>> if (phase_check(PHASE_ACCEL_CREATED)) {
>
> It would be safer to use assert(phase_check(PHASE_ACCEL_CREATED)); here;
> otherwise, the returned value may not be valid.
>
If we want to allow blockers to be registered before accel being
configured, then we cannot assert.
>> if (xen_enabled()) {
>> return MIG_BLOCK_XEN;
>> }
>> }
>>
>> return migrate_mode();
>> }
>
> Aside from the minor adjustments mentioned above, explicitly
> special-casing CPR looks good to me overall.
>
>>
>>>>> ERRP_GUARD();
>>>>>
>>>>> - if (only_migratable && (modes & BIT(MIG_MODE_NORMAL))) {
>>>>> - error_propagate_prepend(errp, *reasonp,
>>>>> - "disallowing migration blocker "
>>>>> - "(--only-migratable) for: ");
>>>>> - *reasonp = NULL;
>>>>> + if (enforcing_only_migratable && (modes & BIT(mode))) {
>>>>> + propagate_disallowed_blocker(errp, reasonp);
>>>>> return true;
>>>>> }
>>>>> return false;
>>>>> @@ -1776,7 +1785,7 @@ static bool is_only_migratable(Error **reasonp,
>>>>> unsigned modes, Error **errp)
>>>>>
>>>>> static int add_blockers(Error **reasonp, unsigned modes, Error **errp)
>>>>> {
>>>>> - for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
>>>>> + for (MigMode mode = 0; mode < ARRAY_SIZE(migration_blockers);
>>>>> mode++) {
>>>>
>>>> Not a MigMode anymore.
>>>
>>> Good catch. I will fix the type in the next version.
>>>
>>>>
>>>>> if (modes & BIT(mode)) {
>>>>> assert(g_slist_index(migration_blockers[mode],
>>>>> *reasonp) == -1);
>>>>> @@ -1809,7 +1818,7 @@ int migrate_add_blocker_modes(Error **reasonp,
>>>>> unsigned modes, Error **errp)
>>>>>
>>>>> int migrate_add_blocker_internal(Error **reasonp, Error **errp)
>>>>> {
>>>>> - unsigned modes = BIT(MIG_MODE__MAX) - 1;
>>>>> + unsigned modes = BIT(ARRAY_SIZE(migration_blockers)) - 1;
>>>>>
>>>>> if (is_busy(reasonp, errp)) {
>>>>> return -EBUSY;
>>>>> @@ -1820,7 +1829,7 @@ int migrate_add_blocker_internal(Error **reasonp,
>>>>> Error **errp)
>>>>> void migrate_del_blocker(Error **reasonp)
>>>>> {
>>>>> if (*reasonp) {
>>>>> - for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
>>>>> + for (MigMode mode = 0; mode < ARRAY_SIZE(migration_blockers);
>>>>> mode++) {
>>>>
>>>> Same here, just use int i.
>>>
>>> Will update the variable name and type accordingly.
>>>
>>>>
>>>>> migration_blockers[mode] =
>>>>> g_slist_remove(migration_blockers[mode],
>>>>> *reasonp);
>>>>> }
>>>>> @@ -1948,6 +1957,26 @@ void qmp_migrate_pause(Error **errp)
>>>>> "during postcopy-active or postcopy-recover state");
>>>>> }
>>>>>
>>>>> +bool enforce_only_migratable(Error **errp)
>>>>> +{
>>>>> + unsigned mode = xen_enabled() ? MIG_XEN : MIG_MODE_NORMAL;
>>>>> + GSList *blockers = migration_blockers[mode];
>>>>
>>>> For Xen, what blockers are those? Is it the case that existing blockers
>>>> are simply not taking effect for Xen because the QMP command never
>>>> checks blockers? Or do you intend to add xen-specific blockers?
>>>
>>> The former.
>>>
>>> Existing migration blockers currently do not take effect for Xen, which
>>> this patch fixes. However, applying all blockers from an existing
>>> MigMode could cause a regression for the reasons stated above.
>>> Therefore, this patch ensures that only blockers relevant to Xen are
>>> applied.
>>>
>>> Specifically, it enforces the blockers added via migrate_add_blocker()
>>> and the vhost-scsi blocker. The remaining blockers have not been proven
>>> incompatible with Xen migration, so applying them unconditionally could
>>> cause regressions.
>>>
>>> Regards,
>>> Akihiko Odaki
>>>
>>>>
>>>>> + Error *reason;
>>>>> +
>>>>> + if (!only_migratable) {
>>>>> + return true;
>>>>> + }
>>>>> +
>>>>> + if (blockers) {
>>>>> + reason = error_copy(blockers->data);
>>>>> + propagate_disallowed_blocker(errp, &reason);
>>>>> + return false;
>>>>> + }
>>>>> +
>>>>> + enforcing_only_migratable = true;
>>>>> + return true;
>>>>> +}
>>
>> We could reduce the scope of migration_is_blocked so
>> enforce_only_migrate can call it instead of checking the blockers
>> itself:
>>
>> // replaces migration_is_blocked
>> bool can_migrate(Error **errp)
>> {
>> if (qemu_savevm_state_blocked(errp)) {
>> return false;
>> }
>>
>> if (migration_is_blocked(errp) {
>> return false;
>> }
>> return true;
>> }
>>
>> static bool migration_is_blocked(Error **errp)
>> {
>> GSList *blockers;
>>
>> assert(phase_check(PHASE_ACCEL_CREATED));
>>
>> blockers = migration_blockers[blocker_mode()];
>> if (blockers) {
>> error_propagate(errp, error_copy(blockers->data));
>> return true;
>> }
>>
>> return false;
>> }
>
> This might be bikeshedding, but I'd rather extract the logic to check
> migration_blockers to a new function, perhaps named
> has_migration_blockers().
>
> I feel can_migrate() is a bit confusing since migrate_prepare() actually
> has more checks and it does not clearly differentiate itself from
> migration_is_blocked() well. Updating migration/savevm.c would also be
> slightly cumbersome.
>
> While has_migration_blockers() doesn't perfectly differentiate itself
> from migration_is_blocked() either, aligning it with the existing
> "migration_blockers" variable clarifies its exact intent.
>
Sure, go ahead. Maybe migration_active_blockers or some other word to
indicate the blockers affect the mode/accel currently in effect.
>>
>>>>> +
>>>>> bool migration_is_blocked(Error **errp)
>>>>> {
>>>>> GSList *blockers = migration_blockers[migrate_mode()];
>>>>> @@ -1964,6 +1993,22 @@ bool migration_is_blocked(Error **errp)
>>>>> return false;
>>>>> }
>>>>>
>>>>> +bool xen_migration_is_blocked(Error **errp)
>>>>> +{
>>>>> + GSList *blockers = migration_blockers[MIG_XEN];
>>
>> Does this need to use MIG_XEN? If we use something like the
>> blocker_mode() I suggested, then this function can be replaced with
>> can_migrate() and we make all the points that start migration uniform.
>>
>> What do you think? Let me know if I missed any details.
>
> That makes sense to me. I'll incorporate this approach in the next version.
>
Thanks! See you in v2.
> Regards,
> Akihiko Odaki
>
>>
>> Thanks
>>
>>>>> +
>>>>> + if (qemu_savevm_state_blocked(errp)) {
>>>>> + return true;
>>>>> + }
>>>>> +
>>>>> + if (blockers) {
>>>>> + error_propagate(errp, error_copy(blockers->data));
>>>>> + return true;
>>>>> + }
>>>>> +
>>>>> + return false;
>>>>> +}
>>>>> +
>>>>> /* Returns true if continue to migrate, or false if error detected */
>>>>> static bool migrate_prepare(MigrationState *s, bool resume, Error
>>>>> **errp)
>>>>> {
>>>>> diff --git a/migration/savevm.c b/migration/savevm.c
>>>>> index 4b590ea672d2..4e5e1af1b202 100644
>>>>> --- a/migration/savevm.c
>>>>> +++ b/migration/savevm.c
>>>>> @@ -3418,6 +3418,10 @@ void qmp_xen_save_devices_state(const char
>>>>> *filename, bool has_live, bool live,
>>>>> int saved_vm_running;
>>>>> int ret;
>>>>>
>>>>> + if (xen_migration_is_blocked(errp)) {
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> if (!has_live) {
>>>>> /* live default to true so old version of Xen tool stack can
>>>>> have a
>>>>> * successful live migration */
>>>>> diff --git a/system/vl.c b/system/vl.c
>>>>> index 9bd7664b85c7..21fa810637c3 100644
>>>>> --- a/system/vl.c
>>>>> +++ b/system/vl.c
>>>>> @@ -2392,6 +2392,7 @@ static int accelerator_set_property(void *opaque,
>>>>>
>>>>> static int do_configure_accelerator(void *opaque, QemuOpts *opts,
>>>>> Error **errp)
>>>>> {
>>>>> + Error *local_err = NULL;
>>>>> bool *p_init_failed = opaque;
>>>>> const char *acc = qemu_opt_get(opts, "accel");
>>>>> AccelClass *ac = accel_find(acc);
>>>>> @@ -2418,10 +2419,12 @@ static int do_configure_accelerator(void *opaque,
>>>>> QemuOpts *opts, Error **errp)
>>>>> accel,
>>>>> &error_fatal);
>>>>>
>>>>> - ret = accel_init_machine(accel, current_machine);
>>>>> + ret = accel_init_machine(accel, current_machine, &local_err);
>>>>> if (ret < 0) {
>>>>> - if (!qtest_with_kvm || ret != -ENOENT) {
>>>>> - error_report("failed to initialize %s: %s", acc,
>>>>> strerror(-ret));
>>>>> + if (qtest_with_kvm && ret == -ENOENT) {
>>>>> + error_free(local_err);
>>>>> + } else {
>>>>> + error_reportf_err(local_err, "failed to initialize %s: ",
>>>>> acc);
>>>>> }
>>>>> goto bad;
>>>>> }