diff options
95 files changed, 1828 insertions, 550 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 794bde23a4..3848d37a38 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3385,6 +3385,7 @@ F: rust/rustfmt.toml Rust-related patches CC here L: qemu-rust@nongnu.org +F: tests/docker/test-rust F: rust/ SLIRP diff --git a/block.c b/block.c index b4459dee2e..0ece805e41 100644 --- a/block.c +++ b/block.c @@ -1573,6 +1573,10 @@ static void update_flags_from_options(int *flags, QemuOpts *opts) if (qemu_opt_get_bool_del(opts, BDRV_OPT_AUTO_READ_ONLY, false)) { *flags |= BDRV_O_AUTO_RDONLY; } + + if (!qemu_opt_get_bool_del(opts, BDRV_OPT_ACTIVE, true)) { + *flags |= BDRV_O_INACTIVE; + } } static void update_options_from_flags(QDict *options, int flags) @@ -1800,6 +1804,11 @@ QemuOptsList bdrv_runtime_opts = { .help = "Ignore flush requests", }, { + .name = BDRV_OPT_ACTIVE, + .type = QEMU_OPT_BOOL, + .help = "Node is activated", + }, + { .name = BDRV_OPT_READ_ONLY, .type = QEMU_OPT_BOOL, .help = "Node is opened in read-only mode", @@ -3077,6 +3086,13 @@ bdrv_attach_child_common(BlockDriverState *child_bs, assert(child_class->get_parent_desc); GLOBAL_STATE_CODE(); + if (bdrv_is_inactive(child_bs) && (perm & ~BLK_PERM_CONSISTENT_READ)) { + g_autofree char *perm_names = bdrv_perm_names(perm); + error_setg(errp, "Permission '%s' unavailable on inactive node", + perm_names); + return NULL; + } + new_child = g_new(BdrvChild, 1); *new_child = (BdrvChild) { .bs = NULL, @@ -3183,6 +3199,11 @@ bdrv_attach_child_noperm(BlockDriverState *parent_bs, child_bs->node_name, child_name, parent_bs->node_name); return NULL; } + if (bdrv_is_inactive(child_bs) && !bdrv_is_inactive(parent_bs)) { + error_setg(errp, "Inactive '%s' can't be a %s child of active '%s'", + child_bs->node_name, child_name, parent_bs->node_name); + return NULL; + } bdrv_get_cumulative_perm(parent_bs, &perm, &shared_perm); bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL, @@ -6824,6 +6845,10 @@ void bdrv_init_with_whitelist(void) bdrv_init(); } +bool bdrv_is_inactive(BlockDriverState *bs) { + return bs->open_flags & BDRV_O_INACTIVE; +} + int bdrv_activate(BlockDriverState *bs, Error **errp) { BdrvChild *child, *parent; @@ -6955,7 +6980,8 @@ bdrv_has_bds_parent(BlockDriverState *bs, bool only_active) return false; } -static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) +static int GRAPH_RDLOCK +bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) { BdrvChild *child, *parent; int ret; @@ -6973,7 +6999,14 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) return 0; } - assert(!(bs->open_flags & BDRV_O_INACTIVE)); + /* + * Inactivating an already inactive node on user request is harmless, but if + * a child is already inactive before its parent, that's bad. + */ + if (bs->open_flags & BDRV_O_INACTIVE) { + assert(top_level); + return 0; + } /* Inactivate this node */ if (bs->drv->bdrv_inactivate) { @@ -6999,7 +7032,9 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) return -EPERM; } + bdrv_drained_begin(bs); bs->open_flags |= BDRV_O_INACTIVE; + bdrv_drained_end(bs); /* * Update permissions, they may differ for inactive nodes. @@ -7010,7 +7045,7 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) /* Recursively inactivate children */ QLIST_FOREACH(child, &bs->children, next) { - ret = bdrv_inactivate_recurse(child->bs); + ret = bdrv_inactivate_recurse(child->bs, false); if (ret < 0) { return ret; } @@ -7019,6 +7054,27 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) return 0; } +int bdrv_inactivate(BlockDriverState *bs, Error **errp) +{ + int ret; + + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + if (bdrv_has_bds_parent(bs, true)) { + error_setg(errp, "Node has active parent node"); + return -EPERM; + } + + ret = bdrv_inactivate_recurse(bs, true); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to inactivate node"); + return ret; + } + + return 0; +} + int bdrv_inactivate_all(void) { BlockDriverState *bs = NULL; @@ -7035,7 +7091,7 @@ int bdrv_inactivate_all(void) if (bdrv_has_bds_parent(bs, false)) { continue; } - ret = bdrv_inactivate_recurse(bs); + ret = bdrv_inactivate_recurse(bs, true); if (ret < 0) { bdrv_next_cleanup(&it); break; diff --git a/block/block-backend.c b/block/block-backend.c index c93a7525ad..9288f7e1c6 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -253,7 +253,7 @@ static bool blk_can_inactivate(BlockBackend *blk) * guest. For block job BBs that satisfy this, we can just allow * it. This is the case for mirror job source, which is required * by libvirt non-shared block migration. */ - if (!(blk->perm & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED))) { + if (!(blk->perm & ~BLK_PERM_CONSISTENT_READ)) { return true; } @@ -900,14 +900,24 @@ void blk_remove_bs(BlockBackend *blk) int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) { ThrottleGroupMember *tgm = &blk->public.throttle_group_member; + uint64_t perm, shared_perm; GLOBAL_STATE_CODE(); bdrv_ref(bs); bdrv_graph_wrlock(); + + if ((bs->open_flags & BDRV_O_INACTIVE) && blk_can_inactivate(blk)) { + blk->disable_perm = true; + perm = 0; + shared_perm = BLK_PERM_ALL; + } else { + perm = blk->perm; + shared_perm = blk->shared_perm; + } + blk->root = bdrv_root_attach_child(bs, "root", &child_root, BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, - blk->perm, blk->shared_perm, - blk, errp); + perm, shared_perm, blk, errp); bdrv_graph_wrunlock(); if (blk->root == NULL) { return -EPERM; @@ -1019,6 +1029,10 @@ DeviceState *blk_get_attached_dev(BlockBackend *blk) return blk->dev; } +/* + * The caller is responsible for releasing the value returned + * with g_free() after use. + */ static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) { DeviceState *dev = blk->dev; @@ -1033,15 +1047,15 @@ static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) return object_get_canonical_path(OBJECT(dev)) ?: g_strdup(""); } -/* - * Return the qdev ID, or if no ID is assigned the QOM path, of the block - * device attached to the BlockBackend. - */ char *blk_get_attached_dev_id(BlockBackend *blk) { return blk_get_attached_dev_id_or_path(blk, true); } +/* + * The caller is responsible for releasing the value returned + * with g_free() after use. + */ static char *blk_get_attached_dev_path(BlockBackend *blk) { return blk_get_attached_dev_id_or_path(blk, false); @@ -2134,10 +2148,10 @@ static void send_qmp_error_event(BlockBackend *blk, { IoOperationType optype; BlockDriverState *bs = blk_bs(blk); + g_autofree char *path = blk_get_attached_dev_path(blk); optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE; - qapi_event_send_block_io_error(blk_name(blk), - blk_get_attached_dev_path(blk), + qapi_event_send_block_io_error(path, blk_name(blk), bs ? bdrv_get_node_name(bs) : NULL, optype, action, blk_iostatus_is_enabled(blk), error == ENOSPC, strerror(error)); diff --git a/block/export/export.c b/block/export/export.c index 79c71ee245..f3bbf11070 100644 --- a/block/export/export.c +++ b/block/export/export.c @@ -75,6 +75,7 @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type) BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) { bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread; + bool allow_inactive = export->has_allow_inactive && export->allow_inactive; const BlockExportDriver *drv; BlockExport *exp = NULL; BlockDriverState *bs; @@ -138,14 +139,25 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) } } - /* - * Block exports are used for non-shared storage migration. Make sure - * that BDRV_O_INACTIVE is cleared and the image is ready for write - * access since the export could be available before migration handover. - * ctx was acquired in the caller. - */ bdrv_graph_rdlock_main_loop(); - bdrv_activate(bs, NULL); + if (allow_inactive) { + if (!drv->supports_inactive) { + error_setg(errp, "Export type does not support inactive exports"); + bdrv_graph_rdunlock_main_loop(); + goto fail; + } + } else { + /* + * Block exports are used for non-shared storage migration. Make sure + * that BDRV_O_INACTIVE is cleared and the image is ready for write + * access since the export could be available before migration handover. + */ + ret = bdrv_activate(bs, errp); + if (ret < 0) { + bdrv_graph_rdunlock_main_loop(); + goto fail; + } + } bdrv_graph_rdunlock_main_loop(); perm = BLK_PERM_CONSISTENT_READ; @@ -158,6 +170,9 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) if (!fixed_iothread) { blk_set_allow_aio_context_change(blk, true); } + if (allow_inactive) { + blk_set_force_allow_inactivate(blk); + } ret = blk_insert_bs(blk, bs, errp); if (ret < 0) { diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index 3f183b6b8e..ad7dc1de45 100644 --- a/block/monitor/block-hmp-cmds.c +++ b/block/monitor/block-hmp-cmds.c @@ -630,11 +630,12 @@ static void print_block_info(Monitor *mon, BlockInfo *info, } if (inserted) { - monitor_printf(mon, ": %s (%s%s%s)\n", + monitor_printf(mon, ": %s (%s%s%s%s)\n", inserted->file, inserted->drv, inserted->ro ? ", read-only" : "", - inserted->encrypted ? ", encrypted" : ""); + inserted->encrypted ? ", encrypted" : "", + inserted->active ? "" : ", inactive"); } else { monitor_printf(mon, ": [not inserted]\n"); } diff --git a/block/qapi.c b/block/qapi.c index bc04b14ad2..2c50a6bf3b 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -63,6 +63,7 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk, info->file = g_strdup(bs->filename); info->ro = bdrv_is_read_only(bs); info->drv = g_strdup(bs->drv->format_name); + info->active = !bdrv_is_inactive(bs); info->encrypted = bs->encrypted; info->cache = g_new(BlockdevCacheInfo, 1); diff --git a/block/replication.c b/block/replication.c index adac1cf207..0020f33843 100644 --- a/block/replication.c +++ b/block/replication.c @@ -576,7 +576,6 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, return; } bdrv_op_block_all(top_bs, s->blocker); - bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker); bdrv_graph_wrunlock(); diff --git a/block/vpc.c b/block/vpc.c index 9110d87746..0309e319f6 100644 --- a/block/vpc.c +++ b/block/vpc.c @@ -216,6 +216,39 @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, } } +/* + * Microsoft Virtual PC and Microsoft Hyper-V produce and read + * VHD image sizes differently. VPC will rely on CHS geometry, + * while Hyper-V and disk2vhd use the size specified in the footer. + * + * We use a couple of approaches to try and determine the correct method: + * look at the Creator App field, and look for images that have CHS + * geometry that is the maximum value. + * + * If the CHS geometry is the maximum CHS geometry, then we assume that + * the size is the footer->current_size to avoid truncation. Otherwise, + * we follow the table based on footer->creator_app: + * + * Known creator apps: + * 'vpc ' : CHS Virtual PC (uses disk geometry) + * 'qemu' : CHS QEMU (uses disk geometry) + * 'qem2' : current_size QEMU (uses current_size) + * 'win ' : current_size Hyper-V + * 'd2v ' : current_size Disk2vhd + * 'tap\0' : current_size XenServer + * 'CTXS' : current_size XenConverter + * 'wa\0\0': current_size Azure + * + * The user can override the table values via drive options, however + * even with an override we will still use current_size for images + * that have CHS geometry of the maximum size. + */ +static bool vpc_ignore_current_size(VHDFooter *footer) +{ + return !strncmp(footer->creator_app, "vpc ", 4) || + !strncmp(footer->creator_app, "qemu", 4); +} + static int vpc_open(BlockDriverState *bs, QDict *options, int flags, Error **errp) { @@ -304,36 +337,8 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags, bs->total_sectors = (int64_t) be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl; - /* Microsoft Virtual PC and Microsoft Hyper-V produce and read - * VHD image sizes differently. VPC will rely on CHS geometry, - * while Hyper-V and disk2vhd use the size specified in the footer. - * - * We use a couple of approaches to try and determine the correct method: - * look at the Creator App field, and look for images that have CHS - * geometry that is the maximum value. - * - * If the CHS geometry is the maximum CHS geometry, then we assume that - * the size is the footer->current_size to avoid truncation. Otherwise, - * we follow the table based on footer->creator_app: - * - * Known creator apps: - * 'vpc ' : CHS Virtual PC (uses disk geometry) - * 'qemu' : CHS QEMU (uses disk geometry) - * 'qem2' : current_size QEMU (uses current_size) - * 'win ' : current_size Hyper-V - * 'd2v ' : current_size Disk2vhd - * 'tap\0' : current_size XenServer - * 'CTXS' : current_size XenConverter - * - * The user can override the table values via drive options, however - * even with an override we will still use current_size for images - * that have CHS geometry of the maximum size. - */ - use_chs = (!!strncmp(footer->creator_app, "win ", 4) && - !!strncmp(footer->creator_app, "qem2", 4) && - !!strncmp(footer->creator_app, "d2v ", 4) && - !!strncmp(footer->creator_app, "CTXS", 4) && - !!memcmp(footer->creator_app, "tap", 4)) || s->force_use_chs; + /* Use CHS or current_size to determine the image size. */ + use_chs = vpc_ignore_current_size(footer) || s->force_use_chs; if (!use_chs || bs->total_sectors == VHD_MAX_GEOMETRY || s->force_use_sz) { bs->total_sectors = be64_to_cpu(footer->current_size) / diff --git a/blockdev.c b/blockdev.c index 7acca5ac48..1d1f27cfff 100644 --- a/blockdev.c +++ b/blockdev.c @@ -1497,6 +1497,22 @@ static void external_snapshot_action(TransactionAction *action, return; } + /* + * Older QEMU versions have allowed adding an active parent node to an + * inactive child node. This is unsafe in the general case, but there is an + * important use case, which is taking a VM snapshot with migration to file + * and then adding an external snapshot while the VM is still stopped and + * images are inactive. Requiring the user to explicitly create the overlay + * as inactive would break compatibility, so just do it automatically here + * to keep this working. + */ + if (bdrv_is_inactive(state->old_bs) && !bdrv_is_inactive(state->new_bs)) { + ret = bdrv_inactivate(state->new_bs, errp); + if (ret < 0) { + return; + } + } + ret = bdrv_append(state->new_bs, state->old_bs, errp); if (ret < 0) { return; @@ -3455,6 +3471,38 @@ void qmp_blockdev_del(const char *node_name, Error **errp) bdrv_unref(bs); } +void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp) +{ + int ret; + + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + if (!node_name) { + if (active) { + bdrv_activate_all(errp); + } else { + ret = bdrv_inactivate_all(); + if (ret < 0) { + error_setg_errno(errp, -ret, "Failed to inactivate all nodes"); + } + } + } else { + BlockDriverState *bs = bdrv_find_node(node_name); + if (!bs) { + error_setg(errp, "Failed to find node with node-name='%s'", + node_name); + return; + } + + if (active) { + bdrv_activate(bs, errp); + } else { + bdrv_inactivate(bs, errp); + } + } +} + static BdrvChild * GRAPH_RDLOCK bdrv_find_child(BlockDriverState *parent_bs, const char *child_name) { diff --git a/blockjob.c b/blockjob.c index e94a840d7f..32007f31a9 100644 --- a/blockjob.c +++ b/blockjob.c @@ -539,8 +539,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, goto fail; } - bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker); - if (!block_job_set_speed(job, speed, errp)) { goto fail; } diff --git a/bsd-user/main.c b/bsd-user/main.c index b2f6a9be2f..fdb160bed0 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -629,7 +629,6 @@ int main(int argc, char **argv) if (gdbstub) { gdbserver_start(gdbstub, &error_fatal); - gdb_handlesig(cpu, 0, NULL, NULL, 0); } cpu_loop(env); /* never exits */ diff --git a/bsd-user/signal-common.h b/bsd-user/signal-common.h index 77d7c7a78b..4e634e04a3 100644 --- a/bsd-user/signal-common.h +++ b/bsd-user/signal-common.h @@ -42,7 +42,6 @@ void process_pending_signals(CPUArchState *env); void queue_signal(CPUArchState *env, int sig, int si_type, target_siginfo_t *info); void signal_init(void); -int target_to_host_signal(int sig); void target_to_host_sigset(sigset_t *d, const target_sigset_t *s); /* diff --git a/bsd-user/signal.c b/bsd-user/signal.c index b4e1458237..ff2ccbbf60 100644 --- a/bsd-user/signal.c +++ b/bsd-user/signal.c @@ -24,6 +24,7 @@ #include "user/cpu_loop.h" #include "exec/page-protection.h" #include "user/page-protection.h" +#include "user/signal.h" #include "user/tswap-target.h" #include "gdbstub/user.h" #include "signal-common.h" @@ -50,6 +51,8 @@ static inline int sas_ss_flags(TaskState *ts, unsigned long sp) on_sig_stack(ts, sp) ? SS_ONSTACK : 0; } +int host_interrupt_signal = SIGRTMAX; + /* * The BSD ABIs use the same signal numbers across all the CPU architectures, so * (unlike Linux) these functions are just the identity mapping. This might not @@ -490,6 +493,12 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc) uintptr_t pc = 0; bool sync_sig = false; + if (host_sig == host_interrupt_signal) { + ts->signal_pending = 1; + cpu_exit(thread_cpu); + return; + } + /* * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special * handling wrt signal blocking and unwinding. @@ -853,6 +862,9 @@ void signal_init(void) for (i = 1; i <= TARGET_NSIG; i++) { host_sig = target_to_host_signal(i); + if (host_sig == host_interrupt_signal) { + continue; + } sigaction(host_sig, NULL, &oact); if (oact.sa_sigaction == (void *)SIG_IGN) { sigact_table[i - 1]._sa_handler = TARGET_SIG_IGN; @@ -871,6 +883,7 @@ void signal_init(void) sigaction(host_sig, &act, NULL); } } + sigaction(host_interrupt_signal, &act, NULL); } static void handle_pending_signal(CPUArchState *env, int sig, diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst index 7c61d0ba16..4c86620f6d 100644 --- a/docs/about/deprecated.rst +++ b/docs/about/deprecated.rst @@ -68,6 +68,19 @@ configurations (e.g. -smp drawers=1,books=1,clusters=1 for x86 PC machine) is marked deprecated since 9.0, users have to ensure that all the topology members described with -smp are supported by the target machine. +``-old-param`` option for booting Arm kernels via param_struct (since 10.0) +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``-old-param`` command line option is specific to Arm targets: +it is used when directly booting a guest kernel to pass it the +command line and other information via the old ``param_struct`` ABI, +rather than the newer ATAGS or DTB mechanisms. This option was only +ever needed to support ancient kernels on some old board types +like the ``akita`` or ``terrier``; it has been deprecated in the +kernel since 2001. None of the board types QEMU supports need +``param_struct`` support, so this option has been deprecated and will +be removed in a future QEMU version. + User-mode emulator command line arguments ----------------------------------------- @@ -211,6 +224,27 @@ Keeping 32-bit host support alive is a substantial burden for the QEMU project. Thus QEMU will in future drop the support for all 32-bit host systems. +linux-user mode CPUs +-------------------- + +iwMMXt emulation and the ``pxa`` CPUs (since 10.0) +'''''''''''''''''''''''''''''''''''''''''''''''''' + +The ``pxa`` CPU family (``pxa250``, ``pxa255``, ``pxa260``, +``pxa261``, ``pxa262``, ``pxa270-a0``, ``pxa270-a1``, ``pxa270``, +``pxa270-b0``, ``pxa270-b1``, ``pxa270-c0``, ``pxa270-c5``) are no +longer used in system emulation, because all the machine types which +used these CPUs were removed in the QEMU 9.2 release. These CPUs can +now only be used in linux-user mode, and to do that you would have to +explicitly select one of these CPUs with the ``-cpu`` command line +option or the ``QEMU_CPU`` environment variable. + +We don't believe that anybody is using the iwMMXt emulation, and we do +not have any tests to validate it or any real hardware or similar +known-good implementation to test against. GCC is in the process of +dropping their support for iwMMXt codegen. These CPU types are +therefore deprecated in QEMU, and will be removed in a future release. + System emulator CPUs -------------------- diff --git a/docs/user/main.rst b/docs/user/main.rst index 80a77f0a0c..9a1c60448c 100644 --- a/docs/user/main.rst +++ b/docs/user/main.rst @@ -54,7 +54,7 @@ Command line options :: - qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g port] [-B offset] [-R size] program [arguments...] + qemu-i386 [-h] [-d] [-L path] [-s size] [-cpu model] [-g endpoint] [-B offset] [-R size] program [arguments...] ``-h`` Print the help @@ -91,8 +91,18 @@ Debug options: Activate logging of the specified items (use '-d help' for a list of log items) -``-g port`` - Wait gdb connection to port +``-g endpoint`` + Wait gdb connection to a port (e.g., ``1234``) or a unix socket (e.g., + ``/tmp/qemu.sock``). + + If a unix socket path contains single ``%d`` placeholder (e.g., + ``/tmp/qemu-%d.sock``), it is replaced by the emulator PID, which is useful + when passing this option via the ``QEMU_GDB`` environment variable to a + multi-process application. + + If the endpoint address is followed by ``,suspend=n`` (e.g., + ``1234,suspend=n``), then the emulated program starts without waiting for a + connection, which can be established at any later point in time. ``-one-insn-per-tb`` Run the emulation with one guest instruction per translation block. diff --git a/gdbstub/user.c b/gdbstub/user.c index c2bdfc3d49..3730f32c41 100644 --- a/gdbstub/user.c +++ b/gdbstub/user.c @@ -22,6 +22,7 @@ #include "gdbstub/user.h" #include "gdbstub/enums.h" #include "hw/core/cpu.h" +#include "user/signal.h" #include "trace.h" #include "internals.h" @@ -315,33 +316,20 @@ static bool gdb_accept_socket(int gdb_fd) return true; } -static int gdbserver_open_socket(const char *path) +static int gdbserver_open_socket(const char *path, Error **errp) { - struct sockaddr_un sockaddr = {}; - int fd, ret; + g_autoptr(GString) buf = g_string_new(""); + char *pid_placeholder; - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { - perror("create socket"); - return -1; + pid_placeholder = strstr(path, "%d"); + if (pid_placeholder != NULL) { + g_string_append_len(buf, path, pid_placeholder - path); + g_string_append_printf(buf, "%d", qemu_get_thread_id()); + g_string_append(buf, pid_placeholder + 2); + path = buf->str; } - sockaddr.sun_family = AF_UNIX; - pstrcpy(sockaddr.sun_path, sizeof(sockaddr.sun_path) - 1, path); - ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); - if (ret < 0) { - perror("bind socket"); - close(fd); - return -1; - } - ret = listen(fd, 1); - if (ret < 0) { - perror("listen socket"); - close(fd); - return -1; - } - - return fd; + return unix_listen(path, errp); } static bool gdb_accept_tcp(int gdb_fd) @@ -406,32 +394,122 @@ static int gdbserver_open_port(int port, Error **errp) return fd; } -bool gdbserver_start(const char *port_or_path, Error **errp) +static bool gdbserver_accept(int port, int gdb_fd, const char *path) { - int port = g_ascii_strtoull(port_or_path, NULL, 10); + bool ret; + + if (port > 0) { + ret = gdb_accept_tcp(gdb_fd); + } else { + ret = gdb_accept_socket(gdb_fd); + if (ret) { + gdbserver_user_state.socket_path = g_strdup(path); + } + } + + if (!ret) { + close(gdb_fd); + } + + return ret; +} + +struct { + int port; int gdb_fd; + char *path; +} gdbserver_args; + +static void do_gdb_handlesig(CPUState *cs, run_on_cpu_data arg) +{ + int sig; + + sig = target_to_host_signal(gdb_handlesig(cs, 0, NULL, NULL, 0)); + if (sig >= 1 && sig < NSIG) { + qemu_kill_thread(gdb_get_cpu_index(cs), sig); + } +} + +static void *gdbserver_accept_thread(void *arg) +{ + if (gdbserver_accept(gdbserver_args.port, gdbserver_args.gdb_fd, + gdbserver_args.path)) { + CPUState *cs = first_cpu; + + async_safe_run_on_cpu(cs, do_gdb_handlesig, RUN_ON_CPU_NULL); + qemu_kill_thread(gdb_get_cpu_index(cs), host_interrupt_signal); + } + + g_free(gdbserver_args.path); + gdbserver_args.path = NULL; + + return NULL; +} +#define USAGE "\nUsage: -g {port|path}[,suspend={y|n}]" + +bool gdbserver_start(const char *args, Error **errp) +{ + g_auto(GStrv) argv = g_strsplit(args, ",", 0); + const char *port_or_path = NULL; + bool suspend = true; + int gdb_fd, port; + GStrv arg; + + for (arg = argv; *arg; arg++) { + g_auto(GStrv) tokens = g_strsplit(*arg, "=", 2); + + if (g_strcmp0(tokens[0], "suspend") == 0) { + if (tokens[1] == NULL) { + error_setg(errp, + "gdbstub: missing \"suspend\" option value" USAGE); + return false; + } else if (!qapi_bool_parse(tokens[0], tokens[1], + &suspend, errp)) { + return false; + } + } else { + if (port_or_path) { + error_setg(errp, "gdbstub: unknown option \"%s\"" USAGE, *arg); + return false; + } + port_or_path = *arg; + } + } + if (!port_or_path) { + error_setg(errp, "gdbstub: port or path not specified" USAGE); + return false; + } + + port = g_ascii_strtoull(port_or_path, NULL, 10); if (port > 0) { gdb_fd = gdbserver_open_port(port, errp); } else { - gdb_fd = gdbserver_open_socket(port_or_path); + gdb_fd = gdbserver_open_socket(port_or_path, errp); } - if (gdb_fd < 0) { return false; } - if (port > 0 && gdb_accept_tcp(gdb_fd)) { - return true; - } else if (gdb_accept_socket(gdb_fd)) { - gdbserver_user_state.socket_path = g_strdup(port_or_path); + if (suspend) { + if (gdbserver_accept(port, gdb_fd, port_or_path)) { + gdb_handlesig(first_cpu, 0, NULL, NULL, 0); + return true; + } else { + error_setg(errp, "gdbstub: failed to accept connection"); + return false; + } + } else { + QemuThread thread; + + gdbserver_args.port = port; + gdbserver_args.gdb_fd = gdb_fd; + gdbserver_args.path = g_strdup(port_or_path); + qemu_thread_create(&thread, "gdb-accept", + &gdbserver_accept_thread, NULL, + QEMU_THREAD_DETACHED); return true; } - - /* gone wrong */ - close(gdb_fd); - error_setg(errp, "gdbstub: failed to accept connection"); - return false; } void gdbserver_fork_start(void) diff --git a/hw/arm/boot.c b/hw/arm/boot.c index cbc24356fc..42c18355e8 100644 --- a/hw/arm/boot.c +++ b/hw/arm/boot.c @@ -432,13 +432,12 @@ out: return ret; } -static void fdt_add_psci_node(void *fdt) +static void fdt_add_psci_node(void *fdt, ARMCPU *armcpu) { uint32_t cpu_suspend_fn; uint32_t cpu_off_fn; uint32_t cpu_on_fn; uint32_t migrate_fn; - ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0)); const char *psci_method; int64_t psci_conduit; int rc; @@ -512,7 +511,8 @@ static void fdt_add_psci_node(void *fdt) } int arm_load_dtb(hwaddr addr, const struct arm_boot_info *binfo, - hwaddr addr_limit, AddressSpace *as, MachineState *ms) + hwaddr addr_limit, AddressSpace *as, MachineState *ms, + ARMCPU *cpu) { void *fdt = NULL; int size, rc, n = 0; @@ -655,7 +655,7 @@ int arm_load_dtb(hwaddr addr, const struct arm_boot_info *binfo, } } - fdt_add_psci_node(fdt); + fdt_add_psci_node(fdt, cpu); if (binfo->modify_dtb) { binfo->modify_dtb(binfo, fdt); @@ -1327,7 +1327,8 @@ void arm_load_kernel(ARMCPU *cpu, MachineState *ms, struct arm_boot_info *info) * decided whether to enable PSCI and set the psci-conduit CPU properties. */ if (!info->skip_dtb_autoload && have_dtb(info)) { - if (arm_load_dtb(info->dtb_start, info, info->dtb_limit, as, ms) < 0) { + if (arm_load_dtb(info->dtb_start, info, info->dtb_limit, + as, ms, cpu) < 0) { exit(1); } } diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c index 88b9ccff49..dc86338b3a 100644 --- a/hw/arm/fsl-imx6.c +++ b/hw/arm/fsl-imx6.c @@ -117,6 +117,8 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) uint16_t i; qemu_irq irq; unsigned int smp_cpus = ms->smp.cpus; + DeviceState *mpcore = DEVICE(&s->a9mpcore); + DeviceState *gic; if (smp_cpus > FSL_IMX6_NUM_CPUS) { error_setg(errp, "%s: Only %d CPUs are supported (%d requested)", @@ -143,21 +145,21 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) } } - object_property_set_int(OBJECT(&s->a9mpcore), "num-cpu", smp_cpus, - &error_abort); + object_property_set_int(OBJECT(mpcore), "num-cpu", smp_cpus, &error_abort); - object_property_set_int(OBJECT(&s->a9mpcore), "num-irq", + object_property_set_int(OBJECT(mpcore), "num-irq", FSL_IMX6_MAX_IRQ + GIC_INTERNAL, &error_abort); - if (!sysbus_realize(SYS_BUS_DEVICE(&s->a9mpcore), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(mpcore), errp)) { return; } - sysbus_mmio_map(SYS_BUS_DEVICE(&s->a9mpcore), 0, FSL_IMX6_A9MPCORE_ADDR); + sysbus_mmio_map(SYS_BUS_DEVICE(mpcore), 0, FSL_IMX6_A9MPCORE_ADDR); + gic = mpcore; for (i = 0; i < smp_cpus; i++) { - sysbus_connect_irq(SYS_BUS_DEVICE(&s->a9mpcore), i, + sysbus_connect_irq(SYS_BUS_DEVICE(gic), i, qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_IRQ)); - sysbus_connect_irq(SYS_BUS_DEVICE(&s->a9mpcore), i + smp_cpus, + sysbus_connect_irq(SYS_BUS_DEVICE(gic), i + smp_cpus, qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_FIQ)); } @@ -195,8 +197,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - serial_table[i].irq)); + qdev_get_gpio_in(gic, serial_table[i].irq)); } s->gpt.ccm = IMX_CCM(&s->ccm); @@ -207,8 +208,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpt), 0, FSL_IMX6_GPT_ADDR); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - FSL_IMX6_GPT_IRQ)); + qdev_get_gpio_in(gic, FSL_IMX6_GPT_IRQ)); /* Initialize all EPIT timers */ for (i = 0; i < FSL_IMX6_NUM_EPITS; i++) { @@ -228,8 +228,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->epit[i]), 0, epit_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->epit[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - epit_table[i].irq)); + qdev_get_gpio_in(gic, epit_table[i].irq)); } /* Initialize all I2C */ @@ -249,8 +248,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, i2c_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - i2c_table[i].irq)); + qdev_get_gpio_in(gic, i2c_table[i].irq)); } /* Initialize all GPIOs */ @@ -307,11 +305,9 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - gpio_table[i].irq_low)); + qdev_get_gpio_in(gic, gpio_table[i].irq_low)); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - gpio_table[i].irq_high)); + qdev_get_gpio_in(gic, gpio_table[i].irq_high)); } /* Initialize all SDHC */ @@ -338,8 +334,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) } sysbus_mmio_map(SYS_BUS_DEVICE(&s->esdhc[i]), 0, esdhc_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->esdhc[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - esdhc_table[i].irq)); + qdev_get_gpio_in(gic, esdhc_table[i].irq)); } /* USB */ @@ -360,8 +355,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, FSL_IMX6_USBOH3_USB_ADDR + i * 0x200); sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - FSL_IMX6_USBn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6_USBn_IRQ[i])); } /* Initialize all ECSPI */ @@ -384,8 +378,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, spi_table[i].addr); sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - spi_table[i].irq)); + qdev_get_gpio_in(gic, spi_table[i].irq)); } object_property_set_uint(OBJECT(&s->eth), "phy-num", s->phy_num, @@ -396,11 +389,9 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) } sysbus_mmio_map(SYS_BUS_DEVICE(&s->eth), 0, FSL_IMX6_ENET_ADDR); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - FSL_IMX6_ENET_MAC_IRQ)); + qdev_get_gpio_in(gic, FSL_IMX6_ENET_MAC_IRQ)); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth), 1, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - FSL_IMX6_ENET_MAC_1588_IRQ)); + qdev_get_gpio_in(gic, FSL_IMX6_ENET_MAC_1588_IRQ)); /* * SNVS @@ -427,8 +418,7 @@ static void fsl_imx6_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, FSL_IMX6_WDOGn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a9mpcore), - FSL_IMX6_WDOGn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6_WDOGn_IRQ[i])); } /* diff --git a/hw/arm/fsl-imx6ul.c b/hw/arm/fsl-imx6ul.c index 79e4847953..34c4aa15cd 100644 --- a/hw/arm/fsl-imx6ul.c +++ b/hw/arm/fsl-imx6ul.c @@ -157,10 +157,12 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) { MachineState *ms = MACHINE(qdev_get_machine()); FslIMX6ULState *s = FSL_IMX6UL(dev); + DeviceState *mpcore = DEVICE(&s->a7mpcore); int i; char name[NAME_SIZE]; - SysBusDevice *sbd; - DeviceState *d; + DeviceState *gic; + SysBusDevice *gicsbd; + DeviceState *cpu; if (ms->smp.cpus > 1) { error_setg(errp, "%s: Only a single CPU is supported (%d requested)", @@ -173,19 +175,19 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) /* * A7MPCORE */ - object_property_set_int(OBJECT(&s->a7mpcore), "num-cpu", 1, &error_abort); - object_property_set_int(OBJECT(&s->a7mpcore), "num-irq", + object_property_set_int(OBJECT(mpcore), "num-cpu", 1, &error_abort); + object_property_set_int(OBJECT(mpcore), "num-irq", FSL_IMX6UL_MAX_IRQ + GIC_INTERNAL, &error_abort); - sysbus_realize(SYS_BUS_DEVICE(&s->a7mpcore), &error_abort); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->a7mpcore), 0, FSL_IMX6UL_A7MPCORE_ADDR); + sysbus_realize(SYS_BUS_DEVICE(mpcore), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(mpcore), 0, FSL_IMX6UL_A7MPCORE_ADDR); - sbd = SYS_BUS_DEVICE(&s->a7mpcore); - d = DEVICE(&s->cpu); - - sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(d, ARM_CPU_IRQ)); - sysbus_connect_irq(sbd, 1, qdev_get_gpio_in(d, ARM_CPU_FIQ)); - sysbus_connect_irq(sbd, 2, qdev_get_gpio_in(d, ARM_CPU_VIRQ)); - sysbus_connect_irq(sbd, 3, qdev_get_gpio_in(d, ARM_CPU_VFIQ)); + gic = mpcore; + gicsbd = SYS_BUS_DEVICE(gic); + cpu = DEVICE(&s->cpu); + sysbus_connect_irq(gicsbd, 0, qdev_get_gpio_in(cpu, ARM_CPU_IRQ)); + sysbus_connect_irq(gicsbd, 1, qdev_get_gpio_in(cpu, ARM_CPU_FIQ)); + sysbus_connect_irq(gicsbd, 2, qdev_get_gpio_in(cpu, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicsbd, 3, qdev_get_gpio_in(cpu, ARM_CPU_VFIQ)); /* * A7MPCORE DAP @@ -244,8 +246,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_GPTn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_GPTn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_GPTn_IRQ[i])); } /* @@ -269,8 +270,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_EPITn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->epit[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_EPITn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_EPITn_IRQ[i])); } /* @@ -307,12 +307,10 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_GPIOn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_GPIOn_LOW_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_GPIOn_LOW_IRQ[i])); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_GPIOn_HIGH_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_GPIOn_HIGH_IRQ[i])); } /* @@ -366,8 +364,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_SPIn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_SPIn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_SPIn_IRQ[i])); } /* @@ -392,8 +389,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, FSL_IMX6UL_I2Cn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_I2Cn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_I2Cn_IRQ[i])); } /* @@ -430,8 +426,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_UARTn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_UARTn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_UARTn_IRQ[i])); } /* @@ -480,12 +475,10 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_ENETn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_ENETn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_ENETn_IRQ[i])); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth[i]), 1, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_ENETn_TIMER_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_ENETn_TIMER_IRQ[i])); } /* @@ -521,8 +514,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, FSL_IMX6UL_USB02_USBn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_USBn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_USBn_IRQ[i])); } /* @@ -547,8 +539,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) FSL_IMX6UL_USDHCn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->usdhc[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_USDHCn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_USDHCn_IRQ[i])); } /* @@ -580,8 +571,7 @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, FSL_IMX6UL_WDOGn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX6UL_WDOGn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX6UL_WDOGn_IRQ[i])); } /* diff --git a/hw/arm/fsl-imx7.c b/hw/arm/fsl-imx7.c index 004bf49937..3374018cde 100644 --- a/hw/arm/fsl-imx7.c +++ b/hw/arm/fsl-imx7.c @@ -166,7 +166,8 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) { MachineState *ms = MACHINE(qdev_get_machine()); FslIMX7State *s = FSL_IMX7(dev); - Object *o; + DeviceState *mpcore = DEVICE(&s->a7mpcore); + DeviceState *gic; int i; qemu_irq irq; char name[NAME_SIZE]; @@ -182,7 +183,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) * CPUs */ for (i = 0; i < smp_cpus; i++) { - o = OBJECT(&s->cpu[i]); + Object *o = OBJECT(&s->cpu[i]); /* On uniprocessor, the CBAR is set to 0 */ if (smp_cpus > 1) { @@ -205,16 +206,15 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) /* * A7MPCORE */ - object_property_set_int(OBJECT(&s->a7mpcore), "num-cpu", smp_cpus, - &error_abort); - object_property_set_int(OBJECT(&s->a7mpcore), "num-irq", + object_property_set_int(OBJECT(mpcore), "num-cpu", smp_cpus, &error_abort); + object_property_set_int(OBJECT(mpcore), "num-irq", FSL_IMX7_MAX_IRQ + GIC_INTERNAL, &error_abort); + sysbus_realize(SYS_BUS_DEVICE(mpcore), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(mpcore), 0, FSL_IMX7_A7MPCORE_ADDR); - sysbus_realize(SYS_BUS_DEVICE(&s->a7mpcore), &error_abort); - sysbus_mmio_map(SYS_BUS_DEVICE(&s->a7mpcore), 0, FSL_IMX7_A7MPCORE_ADDR); - + gic = mpcore; for (i = 0; i < smp_cpus; i++) { - SysBusDevice *sbd = SYS_BUS_DEVICE(&s->a7mpcore); + SysBusDevice *sbd = SYS_BUS_DEVICE(gic); DeviceState *d = DEVICE(qemu_get_cpu(i)); irq = qdev_get_gpio_in(d, ARM_CPU_IRQ); @@ -255,8 +255,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_realize(SYS_BUS_DEVICE(&s->gpt[i]), &error_abort); sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpt[i]), 0, FSL_IMX7_GPTn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_GPTn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_GPTn_IRQ[i])); } /* @@ -298,12 +297,10 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) FSL_IMX7_GPIOn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_GPIOn_LOW_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_GPIOn_LOW_IRQ[i])); sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_GPIOn_HIGH_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_GPIOn_HIGH_IRQ[i])); } /* @@ -355,8 +352,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, FSL_IMX7_SPIn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_SPIn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_SPIn_IRQ[i])); } /* @@ -381,8 +377,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, FSL_IMX7_I2Cn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_I2Cn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_I2Cn_IRQ[i])); } /* @@ -416,7 +411,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, FSL_IMX7_UARTn_ADDR[i]); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_UARTn_IRQ[i]); + irq = qdev_get_gpio_in(gic, FSL_IMX7_UARTn_IRQ[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, irq); } @@ -454,9 +449,9 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->eth[i]), 0, FSL_IMX7_ENETn_ADDR[i]); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_ENET_IRQ(i, 0)); + irq = qdev_get_gpio_in(gic, FSL_IMX7_ENET_IRQ(i, 0)); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth[i]), 0, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_ENET_IRQ(i, 3)); + irq = qdev_get_gpio_in(gic, FSL_IMX7_ENET_IRQ(i, 3)); sysbus_connect_irq(SYS_BUS_DEVICE(&s->eth[i]), 1, irq); } @@ -483,7 +478,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, FSL_IMX7_USDHCn_ADDR[i]); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_USDHCn_IRQ[i]); + irq = qdev_get_gpio_in(gic, FSL_IMX7_USDHCn_IRQ[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->usdhc[i]), 0, irq); } @@ -522,8 +517,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, FSL_IMX7_WDOGn_ADDR[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, - qdev_get_gpio_in(DEVICE(&s->a7mpcore), - FSL_IMX7_WDOGn_IRQ[i])); + qdev_get_gpio_in(gic, FSL_IMX7_WDOGn_IRQ[i])); } /* @@ -606,11 +600,11 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTD_MSI_IRQ); qdev_connect_gpio_out(DEVICE(&s->pcie4_msi_irq), 0, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTA_IRQ); + irq = qdev_get_gpio_in(gic, FSL_IMX7_PCI_INTA_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTB_IRQ); + irq = qdev_get_gpio_in(gic, FSL_IMX7_PCI_INTB_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, irq); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_PCI_INTC_IRQ); + irq = qdev_get_gpio_in(gic, FSL_IMX7_PCI_INTC_IRQ); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, irq); irq = qdev_get_gpio_in(DEVICE(&s->pcie4_msi_irq), 0); sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, irq); @@ -643,7 +637,7 @@ static void fsl_imx7_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, FSL_IMX7_USBn_ADDR[i]); - irq = qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX7_USBn_IRQ[i]); + irq = qdev_get_gpio_in(gic, FSL_IMX7_USBn_IRQ[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i]), 0, irq); snprintf(name, NAME_SIZE, "usbmisc%d", i); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 3448200e3b..4a5a9666e9 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -1746,7 +1746,7 @@ void virt_machine_done(Notifier *notifier, void *data) vms->memmap[VIRT_PLATFORM_BUS].size, vms->irqmap[VIRT_PLATFORM_BUS]); } - if (arm_load_dtb(info->dtb_start, info, info->dtb_limit, as, ms) < 0) { + if (arm_load_dtb(info->dtb_start, info, info->dtb_limit, as, ms, cpu) < 0) { exit(1); } diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index e0acce89e1..a1829e3abd 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -1562,15 +1562,6 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) error_setg(errp, "ioeventfd is required for iothread"); return false; } - - /* - * If ioeventfd is (re-)enabled while the guest is running there could - * be block jobs that can conflict. - */ - if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { - error_prepend(errp, "cannot start virtio-blk ioeventfd: "); - return false; - } } s->vq_aio_context = g_new(AioContext *, conf->num_queues); diff --git a/hw/cpu/a15mpcore.c b/hw/cpu/a15mpcore.c index 3b0897e54e..d24ab0a6ab 100644 --- a/hw/cpu/a15mpcore.c +++ b/hw/cpu/a15mpcore.c @@ -164,17 +164,14 @@ static void a15mp_priv_class_init(ObjectClass *klass, void *data) /* We currently have no saveable state */ } -static const TypeInfo a15mp_priv_info = { - .name = TYPE_A15MPCORE_PRIV, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(A15MPPrivState), - .instance_init = a15mp_priv_initfn, - .class_init = a15mp_priv_class_init, +static const TypeInfo a15mp_types[] = { + { + .name = TYPE_A15MPCORE_PRIV, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(A15MPPrivState), + .instance_init = a15mp_priv_initfn, + .class_init = a15mp_priv_class_init, + }, }; -static void a15mp_register_types(void) -{ - type_register_static(&a15mp_priv_info); -} - -type_init(a15mp_register_types) +DEFINE_TYPES(a15mp_types) diff --git a/hw/cpu/a9mpcore.c b/hw/cpu/a9mpcore.c index 9671585b5f..25416c5032 100644 --- a/hw/cpu/a9mpcore.c +++ b/hw/cpu/a9mpcore.c @@ -177,17 +177,14 @@ static void a9mp_priv_class_init(ObjectClass *klass, void *data) device_class_set_props(dc, a9mp_priv_properties); } -static const TypeInfo a9mp_priv_info = { - .name = TYPE_A9MPCORE_PRIV, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(A9MPPrivState), - .instance_init = a9mp_priv_initfn, - .class_init = a9mp_priv_class_init, +static const TypeInfo a9mp_types[] = { + { + .name = TYPE_A9MPCORE_PRIV, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(A9MPPrivState), + .instance_init = a9mp_priv_initfn, + .class_init = a9mp_priv_class_init, + }, }; -static void a9mp_register_types(void) -{ - type_register_static(&a9mp_priv_info); -} - -type_init(a9mp_register_types) +DEFINE_TYPES(a9mp_types) diff --git a/hw/cpu/arm11mpcore.c b/hw/cpu/arm11mpcore.c index 94861a06d9..b56bee6d54 100644 --- a/hw/cpu/arm11mpcore.c +++ b/hw/cpu/arm11mpcore.c @@ -152,17 +152,14 @@ static void mpcore_priv_class_init(ObjectClass *klass, void *data) device_class_set_props(dc, mpcore_priv_properties); } -static const TypeInfo mpcore_priv_info = { - .name = TYPE_ARM11MPCORE_PRIV, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(ARM11MPCorePriveState), - .instance_init = mpcore_priv_initfn, - .class_init = mpcore_priv_class_init, +static const TypeInfo arm11mp_types[] = { + { + .name = TYPE_ARM11MPCORE_PRIV, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ARM11MPCorePriveState), + .instance_init = mpcore_priv_initfn, + .class_init = mpcore_priv_class_init, + }, }; -static void arm11mpcore_register_types(void) -{ - type_register_static(&mpcore_priv_info); -} - -type_init(arm11mpcore_register_types) +DEFINE_TYPES(arm11mp_types) diff --git a/hw/cpu/realview_mpcore.c b/hw/cpu/realview_mpcore.c index 4268735e3a..b140888618 100644 --- a/hw/cpu/realview_mpcore.c +++ b/hw/cpu/realview_mpcore.c @@ -14,7 +14,6 @@ #include "hw/cpu/arm11mpcore.h" #include "hw/intc/realview_gic.h" #include "hw/irq.h" -#include "hw/qdev-properties.h" #include "qom/object.h" #define TYPE_REALVIEW_MPCORE_RIRQ "realview_mpcore" @@ -68,7 +67,6 @@ static void realview_mpcore_realize(DeviceState *dev, Error **errp) int n; int i; - qdev_prop_set_uint32(priv, "num-cpu", s->num_cpu); if (!sysbus_realize(SYS_BUS_DEVICE(&s->priv), errp)) { return; } @@ -100,6 +98,7 @@ static void mpcore_rirq_init(Object *obj) int i; object_initialize_child(obj, "a11priv", &s->priv, TYPE_ARM11MPCORE_PRIV); + object_property_add_alias(obj, "num-cpu", OBJECT(&s->priv), "num-cpu"); privbusdev = SYS_BUS_DEVICE(&s->priv); sysbus_init_mmio(sbd, sysbus_mmio_get_region(privbusdev, 0)); @@ -108,29 +107,21 @@ static void mpcore_rirq_init(Object *obj) } } -static const Property mpcore_rirq_properties[] = { - DEFINE_PROP_UINT32("num-cpu", mpcore_rirq_state, num_cpu, 1), -}; - static void mpcore_rirq_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = realview_mpcore_realize; - device_class_set_props(dc, mpcore_rirq_properties); } -static const TypeInfo mpcore_rirq_info = { - .name = TYPE_REALVIEW_MPCORE_RIRQ, - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(mpcore_rirq_state), - .instance_init = mpcore_rirq_init, - .class_init = mpcore_rirq_class_init, +static const TypeInfo realview_mpcore_types[] = { + { + .name = TYPE_REALVIEW_MPCORE_RIRQ, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mpcore_rirq_state), + .instance_init = mpcore_rirq_init, + .class_init = mpcore_rirq_class_init, + }, }; -static void realview_mpcore_register_types(void) -{ - type_register_static(&mpcore_rirq_info); -} - -type_init(realview_mpcore_register_types) +DEFINE_TYPES(realview_mpcore_types) diff --git a/hw/net/cadence_gem.c b/hw/net/cadence_gem.c index f744054a6d..80fbbacc1e 100644 --- a/hw/net/cadence_gem.c +++ b/hw/net/cadence_gem.c @@ -909,8 +909,8 @@ static int get_queue_from_screen(CadenceGEMState *s, uint8_t *rxbuf_ptr, /* Compare A, B, C */ for (j = 0; j < 3; j++) { - uint32_t cr0, cr1, mask, compare; - uint16_t rx_cmp; + uint32_t cr0, cr1, mask, compare, disable_mask; + uint32_t rx_cmp; int offset; int cr_idx = extract32(reg, R_SCREENING_TYPE2_REG0_COMPARE_A_SHIFT + j * 6, R_SCREENING_TYPE2_REG0_COMPARE_A_LENGTH); @@ -946,9 +946,25 @@ static int get_queue_from_screen(CadenceGEMState *s, uint8_t *rxbuf_ptr, break; } - rx_cmp = rxbuf_ptr[offset] << 8 | rxbuf_ptr[offset]; - mask = FIELD_EX32(cr0, TYPE2_COMPARE_0_WORD_0, MASK_VALUE); - compare = FIELD_EX32(cr0, TYPE2_COMPARE_0_WORD_0, COMPARE_VALUE); + disable_mask = + FIELD_EX32(cr1, TYPE2_COMPARE_0_WORD_1, DISABLE_MASK); + if (disable_mask) { + /* + * If disable_mask is set, mask_value is used as an + * additional 2 byte Compare Value; that is equivalent + * to using the whole cr0 register as the comparison value. + * Load 32 bits of data from rx_buf, and set mask to + * all-ones so we compare all 32 bits. + */ + rx_cmp = ldl_le_p(rxbuf_ptr + offset); + mask = 0xFFFFFFFF; + compare = cr0; + } else { + rx_cmp = lduw_le_p(rxbuf_ptr + offset); + mask = FIELD_EX32(cr0, TYPE2_COMPARE_0_WORD_0, MASK_VALUE); + compare = + FIELD_EX32(cr0, TYPE2_COMPARE_0_WORD_0, COMPARE_VALUE); + } if ((rx_cmp & mask) == (compare & mask)) { matched = true; diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c index df2e837632..503d441b48 100644 --- a/hw/ppc/spapr_rtas.c +++ b/hw/ppc/spapr_rtas.c @@ -565,7 +565,6 @@ static bool spapr_qtest_callback(CharBackend *chr, gchar **words) g_assert(rc == 0); res = qtest_rtas_call(words[1], nargs, args, nret, ret); - qtest_send_prefix(chr); qtest_sendf(chr, "OK %"PRIu64"\n", res); return true; diff --git a/hw/riscv/riscv_hart.c b/hw/riscv/riscv_hart.c index ad67cd7645..a55d156668 100644 --- a/hw/riscv/riscv_hart.c +++ b/hw/riscv/riscv_hart.c @@ -94,7 +94,6 @@ static bool csr_qtest_callback(CharBackend *chr, gchar **words) g_assert(rc == 0); csr_call(words[1], cpu, csr, &val); - qtest_send_prefix(chr); qtest_sendf(chr, "OK 0 "TARGET_FMT_lx"\n", (target_ulong)val); return true; diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 23516995dc..7d094e1881 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -1065,9 +1065,6 @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, int ret; if (s->ctx && !s->dataplane_fenced) { - if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { - return; - } ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp); if (ret < 0) { return; diff --git a/include/block/block-common.h b/include/block/block-common.h index 338fe5ff7a..0b831ef87b 100644 --- a/include/block/block-common.h +++ b/include/block/block-common.h @@ -257,6 +257,7 @@ typedef enum { #define BDRV_OPT_AUTO_READ_ONLY "auto-read-only" #define BDRV_OPT_DISCARD "discard" #define BDRV_OPT_FORCE_SHARE "force-share" +#define BDRV_OPT_ACTIVE "active" #define BDRV_SECTOR_BITS 9 @@ -355,7 +356,6 @@ typedef enum BlockOpType { BLOCK_OP_TYPE_CHANGE, BLOCK_OP_TYPE_COMMIT_SOURCE, BLOCK_OP_TYPE_COMMIT_TARGET, - BLOCK_OP_TYPE_DATAPLANE, BLOCK_OP_TYPE_DRIVE_DEL, BLOCK_OP_TYPE_EJECT, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h index bd7cecd1cf..9be34b3c99 100644 --- a/include/block/block-global-state.h +++ b/include/block/block-global-state.h @@ -175,12 +175,18 @@ BlockDriverState * GRAPH_RDLOCK check_to_replace_node(BlockDriverState *parent_bs, const char *node_name, Error **errp); + +bool GRAPH_RDLOCK bdrv_is_inactive(BlockDriverState *bs); + int no_coroutine_fn GRAPH_RDLOCK bdrv_activate(BlockDriverState *bs, Error **errp); int coroutine_fn no_co_wrapper_bdrv_rdlock bdrv_co_activate(BlockDriverState *bs, Error **errp); +int no_coroutine_fn +bdrv_inactivate(BlockDriverState *bs, Error **errp); + void bdrv_activate_all(Error **errp); int bdrv_inactivate_all(void); diff --git a/include/block/export.h b/include/block/export.h index f2fe0f8078..4bd9531d4d 100644 --- a/include/block/export.h +++ b/include/block/export.h @@ -29,6 +29,9 @@ typedef struct BlockExportDriver { */ size_t instance_size; + /* True if the export type supports running on an inactive node */ + bool supports_inactive; + /* Creates and starts a new block export */ int (*create)(BlockExport *, BlockExportOptions *, Error **); diff --git a/include/hw/arm/boot.h b/include/hw/arm/boot.h index 80c492d742..b12bf61ca8 100644 --- a/include/hw/arm/boot.h +++ b/include/hw/arm/boot.h @@ -160,6 +160,7 @@ AddressSpace *arm_boot_address_space(ARMCPU *cpu, * @binfo: struct describing the boot environment * @addr_limit: upper limit of the available memory area at @addr * @as: address space to load image to + * @cpu: ARM CPU object * * Load a device tree supplied by the machine or by the user with the * '-dtb' command line option, and put it at offset @addr in target @@ -176,7 +177,8 @@ AddressSpace *arm_boot_address_space(ARMCPU *cpu, * Note: Must not be called unless have_dtb(binfo) is true. */ int arm_load_dtb(hwaddr addr, const struct arm_boot_info *binfo, - hwaddr addr_limit, AddressSpace *as, MachineState *ms); + hwaddr addr_limit, AddressSpace *as, MachineState *ms, + ARMCPU *cpu); /* Write a secure board setup routine with a dummy handler for SMCs */ void arm_write_secure_board_setup_dummy_smc(ARMCPU *cpu, diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h index 112ebdff21..4397a90680 100644 --- a/include/qemu/osdep.h +++ b/include/qemu/osdep.h @@ -631,6 +631,15 @@ bool qemu_write_pidfile(const char *pidfile, Error **errp); int qemu_get_thread_id(void); +/** + * qemu_kill_thread: + * @tid: thread id. + * @sig: host signal. + * + * Send @sig to one of QEMU's own threads with identifier @tid. + */ +int qemu_kill_thread(int tid, int sig); + #ifndef CONFIG_IOVEC struct iovec { void *iov_base; diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h index d174275a5c..ba8dfcc7d0 100644 --- a/include/system/block-backend-io.h +++ b/include/system/block-backend-io.h @@ -32,6 +32,13 @@ void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow); void blk_set_disable_request_queuing(BlockBackend *blk, bool disable); bool blk_iostatus_is_enabled(const BlockBackend *blk); +/* + * Return the qdev ID, or if no ID is assigned the QOM path, + * of the block device attached to the BlockBackend. + * + * The caller is responsible for releasing the value returned + * with g_free() after use. + */ char *blk_get_attached_dev_id(BlockBackend *blk); BlockAIOCB *blk_aio_pwrite_zeroes(BlockBackend *blk, int64_t offset, diff --git a/include/system/qtest.h b/include/system/qtest.h index c161d75165..6ddddc501b 100644 --- a/include/system/qtest.h +++ b/include/system/qtest.h @@ -24,7 +24,6 @@ static inline bool qtest_enabled(void) } #ifndef CONFIG_USER_ONLY -void qtest_send_prefix(CharBackend *chr); void G_GNUC_PRINTF(2, 3) qtest_sendf(CharBackend *chr, const char *fmt, ...); void qtest_set_command_cb(bool (*pc_cb)(CharBackend *chr, gchar **words)); bool qtest_driver(void); diff --git a/include/user/signal.h b/include/user/signal.h new file mode 100644 index 0000000000..7fa33b05d9 --- /dev/null +++ b/include/user/signal.h @@ -0,0 +1,25 @@ +/* + * Signal-related declarations. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef USER_SIGNAL_H +#define USER_SIGNAL_H + +#ifndef CONFIG_USER_ONLY +#error Cannot include this header from system emulation +#endif + +/** + * target_to_host_signal: + * @sig: target signal. + * + * On success, return the host signal between 0 (inclusive) and NSIG + * (exclusive) corresponding to the target signal @sig. Return any other value + * on failure. + */ +int target_to_host_signal(int sig); + +extern int host_interrupt_signal; + +#endif diff --git a/linux-user/main.c b/linux-user/main.c index 7198fa0986..5c74c52cc5 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -1024,7 +1024,6 @@ int main(int argc, char **argv, char **envp) if (gdbstub) { gdbserver_start(gdbstub, &error_fatal); - gdb_handlesig(cpu, 0, NULL, NULL, 0); } #ifdef CONFIG_SEMIHOSTING diff --git a/linux-user/signal-common.h b/linux-user/signal-common.h index 8584d9ecc2..196d2406f8 100644 --- a/linux-user/signal-common.h +++ b/linux-user/signal-common.h @@ -61,7 +61,6 @@ void queue_signal(CPUArchState *env, int sig, int si_type, target_siginfo_t *info); void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info); void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo); -int target_to_host_signal(int sig); int host_to_target_signal(int sig); long do_sigreturn(CPUArchState *env); long do_rt_sigreturn(CPUArchState *env); diff --git a/linux-user/signal.c b/linux-user/signal.c index 087c4d270e..81a98c6d02 100644 --- a/linux-user/signal.c +++ b/linux-user/signal.c @@ -36,6 +36,7 @@ #include "user/cpu_loop.h" #include "user/page-protection.h" #include "user/safe-syscall.h" +#include "user/signal.h" #include "tcg/tcg.h" /* target_siginfo_t must fit in gdbstub's siginfo save area. */ @@ -516,6 +517,8 @@ static int core_dump_signal(int sig) } } +int host_interrupt_signal; + static void signal_table_init(const char *rtsig_map) { int hsig, tsig, count; @@ -579,10 +582,10 @@ static void signal_table_init(const char *rtsig_map) * Attempts for configure "missing" signals via sigaction will be * silently ignored. * - * Reserve one signal for internal usage (see below). + * Reserve two signals for internal usage (see below). */ - hsig = SIGRTMIN + 1; + hsig = SIGRTMIN + 2; for (tsig = TARGET_SIGRTMIN; hsig <= SIGRTMAX && tsig <= TARGET_NSIG; hsig++, tsig++) { @@ -603,12 +606,17 @@ static void signal_table_init(const char *rtsig_map) host_to_target_signal_table[SIGABRT] = 0; for (hsig = SIGRTMIN; hsig <= SIGRTMAX; hsig++) { if (!host_to_target_signal_table[hsig]) { - host_to_target_signal_table[hsig] = TARGET_SIGABRT; - break; + if (host_interrupt_signal) { + host_to_target_signal_table[hsig] = TARGET_SIGABRT; + break; + } else { + host_interrupt_signal = hsig; + } } } if (hsig > SIGRTMAX) { - fprintf(stderr, "No rt signals left for SIGABRT mapping\n"); + fprintf(stderr, + "No rt signals left for interrupt and SIGABRT mapping\n"); exit(EXIT_FAILURE); } @@ -688,6 +696,8 @@ void signal_init(const char *rtsig_map) } sigact_table[tsig - 1]._sa_handler = thand; } + + sigaction(host_interrupt_signal, &act, NULL); } /* Force a synchronously taken signal. The kernel force_sig() function @@ -1035,6 +1045,12 @@ static void host_signal_handler(int host_sig, siginfo_t *info, void *puc) bool sync_sig = false; void *sigmask; + if (host_sig == host_interrupt_signal) { + ts->signal_pending = 1; + cpu_exit(thread_cpu); + return; + } + /* * Non-spoofed SIGSEGV and SIGBUS are synchronous, and need special * handling wrt signal blocking and unwinding. Non-spoofed SIGILL, diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 6ee02383da..02ea4221c9 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -138,6 +138,7 @@ #include "user-mmap.h" #include "user/page-protection.h" #include "user/safe-syscall.h" +#include "user/signal.h" #include "qemu/guest-random.h" #include "qemu/selfmap.h" #include "user/syscall-trace.h" @@ -360,7 +361,8 @@ _syscall3(int, sys_sched_getaffinity, pid_t, pid, unsigned int, len, #define __NR_sys_sched_setaffinity __NR_sched_setaffinity _syscall3(int, sys_sched_setaffinity, pid_t, pid, unsigned int, len, unsigned long *, user_mask_ptr); -/* sched_attr is not defined in glibc */ +/* sched_attr is not defined in glibc < 2.41 */ +#ifndef SCHED_ATTR_SIZE_VER0 struct sched_attr { uint32_t size; uint32_t sched_policy; @@ -373,6 +375,7 @@ struct sched_attr { uint32_t sched_util_min; uint32_t sched_util_max; }; +#endif #define __NR_sys_sched_getattr __NR_sched_getattr _syscall4(int, sys_sched_getattr, pid_t, pid, struct sched_attr *, attr, unsigned int, size, unsigned int, flags); diff --git a/migration/block-active.c b/migration/block-active.c index d477cf8182..40e986aade 100644 --- a/migration/block-active.c +++ b/migration/block-active.c @@ -12,51 +12,12 @@ #include "qemu/error-report.h" #include "trace.h" -/* - * Migration-only cache to remember the block layer activation status. - * Protected by BQL. - * - * We need this because.. - * - * - Migration can fail after block devices are invalidated (during - * switchover phase). When that happens, we need to be able to recover - * the block drive status by re-activating them. - * - * - Currently bdrv_inactivate_all() is not safe to be invoked on top of - * invalidated drives (even if bdrv_activate_all() is actually safe to be - * called any time!). It means remembering this could help migration to - * make sure it won't invalidate twice in a row, crashing QEMU. It can - * happen when we migrate a PAUSED VM from host1 to host2, then migrate - * again to host3 without starting it. TODO: a cleaner solution is to - * allow safe invoke of bdrv_inactivate_all() at anytime, like - * bdrv_activate_all(). - * - * For freshly started QEMU, the flag is initialized to TRUE reflecting the - * scenario where QEMU owns block device ownerships. - * - * For incoming QEMU taking a migration stream, the flag is initialized to - * FALSE reflecting that the incoming side doesn't own the block devices, - * not until switchover happens. - */ -static bool migration_block_active; - -/* Setup the disk activation status */ -void migration_block_active_setup(bool active) -{ - migration_block_active = active; -} - bool migration_block_activate(Error **errp) { ERRP_GUARD(); assert(bql_locked()); - if (migration_block_active) { - trace_migration_block_activation("active-skipped"); - return true; - } - trace_migration_block_activation("active"); bdrv_activate_all(errp); @@ -65,7 +26,6 @@ bool migration_block_activate(Error **errp) return false; } - migration_block_active = true; return true; } @@ -75,11 +35,6 @@ bool migration_block_inactivate(void) assert(bql_locked()); - if (!migration_block_active) { - trace_migration_block_activation("inactive-skipped"); - return true; - } - trace_migration_block_activation("inactive"); ret = bdrv_inactivate_all(); @@ -89,6 +44,5 @@ bool migration_block_inactivate(void) return false; } - migration_block_active = false; return true; } diff --git a/migration/migration.c b/migration/migration.c index 0aa084f380..396928513a 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -1895,12 +1895,6 @@ void qmp_migrate_incoming(const char *uri, bool has_channels, return; } - /* - * Newly setup incoming QEMU. Mark the block active state to reflect - * that the src currently owns the disks. - */ - migration_block_active_setup(false); - once = false; } @@ -3992,8 +3986,6 @@ static void migration_instance_init(Object *obj) ms->state = MIGRATION_STATUS_NONE; ms->mbps = -1; ms->pages_per_second = -1; - /* Freshly started QEMU owns all the block devices */ - migration_block_active_setup(true); qemu_sem_init(&ms->pause_sem, 0); qemu_mutex_init(&ms->error_mutex); diff --git a/migration/migration.h b/migration/migration.h index 70f45af036..eaebcc2042 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -554,7 +554,4 @@ void migration_bitmap_sync_precopy(bool last_stage); void dirty_bitmap_mig_init(void); bool should_send_vmdesc(void); -/* migration/block-active.c */ -void migration_block_active_setup(bool active); - #endif diff --git a/nbd/server.c b/nbd/server.c index f64e47270c..2076fb2666 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -2026,6 +2026,7 @@ static void nbd_export_delete(BlockExport *blk_exp) const BlockExportDriver blk_exp_nbd = { .type = BLOCK_EXPORT_TYPE_NBD, .instance_size = sizeof(NBDExport), + .supports_inactive = true, .create = nbd_export_create, .delete = nbd_export_delete, .request_shutdown = nbd_export_request_shutdown, @@ -2920,6 +2921,22 @@ static coroutine_fn int nbd_handle_request(NBDClient *client, NBDExport *exp = client->exp; char *msg; size_t i; + bool inactive; + + WITH_GRAPH_RDLOCK_GUARD() { + inactive = bdrv_is_inactive(blk_bs(exp->common.blk)); + if (inactive) { + switch (request->type) { + case NBD_CMD_READ: + /* These commands are allowed on inactive nodes */ + break; + default: + /* Return an error for the rest */ + return nbd_send_generic_reply(client, request, -EPERM, + "export is inactive", errp); + } + } + } switch (request->type) { case NBD_CMD_CACHE: diff --git a/qapi/block-core.json b/qapi/block-core.json index fd3bcc1c17..ee6eccc68c 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -486,6 +486,10 @@ # @backing_file_depth: number of files in the backing file chain # (since: 1.2) # +# @active: true if the backend is active; typical cases for inactive backends +# are on the migration source instance after migration completes and on the +# destination before it completes. (since: 10.0) +# # @encrypted: true if the backing device is encrypted # # @detect_zeroes: detect and optimize zero writes (Since 2.1) @@ -556,7 +560,7 @@ { 'struct': 'BlockDeviceInfo', 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', '*backing_file': 'str', 'backing_file_depth': 'int', - 'encrypted': 'bool', + 'active': 'bool', 'encrypted': 'bool', 'detect_zeroes': 'BlockdevDetectZeroesOptions', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int', @@ -4679,6 +4683,11 @@ # # @cache: cache-related options # +# @active: whether the block node should be activated (default: true). +# Having inactive block nodes is useful primarily for migration because it +# allows opening an image on the destination while the source is still +# holding locks for it. (Since 10.0) +# # @read-only: whether the block device should be read-only (default: # false). Note that some block drivers support only read-only # access, either generally or in certain configurations. In this @@ -4705,6 +4714,7 @@ '*node-name': 'str', '*discard': 'BlockdevDiscardOptions', '*cache': 'BlockdevCacheOptions', + '*active': 'bool', '*read-only': 'bool', '*auto-read-only': 'bool', '*force-share': 'bool', @@ -4936,6 +4946,38 @@ 'allow-preconfig': true } ## +# @blockdev-set-active: +# +# Activate or inactivate a block device. Use this to manage the handover of +# block devices on migration with qemu-storage-daemon. +# +# Activating a node automatically activates all of its child nodes first. +# Inactivating a node automatically inactivates any of its child nodes that are +# not in use by a still active node. +# +# @node-name: Name of the graph node to activate or inactivate. By default, all +# nodes are affected by the operation. +# +# @active: true if the nodes should be active when the command returns success, +# false if they should be inactive. +# +# Since: 10.0 +# +# .. qmp-example:: +# +# -> { "execute": "blockdev-set-active", +# "arguments": { +# "node-name": "node0", +# "active": false +# } +# } +# <- { "return": {} } +## +{ 'command': 'blockdev-set-active', + 'data': { '*node-name': 'str', 'active': 'bool' }, + 'allow-preconfig': true } + +## # @BlockdevCreateOptionsFile: # # Driver specific image creation options for file. diff --git a/qapi/block-export.json b/qapi/block-export.json index ce33fe378d..117b05d13c 100644 --- a/qapi/block-export.json +++ b/qapi/block-export.json @@ -372,6 +372,13 @@ # cannot be moved to the iothread. The default is false. # (since: 5.2) # +# @allow-inactive: If true, the export allows the exported node to be inactive. +# If it is created for an inactive block node, the node remains inactive. If +# the export type doesn't support running on an inactive node, an error is +# returned. If false, inactive block nodes are automatically activated before +# creating the export and trying to inactivate them later fails. +# (since: 10.0; default: false) +# # Since: 4.2 ## { 'union': 'BlockExportOptions', @@ -381,7 +388,8 @@ '*iothread': 'str', 'node-name': 'str', '*writable': 'bool', - '*writethrough': 'bool' }, + '*writethrough': 'bool', + '*allow-inactive': 'bool' }, 'discriminator': 'type', 'data': { 'nbd': 'BlockExportOptionsNbd', diff --git a/scripts/qemu-gdb.py b/scripts/qemu-gdb.py index 4d2a9f6c43..cfae94a2e9 100644 --- a/scripts/qemu-gdb.py +++ b/scripts/qemu-gdb.py @@ -45,3 +45,5 @@ coroutine.CoroutineBt() # Default to silently passing through SIGUSR1, because QEMU sends it # to itself a lot. gdb.execute('handle SIGUSR1 pass noprint nostop') +# Always print full stack for python errors, easier to debug and report issues +gdb.execute('set python print-stack full') diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py index 7db46d4b68..e98fc48a4b 100644 --- a/scripts/qemugdb/coroutine.py +++ b/scripts/qemugdb/coroutine.py @@ -13,28 +13,9 @@ import gdb VOID_PTR = gdb.lookup_type('void').pointer() -def get_fs_base(): - '''Fetch %fs base value using arch_prctl(ARCH_GET_FS). This is - pthread_self().''' - # %rsp - 120 is scratch space according to the SystemV ABI - old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') - gdb.execute('call (int)arch_prctl(0x1003, $rsp - 120)', False, True) - fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') - gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True) - return fs_base - def pthread_self(): - '''Fetch pthread_self() from the glibc start_thread function.''' - f = gdb.newest_frame() - while f.name() != 'start_thread': - f = f.older() - if f is None: - return get_fs_base() - - try: - return f.read_var("arg") - except ValueError: - return get_fs_base() + '''Fetch the base address of TLS.''' + return gdb.parse_and_eval("$fs_base") def get_glibc_pointer_guard(): '''Fetch glibc pointer guard value''' @@ -65,9 +46,60 @@ def get_jmpbuf_regs(jmpbuf): 'r15': jmpbuf[JB_R15], 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } -def bt_jmpbuf(jmpbuf): - '''Backtrace a jmpbuf''' - regs = get_jmpbuf_regs(jmpbuf) +def symbol_lookup(addr): + # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" + result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() + try: + if "+" in result: + (func, result) = result.split(" + ") + (offset, result) = result.split(" in ") + else: + offset = "0" + (func, result) = result.split(" in ") + func_str = f"{func}<+{offset}> ()" + except: + return f"??? ({result})" + + # Example: Line 321 of "../util/coroutine-ucontext.c" starts at address + # 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab + # <qemu_coroutine_switch+123>. + result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip() + if not result.startswith("Line "): + return func_str + result = result[5:] + + try: + result = result.split(" starts ")[0] + (line, path) = result.split(" of ") + path = path.replace("\"", "") + except: + return func_str + + return f"{func_str} at {path}:{line}" + +def dump_backtrace(regs): + ''' + Backtrace dump with raw registers, mimic GDB command 'bt'. + ''' + # Here only rbp and rip that matter.. + rbp = regs['rbp'] + rip = regs['rip'] + i = 0 + + while rbp: + # For all return addresses on stack, we want to look up symbol/line + # on the CALL command, because the return address is the next + # instruction instead of the CALL. Here -1 would work for any + # sized CALL instruction. + print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}") + rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") + rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") + i += 1 + +def dump_backtrace_live(regs): + ''' + Backtrace dump with gdb's 'bt' command, only usable in a live session. + ''' old = dict() # remember current stack frame and select the topmost @@ -88,6 +120,17 @@ def bt_jmpbuf(jmpbuf): selected_frame.select() +def bt_jmpbuf(jmpbuf): + '''Backtrace a jmpbuf''' + regs = get_jmpbuf_regs(jmpbuf) + try: + # This reuses gdb's "bt" command, which can be slightly prettier + # but only works with live sessions. + dump_backtrace_live(regs) + except: + # If above doesn't work, fallback to poor man's unwind + dump_backtrace(regs) + def co_cast(co): return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) @@ -120,10 +163,15 @@ class CoroutineBt(gdb.Command): gdb.execute("bt") - if gdb.parse_and_eval("qemu_in_coroutine()") == False: - return + try: + # This only works with a live session + co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") + except: + # Fallback to use hard-coded ucontext vars if it's coredump + co_ptr = gdb.parse_and_eval("co_tls_current") - co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") + if co_ptr == False: + return while True: co = co_cast(co_ptr) diff --git a/stubs/meson.build b/stubs/meson.build index a8b3aeb564..b0fee37e05 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -61,6 +61,8 @@ if have_user if not have_system stub_ss.add(files('qdev.c')) endif + + stub_ss.add(files('monitor-fd.c')) endif if have_system diff --git a/stubs/monitor-fd.c b/stubs/monitor-fd.c new file mode 100644 index 0000000000..9bb6749885 --- /dev/null +++ b/stubs/monitor-fd.c @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "qemu/osdep.h" +#include "monitor/monitor.h" + +int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) +{ + abort(); +} diff --git a/system/qtest.c b/system/qtest.c index e68ed0f2a8..12152efbcd 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -265,7 +265,7 @@ static int hex2nib(char ch) } } -void qtest_send_prefix(CharBackend *chr) +static void qtest_log_timestamp(void) { if (!qtest_log_fp || !qtest_opened) { return; @@ -282,7 +282,7 @@ static void G_GNUC_PRINTF(1, 2) qtest_log_send(const char *fmt, ...) return; } - qtest_send_prefix(NULL); + qtest_log_timestamp(); va_start(ap, fmt); vfprintf(qtest_log_fp, fmt, ap); @@ -301,6 +301,7 @@ static void qtest_server_char_be_send(void *opaque, const char *str) static void qtest_send(CharBackend *chr, const char *str) { + qtest_log_timestamp(); qtest_server_send(qtest_server_send_opaque, str); } @@ -324,7 +325,6 @@ static void qtest_irq_handler(void *opaque, int n, int level) if (irq_levels[n] != level) { CharBackend *chr = &qtest->qtest_chr; irq_levels[n] = level; - qtest_send_prefix(chr); qtest_sendf(chr, "IRQ %s %d\n", level ? "raise" : "lower", n); } @@ -380,19 +380,16 @@ static void qtest_process_command(CharBackend *chr, gchar **words) is_outbound = words[0][14] == 'o'; dev = DEVICE(object_resolve_path(words[1], NULL)); if (!dev) { - qtest_send_prefix(chr); qtest_send(chr, "FAIL Unknown device\n"); return; } if (is_named && !is_outbound) { - qtest_send_prefix(chr); qtest_send(chr, "FAIL Interception of named in-GPIOs not yet supported\n"); return; } if (irq_intercept_dev) { - qtest_send_prefix(chr); if (irq_intercept_dev != dev) { qtest_send(chr, "FAIL IRQ intercept already enabled\n"); } else { @@ -419,7 +416,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) } } - qtest_send_prefix(chr); if (interception_succeeded) { irq_intercept_dev = dev; qtest_send(chr, "OK\n"); @@ -438,7 +434,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) dev = DEVICE(object_resolve_path(words[1], NULL)); if (!dev) { - qtest_send_prefix(chr); qtest_send(chr, "FAIL Unknown device\n"); return; } @@ -457,7 +452,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) irq = qdev_get_gpio_in_named(dev, name, num); qemu_set_irq(irq, level); - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "outb") == 0 || strcmp(words[0], "outw") == 0 || @@ -480,7 +474,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) } else if (words[0][3] == 'l') { cpu_outl(addr, value); } - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "inb") == 0 || strcmp(words[0], "inw") == 0 || @@ -501,7 +494,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) } else if (words[0][2] == 'l') { value = cpu_inl(addr); } - qtest_send_prefix(chr); qtest_sendf(chr, "OK 0x%04x\n", value); } else if (strcmp(words[0], "writeb") == 0 || strcmp(words[0], "writew") == 0 || @@ -537,7 +529,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, &data, 8); } - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "readb") == 0 || strcmp(words[0], "readw") == 0 || @@ -571,7 +562,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) &value, 8); tswap64s(&value); } - qtest_send_prefix(chr); qtest_sendf(chr, "OK 0x%016" PRIx64 "\n", value); } else if (strcmp(words[0], "read") == 0) { g_autoptr(GString) enc = NULL; @@ -593,7 +583,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) enc = qemu_hexdump_line(NULL, data, len, 0, 0); - qtest_send_prefix(chr); qtest_sendf(chr, "OK 0x%s\n", enc->str); g_free(data); @@ -613,7 +602,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) address_space_read(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data, len); b64_data = g_base64_encode(data, len); - qtest_send_prefix(chr); qtest_sendf(chr, "OK %s\n", b64_data); g_free(data); @@ -649,7 +637,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) len); g_free(data); - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "memset") == 0) { uint64_t addr, len; @@ -673,7 +660,6 @@ static void qtest_process_command(CharBackend *chr, gchar **words) g_free(data); } - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "b64write") == 0) { uint64_t addr, len; @@ -705,10 +691,8 @@ static void qtest_process_command(CharBackend *chr, gchar **words) address_space_write(first_cpu->as, addr, MEMTXATTRS_UNSPECIFIED, data, len); - qtest_send_prefix(chr); qtest_send(chr, "OK\n"); } else if (strcmp(words[0], "endianness") == 0) { - qtest_send_prefix(chr); if (target_words_bigendian()) { qtest_sendf(chr, "OK big\n"); } else { @@ -724,17 +708,24 @@ static void qtest_process_command(CharBackend *chr, gchar **words) } else { ns = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, QEMU_TIMER_ATTR_ALL); + if (ns < 0) { + qtest_send(chr, "FAIL " + "cannot advance clock to the next deadline " + "because there is no pending deadline\n"); + return; + } } new_ns = qemu_clock_advance_virtual_time(old_ns + ns); - qtest_send_prefix(chr); - qtest_sendf(chr, "%s %"PRIi64"\n", - new_ns > old_ns ? "OK" : "FAIL", new_ns); + if (new_ns > old_ns) { + qtest_sendf(chr, "OK %"PRIi64"\n", new_ns); + } else { + qtest_sendf(chr, "FAIL could not advance time\n"); + } } else if (strcmp(words[0], "module_load") == 0) { Error *local_err = NULL; int rv; g_assert(words[1] && words[2]); - qtest_send_prefix(chr); rv = module_load(words[1], words[2], &local_err); if (rv > 0) { qtest_sendf(chr, "OK\n"); @@ -752,36 +743,30 @@ static void qtest_process_command(CharBackend *chr, gchar **words) ret = qemu_strtoi64(words[1], NULL, 0, &ns); g_assert(ret == 0); new_ns = qemu_clock_advance_virtual_time(ns); - qtest_send_prefix(chr); qtest_sendf(chr, "%s %"PRIi64"\n", new_ns == ns ? "OK" : "FAIL", new_ns); } else if (process_command_cb && process_command_cb(chr, words)) { /* Command got consumed by the callback handler */ } else { - qtest_send_prefix(chr); qtest_sendf(chr, "FAIL Unknown command '%s'\n", words[0]); } } +/* + * Process as much of @inbuf as we can in newline terminated chunks. + * Remove the processed commands from @inbuf as we go. + */ static void qtest_process_inbuf(CharBackend *chr, GString *inbuf) { char *end; while ((end = strchr(inbuf->str, '\n')) != NULL) { - size_t offset; - GString *cmd; - gchar **words; - - offset = end - inbuf->str; + size_t len = end - inbuf->str; + g_autofree char *cmd = g_strndup(inbuf->str, len); + g_auto(GStrv) words = g_strsplit(cmd, " ", 0); - cmd = g_string_new_len(inbuf->str, offset); - g_string_erase(inbuf, 0, offset + 1); - - words = g_strsplit(cmd->str, " ", 0); + g_string_erase(inbuf, 0, len + 1); qtest_process_command(chr, words); - g_strfreev(words); - - g_string_free(cmd, TRUE); } } diff --git a/system/vl.c b/system/vl.c index ff91af8eca..9c6942c6cf 100644 --- a/system/vl.c +++ b/system/vl.c @@ -3469,6 +3469,7 @@ void qemu_init(int argc, char **argv) nb_prom_envs++; break; case QEMU_OPTION_old_param: + warn_report("-old-param is deprecated"); old_param = 1; break; case QEMU_OPTION_rtc: diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 7a83b9ee34..32dc7c1e69 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -2758,6 +2758,9 @@ static void cpu_register_class_init(ObjectClass *oc, void *data) acc->info = data; cc->gdb_core_xml_file = "arm-core.xml"; + if (acc->info->deprecation_note) { + cc->deprecation_note = acc->info->deprecation_note; + } } void arm_cpu_register(const ARMCPUInfo *info) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 2213c27734..c2d2d99b46 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -1118,6 +1118,7 @@ struct ArchCPU { typedef struct ARMCPUInfo { const char *name; + const char *deprecation_note; void (*initfn)(Object *obj); void (*class_init)(ObjectClass *oc, void *data); } ARMCPUInfo; diff --git a/target/arm/tcg/cpu32.c b/target/arm/tcg/cpu32.c index 2ad2182525..0f1c5bc87e 100644 --- a/target/arm/tcg/cpu32.c +++ b/target/arm/tcg/cpu32.c @@ -1026,19 +1026,31 @@ static const ARMCPUInfo arm_tcg_cpus[] = { { .name = "ti925t", .initfn = ti925t_initfn }, { .name = "sa1100", .initfn = sa1100_initfn }, { .name = "sa1110", .initfn = sa1110_initfn }, - { .name = "pxa250", .initfn = pxa250_initfn }, - { .name = "pxa255", .initfn = pxa255_initfn }, - { .name = "pxa260", .initfn = pxa260_initfn }, - { .name = "pxa261", .initfn = pxa261_initfn }, - { .name = "pxa262", .initfn = pxa262_initfn }, + { .name = "pxa250", .initfn = pxa250_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa255", .initfn = pxa255_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa260", .initfn = pxa260_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa261", .initfn = pxa261_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa262", .initfn = pxa262_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, /* "pxa270" is an alias for "pxa270-a0" */ - { .name = "pxa270", .initfn = pxa270a0_initfn }, - { .name = "pxa270-a0", .initfn = pxa270a0_initfn }, - { .name = "pxa270-a1", .initfn = pxa270a1_initfn }, - { .name = "pxa270-b0", .initfn = pxa270b0_initfn }, - { .name = "pxa270-b1", .initfn = pxa270b1_initfn }, - { .name = "pxa270-c0", .initfn = pxa270c0_initfn }, - { .name = "pxa270-c5", .initfn = pxa270c5_initfn }, + { .name = "pxa270", .initfn = pxa270a0_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-a0", .initfn = pxa270a0_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-a1", .initfn = pxa270a1_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-b0", .initfn = pxa270b0_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-b1", .initfn = pxa270b1_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-c0", .initfn = pxa270c0_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, + { .name = "pxa270-c5", .initfn = pxa270c5_initfn, + .deprecation_note = "iwMMXt CPUs are no longer supported", }, #ifndef TARGET_AARCH64 { .name = "max", .initfn = arm_max_initfn }, #endif diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index 0b76a2cdb7..d6ac2ed418 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -75,17 +75,6 @@ static int scale_by_log2_tag_granule(DisasContext *s, int x) #include "decode-sme-fa64.c.inc" #include "decode-a64.c.inc" -/* Table based decoder typedefs - used when the relevant bits for decode - * are too awkwardly scattered across the instruction (eg SIMD). - */ -typedef void AArch64DecodeFn(DisasContext *s, uint32_t insn); - -typedef struct AArch64DecodeTable { - uint32_t pattern; - uint32_t mask; - AArch64DecodeFn *disas_fn; -} AArch64DecodeTable; - /* initialize TCG globals. */ void a64_translate_init(void) { diff --git a/tests/docker/Makefile.include b/tests/docker/Makefile.include index fead7d3abe..fa1cbb6726 100644 --- a/tests/docker/Makefile.include +++ b/tests/docker/Makefile.include @@ -236,3 +236,6 @@ docker-image: ${DOCKER_IMAGES:%=docker-image-%} docker-clean: $(call quiet-command, $(DOCKER_SCRIPT) clean) + +# Overrides +docker-test-rust%: NETWORK=1 diff --git a/tests/docker/test-rust b/tests/docker/test-rust new file mode 100755 index 0000000000..e7e3e94a55 --- /dev/null +++ b/tests/docker/test-rust @@ -0,0 +1,21 @@ +#!/bin/bash -e +# +# Run the rust code checks (a.k.a. check-rust-tools-nightly) +# +# Copyright (c) 2025 Linaro Ltd +# +# Authors: +# Alex Bennée <alex.bennee@linaro.org> +# +# This work is licensed under the terms of the GNU GPL, version 2 +# or (at your option) any later version. See the COPYING file in +# the top-level directory. + +. common.rc + +cd "$BUILD_DIR" + +configure_qemu --disable-user --disable-docs --enable-rust +pyvenv/bin/meson devenv -w $QEMU_SRC/rust ${CARGO-cargo} fmt --check +make clippy +make rustdoc diff --git a/tests/guest-debug/run-test.py b/tests/guest-debug/run-test.py index 5a091db8be..75e9c92e03 100755 --- a/tests/guest-debug/run-test.py +++ b/tests/guest-debug/run-test.py @@ -36,6 +36,8 @@ def get_args(): parser.add_argument("--gdb-args", help="Additional gdb arguments") parser.add_argument("--output", help="A file to redirect output to") parser.add_argument("--stderr", help="A file to redirect stderr to") + parser.add_argument("--no-suspend", action="store_true", + help="Ask the binary to not wait for GDB connection") return parser.parse_args() @@ -73,10 +75,19 @@ if __name__ == '__main__': # Launch QEMU with binary if "system" in args.qemu: + if args.no_suspend: + suspend = '' + else: + suspend = ' -S' cmd = f'{args.qemu} {args.qargs} {args.binary}' \ - f' -S -gdb unix:path={socket_name},server=on' + f'{suspend} -gdb unix:path={socket_name},server=on' else: - cmd = f'{args.qemu} {args.qargs} -g {socket_name} {args.binary}' + if args.no_suspend: + suspend = ',suspend=n' + else: + suspend = '' + cmd = f'{args.qemu} {args.qargs} -g {socket_name}{suspend}' \ + f' {args.binary}' log(output, "QEMU CMD: %s" % (cmd)) inferior = subprocess.Popen(shlex.split(cmd)) diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 index 98d17b1388..8452845f44 100755 --- a/tests/qemu-iotests/041 +++ b/tests/qemu-iotests/041 @@ -1100,10 +1100,8 @@ class TestRepairQuorum(iotests.QMPTestCase): # Check the full error message now self.vm.shutdown() - log = self.vm.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) + log = iotests.filter_qtest(self.vm.get_log()) log = re.sub(r'^Formatting.*\n', '', log) - log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) self.assertEqual(log, diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165 index b24907a62f..b3b1709d71 100755 --- a/tests/qemu-iotests/165 +++ b/tests/qemu-iotests/165 @@ -82,9 +82,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): self.vm.shutdown() #catch 'Persistent bitmaps are lost' possible error - log = self.vm.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) + log = iotests.filter_qtest(self.vm.get_log()) if log: print(log) diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out index e8f631f853..52692b6b3b 100644 --- a/tests/qemu-iotests/184.out +++ b/tests/qemu-iotests/184.out @@ -26,6 +26,7 @@ Testing: { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 1073741824, @@ -59,6 +60,7 @@ Testing: { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 1073741824, "filename": "null-co://", diff --git a/tests/qemu-iotests/191.out b/tests/qemu-iotests/191.out index c3309e4bc6..2a72ca7106 100644 --- a/tests/qemu-iotests/191.out +++ b/tests/qemu-iotests/191.out @@ -114,6 +114,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 67108864, @@ -155,6 +156,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT.ovl2", @@ -183,6 +185,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 67108864, @@ -224,6 +227,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT", @@ -252,6 +256,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 67108864, @@ -293,6 +298,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 393216, "filename": "TEST_DIR/t.IMGFMT.mid", @@ -321,6 +327,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 67108864, "filename": "TEST_DIR/t.IMGFMT.base", @@ -350,6 +357,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 393216, "filename": "TEST_DIR/t.IMGFMT.base", @@ -521,6 +529,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 67108864, @@ -562,6 +571,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT.ovl2", @@ -590,6 +600,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "backing-image": { @@ -642,6 +653,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT.ovl3", @@ -670,6 +682,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 67108864, "filename": "TEST_DIR/t.IMGFMT.base", @@ -699,6 +712,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 393216, "filename": "TEST_DIR/t.IMGFMT.base", @@ -727,6 +741,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 67108864, @@ -768,6 +783,7 @@ wrote 65536/65536 bytes at offset 1048576 { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT", diff --git a/tests/qemu-iotests/273.out b/tests/qemu-iotests/273.out index 71843f02de..c19753c685 100644 --- a/tests/qemu-iotests/273.out +++ b/tests/qemu-iotests/273.out @@ -23,6 +23,7 @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "backing-image": { @@ -74,6 +75,7 @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT", @@ -102,6 +104,7 @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "backing-image": { "virtual-size": 197120, @@ -142,6 +145,7 @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT.mid", @@ -170,6 +174,7 @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev { "iops_rd": 0, "detect_zeroes": "off", + "active": true, "image": { "virtual-size": 197120, "filename": "TEST_DIR/t.IMGFMT.base", diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 19817c7353..7292c8b342 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -701,6 +701,10 @@ def filter_qmp_imgfmt(qmsg): def filter_nbd_exports(output: str) -> str: return re.sub(r'((min|opt|max) block): [0-9]+', r'\1: XXX', output) +def filter_qtest(output: str) -> str: + output = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', output) + output = re.sub(r'\n?\[I \+\d+\.\d+\] CLOSED\n?$', '', output) + return output Msg = TypeVar('Msg', Dict[str, Any], List[Any], str) @@ -909,6 +913,10 @@ class VM(qtest.QEMUQtestMachine): self._args.append(addr) return self + def add_paused(self): + self._args.append('-S') + return self + def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage: cmd = 'human-monitor-command' kwargs: Dict[str, Any] = {'command-line': command_line} diff --git a/tests/qemu-iotests/tests/copy-before-write b/tests/qemu-iotests/tests/copy-before-write index d33bea577d..498c558008 100755 --- a/tests/qemu-iotests/tests/copy-before-write +++ b/tests/qemu-iotests/tests/copy-before-write @@ -95,8 +95,7 @@ class TestCbwError(iotests.QMPTestCase): self.vm.shutdown() log = self.vm.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) + log = iotests.filter_qtest(log) log = iotests.filter_qemu_io(log) return log diff --git a/tests/qemu-iotests/tests/inactive-node-nbd b/tests/qemu-iotests/tests/inactive-node-nbd new file mode 100755 index 0000000000..a95b37e796 --- /dev/null +++ b/tests/qemu-iotests/tests/inactive-node-nbd @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +# group: rw quick +# +# Copyright (C) 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, see <http://www.gnu.org/licenses/>. +# +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> + +import iotests + +from iotests import QemuIoInteractive +from iotests import filter_qemu_io, filter_qtest, filter_qmp_testfiles + +iotests.script_initialize(supported_fmts=['generic'], + supported_protocols=['file'], + supported_platforms=['linux']) + +def get_export(node_name='disk-fmt', allow_inactive=None): + exp = { + 'id': 'exp0', + 'type': 'nbd', + 'node-name': node_name, + 'writable': True, + } + + if allow_inactive is not None: + exp['allow-inactive'] = allow_inactive + + return exp + +def node_is_active(_vm, node_name): + nodes = _vm.cmd('query-named-block-nodes', flat=True) + node = next(n for n in nodes if n['node-name'] == node_name) + return node['active'] + +with iotests.FilePath('disk.img') as path, \ + iotests.FilePath('snap.qcow2') as snap_path, \ + iotests.FilePath('snap2.qcow2') as snap2_path, \ + iotests.FilePath('target.img') as target_path, \ + iotests.FilePath('nbd.sock', base_dir=iotests.sock_dir) as nbd_sock, \ + iotests.VM() as vm: + + img_size = '10M' + + iotests.log('Preparing disk...') + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) + iotests.qemu_img_create('-f', iotests.imgfmt, target_path, img_size) + + iotests.qemu_img_create('-f', 'qcow2', '-b', path, '-F', iotests.imgfmt, + snap_path) + iotests.qemu_img_create('-f', 'qcow2', '-b', snap_path, '-F', 'qcow2', + snap2_path) + + iotests.log('Launching VM...') + vm.add_blockdev(f'file,node-name=disk-file,filename={path}') + vm.add_blockdev(f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' + 'active=off') + vm.add_blockdev(f'file,node-name=target-file,filename={target_path}') + vm.add_blockdev(f'{iotests.imgfmt},file=target-file,node-name=target-fmt') + vm.add_blockdev(f'file,node-name=snap-file,filename={snap_path}') + vm.add_blockdev(f'file,node-name=snap2-file,filename={snap2_path}') + + # Actually running the VM activates all images + vm.add_paused() + + vm.launch() + vm.qmp_log('nbd-server-start', + addr={'type': 'unix', 'data':{'path': nbd_sock}}, + filters=[filter_qmp_testfiles]) + + iotests.log('\n=== Creating export of inactive node ===') + + iotests.log('\nExports activate nodes without allow-inactive') + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('block-export-add', **get_export()) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\nExports activate nodes with allow-inactive=false') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('block-export-add', **get_export(allow_inactive=False)) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\nExport leaves nodes inactive with allow-inactive=true') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('block-export-add', **get_export(allow_inactive=True)) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\n=== Inactivating node with existing export ===') + + iotests.log('\nInactivating nodes with an export fails without ' + 'allow-inactive') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) + vm.qmp_log('block-export-add', **get_export(node_name='disk-fmt')) + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\nInactivating nodes with an export fails with ' + 'allow-inactive=false') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) + vm.qmp_log('block-export-add', + **get_export(node_name='disk-fmt', allow_inactive=False)) + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\nInactivating nodes with an export works with ' + 'allow-inactive=true') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) + vm.qmp_log('block-export-add', + **get_export(node_name='disk-fmt', allow_inactive=True)) + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + vm.qmp_log('query-block-exports') + vm.qmp_log('block-export-del', id='exp0') + vm.event_wait('BLOCK_EXPORT_DELETED') + vm.qmp_log('query-block-exports') + + iotests.log('\n=== Inactive nodes with parent ===') + + iotests.log('\nInactivating nodes with an active parent fails') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + + iotests.log('\nInactivating nodes with an inactive parent works') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + + iotests.log('\nCreating active parent node with an inactive child fails') + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', + node_name='disk-filter') + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', + node_name='disk-filter', active=True) + + iotests.log('\nCreating inactive parent node with an inactive child works') + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', + node_name='disk-filter', active=False) + vm.qmp_log('blockdev-del', node_name='disk-filter') + + iotests.log('\n=== Resizing an inactive node ===') + vm.qmp_log('block_resize', node_name='disk-fmt', size=16*1024*1024) + + iotests.log('\n=== Taking a snapshot of an inactive node ===') + + iotests.log('\nActive overlay over inactive backing file automatically ' + 'makes both inactive for compatibility') + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', + file='snap-file', backing=None) + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + vm.qmp_log('blockdev-del', node_name='snap-fmt') + + iotests.log('\nInactive overlay over inactive backing file just works') + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', + file='snap-file', backing=None, active=False) + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') + + iotests.log('\n=== Block jobs with inactive nodes ===') + + iotests.log('\nStreaming into an inactive node') + vm.qmp_log('block-stream', device='snap-fmt', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nCommitting an inactive root node (active commit)') + vm.qmp_log('block-commit', job_id='job0', device='snap-fmt', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nCommitting an inactive intermediate node to inactive base') + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap2-fmt', + file='snap2-file', backing='snap-fmt', active=False) + + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) + + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', + top_node='snap-fmt', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nCommitting an inactive intermediate node to active base') + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', + top_node='snap-fmt', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nMirror from inactive source to active target') + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', + target='target-fmt', sync='full', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nMirror from active source to inactive target') + + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) + + # Activating snap2-fmt recursively activates the whole backing chain + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=True) + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) + + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) + + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', + target='target-fmt', sync='full', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nBackup from active source to inactive target') + + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', + target='target-fmt', sync='full', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\nBackup from inactive source to active target') + + # Inactivating snap2-fmt recursively inactivates the whole backing chain + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=False) + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=True) + + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) + + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', + target='target-fmt', sync='full', + filters=[iotests.filter_qmp_generated_node_ids]) + + iotests.log('\n=== Accessing export on inactive node ===') + + # Use the target node because it has the right image format and isn't the + # (read-only) backing file of a qcow2 node + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) + vm.qmp_log('block-export-add', + **get_export(node_name='target-fmt', allow_inactive=True)) + + # The read should succeed, everything else should fail gracefully + qemu_io = QemuIoInteractive('-f', 'raw', + f'nbd+unix:///target-fmt?socket={nbd_sock}') + iotests.log(qemu_io.cmd('read 0 64k'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('write 0 64k'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('write -z 0 64k'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('write -zu 0 64k'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('discard 0 64k'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('flush'), filters=[filter_qemu_io]) + iotests.log(qemu_io.cmd('map'), filters=[filter_qemu_io]) + qemu_io.close() + + iotests.log('\n=== Resuming VM activates all images ===') + vm.qmp_log('cont') + + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) + + iotests.log('\nShutting down...') + vm.shutdown() + log = vm.get_log() + if log: + iotests.log(log, [filter_qtest, filter_qemu_io]) diff --git a/tests/qemu-iotests/tests/inactive-node-nbd.out b/tests/qemu-iotests/tests/inactive-node-nbd.out new file mode 100644 index 0000000000..a458b4fc05 --- /dev/null +++ b/tests/qemu-iotests/tests/inactive-node-nbd.out @@ -0,0 +1,239 @@ +Preparing disk... +Launching VM... +{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-nbd.sock"}, "type": "unix"}}} +{"return": {}} + +=== Creating export of inactive node === + +Exports activate nodes without allow-inactive +disk-fmt active: False +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +disk-fmt active: True +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +Exports activate nodes with allow-inactive=false +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"return": {}} +disk-fmt active: False +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +disk-fmt active: True +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +Export leaves nodes inactive with allow-inactive=true +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"return": {}} +disk-fmt active: False +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +disk-fmt active: False +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +=== Inactivating node with existing export === + +Inactivating nodes with an export fails without allow-inactive +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} +disk-fmt active: True +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +Inactivating nodes with an export fails with allow-inactive=false +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} +disk-fmt active: True +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +Inactivating nodes with an export works with allow-inactive=true +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"return": {}} +disk-fmt active: False +{"execute": "query-block-exports", "arguments": {}} +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} +{"execute": "block-export-del", "arguments": {"id": "exp0"}} +{"return": {}} +{"execute": "query-block-exports", "arguments": {}} +{"return": []} + +=== Inactive nodes with parent === + +Inactivating nodes with an active parent fails +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} +{"error": {"class": "GenericError", "desc": "Node has active parent node"}} +disk-file active: True +disk-fmt active: True + +Inactivating nodes with an inactive parent works +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} +{"return": {}} +disk-file active: False +disk-fmt active: False + +Creating active parent node with an inactive child fails +{"execute": "blockdev-add", "arguments": {"driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} +{"execute": "blockdev-add", "arguments": {"active": true, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} + +Creating inactive parent node with an inactive child works +{"execute": "blockdev-add", "arguments": {"active": false, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} +{"return": {}} +{"execute": "blockdev-del", "arguments": {"node-name": "disk-filter"}} +{"return": {}} + +=== Resizing an inactive node === +{"execute": "block_resize", "arguments": {"node-name": "disk-fmt", "size": 16777216}} +{"error": {"class": "GenericError", "desc": "Permission 'resize' unavailable on inactive node"}} + +=== Taking a snapshot of an inactive node === + +Active overlay over inactive backing file automatically makes both inactive for compatibility +{"execute": "blockdev-add", "arguments": {"backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} +{"return": {}} +disk-fmt active: False +snap-fmt active: True +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} +{"return": {}} +disk-fmt active: False +snap-fmt active: False +{"execute": "blockdev-del", "arguments": {"node-name": "snap-fmt"}} +{"return": {}} + +Inactive overlay over inactive backing file just works +{"execute": "blockdev-add", "arguments": {"active": false, "backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} +{"return": {}} +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} +{"return": {}} + +=== Block jobs with inactive nodes === + +Streaming into an inactive node +{"execute": "block-stream", "arguments": {"device": "snap-fmt"}} +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap-fmt' can't be a file child of active 'NODE_NAME'"}} + +Committing an inactive root node (active commit) +{"execute": "block-commit", "arguments": {"device": "snap-fmt", "job-id": "job0"}} +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} + +Committing an inactive intermediate node to inactive base +{"execute": "blockdev-add", "arguments": {"active": false, "backing": "snap-fmt", "driver": "qcow2", "file": "snap2-file", "node-name": "snap2-fmt"}} +{"return": {}} +disk-fmt active: False +snap-fmt active: False +snap2-fmt active: False +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} + +Committing an inactive intermediate node to active base +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} +{"return": {}} +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} + +Mirror from inactive source to active target +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} +{"error": {"class": "GenericError", "desc": "Inactive 'snap2-fmt' can't be a backing child of active 'NODE_NAME'"}} + +Mirror from active source to inactive target +disk-fmt active: True +snap-fmt active: False +snap2-fmt active: False +target-fmt active: True +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "snap2-fmt"}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} +{"return": {}} +disk-fmt active: True +snap-fmt active: True +snap2-fmt active: True +target-fmt active: False +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} +{"error": {"class": "GenericError", "desc": "Permission 'write' unavailable on inactive node"}} + +Backup from active source to inactive target +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'target-fmt' can't be a target child of active 'NODE_NAME'"}} + +Backup from inactive source to active target +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "snap2-fmt"}} +{"return": {}} +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "target-fmt"}} +{"return": {}} +disk-fmt active: False +snap-fmt active: False +snap2-fmt active: False +target-fmt active: True +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap2-fmt' can't be a file child of active 'NODE_NAME'"}} + +=== Accessing export on inactive node === +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} +{"return": {}} +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "target-fmt", "type": "nbd", "writable": true}} +{"return": {}} +read 65536/65536 bytes at offset 0 +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +write failed: Operation not permitted + +write failed: Operation not permitted + +write failed: Operation not permitted + +discard failed: Operation not permitted + + +qemu-io: Failed to get allocation status: Operation not permitted + + +=== Resuming VM activates all images === +{"execute": "cont", "arguments": {}} +{"return": {}} +disk-fmt active: True +snap-fmt active: True +snap2-fmt active: True +target-fmt active: True + +Shutting down... + diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-test b/tests/qemu-iotests/tests/migrate-bitmaps-test index f98e721e97..8fb4099201 100755 --- a/tests/qemu-iotests/tests/migrate-bitmaps-test +++ b/tests/qemu-iotests/tests/migrate-bitmaps-test @@ -122,11 +122,10 @@ class TestDirtyBitmapMigration(iotests.QMPTestCase): # catch 'Could not reopen qcow2 layer: Bitmap already exists' # possible error - log = self.vm_a.get_log() - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) - log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', + log = iotests.filter_qtest(self.vm_a.get_log()) + log = re.sub(r'^(wrote .* bytes at offset .*\n' + r'.*KiB.*ops.*sec.*\n?){3}', '', log) - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) self.assertEqual(log, '') # test that bitmap is still persistent diff --git a/tests/qemu-iotests/tests/qsd-migrate b/tests/qemu-iotests/tests/qsd-migrate new file mode 100755 index 0000000000..de17562cb0 --- /dev/null +++ b/tests/qemu-iotests/tests/qsd-migrate @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# group: rw quick +# +# Copyright (C) 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, see <http://www.gnu.org/licenses/>. +# +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> + +import iotests + +from iotests import filter_qemu_io, filter_qtest + +iotests.script_initialize(supported_fmts=['generic'], + supported_protocols=['file'], + supported_platforms=['linux']) + +with iotests.FilePath('disk.img') as path, \ + iotests.FilePath('nbd-src.sock', base_dir=iotests.sock_dir) as nbd_src, \ + iotests.FilePath('nbd-dst.sock', base_dir=iotests.sock_dir) as nbd_dst, \ + iotests.FilePath('migrate.sock', base_dir=iotests.sock_dir) as mig_sock, \ + iotests.VM(path_suffix="-src") as vm_src, \ + iotests.VM(path_suffix="-dst") as vm_dst: + + img_size = '10M' + + iotests.log('Preparing disk...') + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) + + iotests.log('Launching source QSD...') + qsd_src = iotests.QemuStorageDaemon( + '--blockdev', f'file,node-name=disk-file,filename={path}', + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt', + '--nbd-server', f'addr.type=unix,addr.path={nbd_src}', + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' + 'allow-inactive=true', + qmp=True, + ) + + iotests.log('Launching source VM...') + vm_src.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' + f'server.path={nbd_src},export=disk-fmt') + vm_src.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') + vm_src.launch() + + iotests.log('Launching destination QSD...') + qsd_dst = iotests.QemuStorageDaemon( + '--blockdev', f'file,node-name=disk-file,filename={path},active=off', + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' + f'active=off', + '--nbd-server', f'addr.type=unix,addr.path={nbd_dst}', + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' + 'allow-inactive=true', + qmp=True, + instance_id='b', + ) + + iotests.log('Launching destination VM...') + vm_dst.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' + f'server.path={nbd_dst},export=disk-fmt') + vm_dst.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') + vm_dst.add_args('-incoming', f'unix:{mig_sock}') + vm_dst.launch() + + iotests.log('\nTest I/O on the source') + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x11 0 4k', + use_log=True, qdev=True) + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', + use_log=True, qdev=True) + + iotests.log('\nStarting migration...') + + mig_caps = [ + {'capability': 'events', 'state': True}, + {'capability': 'pause-before-switchover', 'state': True}, + ] + vm_src.qmp_log('migrate-set-capabilities', capabilities=mig_caps) + vm_dst.qmp_log('migrate-set-capabilities', capabilities=mig_caps) + vm_src.qmp_log('migrate', uri=f'unix:{mig_sock}', + filters=[iotests.filter_qmp_testfiles]) + + vm_src.event_wait('MIGRATION', + match={'data': {'status': 'pre-switchover'}}) + + iotests.log('\nPre-switchover: Reconfigure QSD instances') + + iotests.log(qsd_src.qmp('blockdev-set-active', {'active': False})) + + # Reading is okay from both sides while the image is inactive. Note that + # the destination may have stale data until it activates the image, though. + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', + use_log=True, qdev=True) + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read 0 4k', + use_log=True, qdev=True) + + iotests.log(qsd_dst.qmp('blockdev-set-active', {'active': True})) + + iotests.log('\nCompleting migration...') + + vm_src.qmp_log('migrate-continue', state='pre-switchover') + vm_dst.event_wait('MIGRATION', match={'data': {'status': 'completed'}}) + + iotests.log('\nTest I/O on the destination') + + # Now the destination must see what the source wrote + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', + use_log=True, qdev=True) + + # And be able to overwrite it + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x22 0 4k', + use_log=True, qdev=True) + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x22 0 4k', + use_log=True, qdev=True) + + iotests.log('\nDone') + + vm_src.shutdown() + iotests.log('\n--- vm_src log ---') + log = vm_src.get_log() + if log: + iotests.log(log, [filter_qtest, filter_qemu_io]) + qsd_src.stop() + + vm_dst.shutdown() + iotests.log('\n--- vm_dst log ---') + log = vm_dst.get_log() + if log: + iotests.log(log, [filter_qtest, filter_qemu_io]) + qsd_dst.stop() diff --git a/tests/qemu-iotests/tests/qsd-migrate.out b/tests/qemu-iotests/tests/qsd-migrate.out new file mode 100644 index 0000000000..4a5241e5d4 --- /dev/null +++ b/tests/qemu-iotests/tests/qsd-migrate.out @@ -0,0 +1,59 @@ +Preparing disk... +Launching source QSD... +Launching source VM... +Launching destination QSD... +Launching destination VM... + +Test I/O on the source +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x11 0 4k\""}} +{"return": ""} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} +{"return": ""} + +Starting migration... +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} +{"return": {}} +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} +{"return": {}} +{"execute": "migrate", "arguments": {"uri": "unix:SOCK_DIR/PID-migrate.sock"}} +{"return": {}} + +Pre-switchover: Reconfigure QSD instances +{"return": {}} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} +{"return": ""} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read 0 4k\""}} +{"return": ""} +{"return": {}} + +Completing migration... +{"execute": "migrate-continue", "arguments": {"state": "pre-switchover"}} +{"return": {}} + +Test I/O on the destination +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} +{"return": ""} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x22 0 4k\""}} +{"return": ""} +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x22 0 4k\""}} +{"return": ""} + +Done + +--- vm_src log --- +wrote 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) + +--- vm_dst log --- +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +wrote 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +read 4096/4096 bytes at offset 0 +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) diff --git a/tests/qtest/libqos/virtio-pci-modern.c b/tests/qtest/libqos/virtio-pci-modern.c index 18d118866f..4e67fcbd5d 100644 --- a/tests/qtest/libqos/virtio-pci-modern.c +++ b/tests/qtest/libqos/virtio-pci-modern.c @@ -173,13 +173,11 @@ static bool get_config_isr_status(QVirtioDevice *d) static void wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) { - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); gint64 start_time = g_get_monotonic_time(); - do { + while (!get_config_isr_status(d)) { g_assert(g_get_monotonic_time() - start_time <= timeout_us); - qtest_clock_step(dev->pdev->bus->qts, 100); - } while (!get_config_isr_status(d)); + } } static void queue_select(QVirtioDevice *d, uint16_t index) diff --git a/tests/qtest/libqos/virtio-pci.c b/tests/qtest/libqos/virtio-pci.c index 485b8f6b7e..002bf8b8c2 100644 --- a/tests/qtest/libqos/virtio-pci.c +++ b/tests/qtest/libqos/virtio-pci.c @@ -171,13 +171,11 @@ static bool qvirtio_pci_get_config_isr_status(QVirtioDevice *d) static void qvirtio_pci_wait_config_isr_status(QVirtioDevice *d, gint64 timeout_us) { - QVirtioPCIDevice *dev = container_of(d, QVirtioPCIDevice, vdev); gint64 start_time = g_get_monotonic_time(); - do { + while (!qvirtio_pci_get_config_isr_status(d)) { g_assert(g_get_monotonic_time() - start_time <= timeout_us); - qtest_clock_step(dev->pdev->bus->qts, 100); - } while (!qvirtio_pci_get_config_isr_status(d)); + } } static void qvirtio_pci_queue_select(QVirtioDevice *d, uint16_t index) diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 5e062c752a..68316dbdc1 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -405,6 +405,8 @@ foreach dir : target_dirs target_base = dir.split('-')[0] qtest_emulator = emulators['qemu-system-' + target_base] target_qtests = get_variable('qtests_' + target_base, []) + qtests_generic + has_kvm = ('CONFIG_KVM' in config_all_accel and host_os == 'linux' + and cpu == target_base and fs.exists('/dev/kvm')) test_deps = roms qtest_env = environment() @@ -438,11 +440,18 @@ foreach dir : target_dirs test: executable(test, src, dependencies: deps) } endif + + test_args = ['--tap', '-k'] + + if test == 'migration-test' and has_kvm + test_args += ['--full'] + endif + test('qtest-@0@/@1@'.format(target_base, test), qtest_executables[test], depends: [test_deps, qtest_emulator, emulator_modules], env: qtest_env, - args: ['--tap', '-k'], + args: test_args, protocol: 'tap', timeout: slow_qtests.get(test, 60), priority: slow_qtests.get(test, 60), diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 5cad5060b3..0893687174 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -14,13 +14,38 @@ #include "migration/framework.h" #include "qemu/module.h" +static void parse_args(int *argc_p, char ***argv_p, bool *full_set) +{ + int argc = *argc_p; + char **argv = *argv_p; + int i, j; + + j = 1; + for (i = 1; i < argc; i++) { + if (g_str_equal(argv[i], "--full")) { + *full_set = true; + continue; + } + argv[j++] = argv[i]; + if (i >= j) { + argv[i] = NULL; + } + } + *argc_p = j; +} + int main(int argc, char **argv) { MigrationTestEnv *env; int ret; + bool full_set = false; + + /* strip the --full option if it's present */ + parse_args(&argc, &argv, &full_set); g_test_init(&argc, &argv, NULL); env = migration_get_env(); + env->full_set = full_set; module_call_init(MODULE_INIT_QOM); migration_test_add_tls(env); diff --git a/tests/qtest/migration/compression-tests.c b/tests/qtest/migration/compression-tests.c index d78f1f11f1..8b58401b84 100644 --- a/tests/qtest/migration/compression-tests.c +++ b/tests/qtest/migration/compression-tests.c @@ -151,10 +151,22 @@ static void test_multifd_tcp_zlib(void) test_precopy_common(&args); } +static void migration_test_add_compression_smoke(MigrationTestEnv *env) +{ + migration_test_add("/migration/multifd/tcp/plain/zlib", + test_multifd_tcp_zlib); +} + void migration_test_add_compression(MigrationTestEnv *env) { tmpfs = env->tmpfs; + migration_test_add_compression_smoke(env); + + if (!env->full_set) { + return; + } + #ifdef CONFIG_ZSTD migration_test_add("/migration/multifd/tcp/plain/zstd", test_multifd_tcp_zstd); @@ -179,7 +191,4 @@ void migration_test_add_compression(MigrationTestEnv *env) migration_test_add("/migration/precopy/unix/xbzrle", test_precopy_unix_xbzrle); } - - migration_test_add("/migration/multifd/tcp/plain/zlib", - test_multifd_tcp_zlib); } diff --git a/tests/qtest/migration/cpr-tests.c b/tests/qtest/migration/cpr-tests.c index 215b0df8c0..4758841824 100644 --- a/tests/qtest/migration/cpr-tests.c +++ b/tests/qtest/migration/cpr-tests.c @@ -104,6 +104,12 @@ void migration_test_add_cpr(MigrationTestEnv *env) { tmpfs = env->tmpfs; + /* no tests in the smoke set for now */ + + if (!env->full_set) { + return; + } + /* * Our CI system has problems with shared memory. * Don't run this test until we find a workaround. diff --git a/tests/qtest/migration/file-tests.c b/tests/qtest/migration/file-tests.c index 6400ddca51..f260e2871d 100644 --- a/tests/qtest/migration/file-tests.c +++ b/tests/qtest/migration/file-tests.c @@ -300,12 +300,24 @@ static void test_multifd_file_mapped_ram_fdset_dio(void) } #endif /* !_WIN32 */ +static void migration_test_add_file_smoke(MigrationTestEnv *env) +{ + migration_test_add("/migration/precopy/file", + test_precopy_file); + + migration_test_add("/migration/multifd/file/mapped-ram/dio", + test_multifd_file_mapped_ram_dio); +} + void migration_test_add_file(MigrationTestEnv *env) { tmpfs = env->tmpfs; - migration_test_add("/migration/precopy/file", - test_precopy_file); + migration_test_add_file_smoke(env); + + if (!env->full_set) { + return; + } migration_test_add("/migration/precopy/file/offset", test_precopy_file_offset); @@ -326,9 +338,6 @@ void migration_test_add_file(MigrationTestEnv *env) migration_test_add("/migration/multifd/file/mapped-ram/live", test_multifd_file_mapped_ram_live); - migration_test_add("/migration/multifd/file/mapped-ram/dio", - test_multifd_file_mapped_ram_dio); - #ifndef _WIN32 migration_test_add("/migration/multifd/file/mapped-ram/fdset", test_multifd_file_mapped_ram_fdset); diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h index cb4a984700..e4a11870f6 100644 --- a/tests/qtest/migration/framework.h +++ b/tests/qtest/migration/framework.h @@ -24,6 +24,7 @@ typedef struct MigrationTestEnv { bool uffd_feature_thread_id; bool has_dirty_ring; bool is_x86; + bool full_set; const char *arch; const char *qemu_src; const char *qemu_dst; diff --git a/tests/qtest/migration/misc-tests.c b/tests/qtest/migration/misc-tests.c index 04e5a472d5..2e612d9e38 100644 --- a/tests/qtest/migration/misc-tests.c +++ b/tests/qtest/migration/misc-tests.c @@ -258,14 +258,24 @@ static void test_validate_uri_channels_none_set(void) do_test_validate_uri_channel(&args); } +static void migration_test_add_misc_smoke(MigrationTestEnv *env) +{ +#ifndef _WIN32 + migration_test_add("/migration/analyze-script", test_analyze_script); +#endif +} + void migration_test_add_misc(MigrationTestEnv *env) { tmpfs = env->tmpfs; + migration_test_add_misc_smoke(env); + + if (!env->full_set) { + return; + } + migration_test_add("/migration/bad_dest", test_baddest); -#ifndef _WIN32 - migration_test_add("/migration/analyze-script", test_analyze_script); -#endif /* * Our CI system has problems with shared memory. diff --git a/tests/qtest/migration/postcopy-tests.c b/tests/qtest/migration/postcopy-tests.c index 59e8c124c3..982457bed1 100644 --- a/tests/qtest/migration/postcopy-tests.c +++ b/tests/qtest/migration/postcopy-tests.c @@ -79,7 +79,7 @@ static void test_postcopy_preempt_recovery(void) test_postcopy_recovery_common(&args); } -void migration_test_add_postcopy(MigrationTestEnv *env) +static void migration_test_add_postcopy_smoke(MigrationTestEnv *env) { if (env->has_uffd) { migration_test_add("/migration/postcopy/plain", test_postcopy); @@ -87,6 +87,18 @@ void migration_test_add_postcopy(MigrationTestEnv *env) test_postcopy_recovery); migration_test_add("/migration/postcopy/preempt/plain", test_postcopy_preempt); + } +} + +void migration_test_add_postcopy(MigrationTestEnv *env) +{ + migration_test_add_postcopy_smoke(env); + + if (!env->full_set) { + return; + } + + if (env->has_uffd) { migration_test_add("/migration/postcopy/preempt/recovery/plain", test_postcopy_preempt_recovery); diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c index 7d6d4f56e2..162fa69531 100644 --- a/tests/qtest/migration/precopy-tests.c +++ b/tests/qtest/migration/precopy-tests.c @@ -951,10 +951,8 @@ static void test_dirty_limit(void) migrate_end(from, to, true); } -void migration_test_add_precopy(MigrationTestEnv *env) +static void migration_test_add_precopy_smoke(MigrationTestEnv *env) { - tmpfs = env->tmpfs; - if (env->is_x86) { migration_test_add("/migration/precopy/unix/suspend/live", test_precopy_unix_suspend_live); @@ -966,6 +964,21 @@ void migration_test_add_precopy(MigrationTestEnv *env) test_precopy_unix_plain); migration_test_add("/migration/precopy/tcp/plain", test_precopy_tcp_plain); + migration_test_add("/migration/multifd/tcp/uri/plain/none", + test_multifd_tcp_uri_none); + migration_test_add("/migration/multifd/tcp/plain/cancel", + test_multifd_tcp_cancel); +} + +void migration_test_add_precopy(MigrationTestEnv *env) +{ + tmpfs = env->tmpfs; + + migration_test_add_precopy_smoke(env); + + if (!env->full_set) { + return; + } migration_test_add("/migration/precopy/tcp/plain/switchover-ack", test_precopy_tcp_switchover_ack); @@ -989,16 +1002,12 @@ void migration_test_add_precopy(MigrationTestEnv *env) test_dirty_limit); } } - migration_test_add("/migration/multifd/tcp/uri/plain/none", - test_multifd_tcp_uri_none); migration_test_add("/migration/multifd/tcp/channels/plain/none", test_multifd_tcp_channels_none); migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy", test_multifd_tcp_zero_page_legacy); migration_test_add("/migration/multifd/tcp/plain/zero-page/none", test_multifd_tcp_no_zero_page); - migration_test_add("/migration/multifd/tcp/plain/cancel", - test_multifd_tcp_cancel); if (g_str_equal(env->arch, "x86_64") && env->has_kvm && env->has_dirty_ring) { diff --git a/tests/qtest/migration/tls-tests.c b/tests/qtest/migration/tls-tests.c index 5704a1f992..2cb4a44bcd 100644 --- a/tests/qtest/migration/tls-tests.c +++ b/tests/qtest/migration/tls-tests.c @@ -722,10 +722,22 @@ static void test_multifd_tcp_tls_x509_reject_anon_client(void) } #endif /* CONFIG_TASN1 */ +static void migration_test_add_tls_smoke(MigrationTestEnv *env) +{ + migration_test_add("/migration/precopy/tcp/tls/psk/match", + test_precopy_tcp_tls_psk_match); +} + void migration_test_add_tls(MigrationTestEnv *env) { tmpfs = env->tmpfs; + migration_test_add_tls_smoke(env); + + if (!env->full_set) { + return; + } + migration_test_add("/migration/precopy/unix/tls/psk", test_precopy_unix_tls_psk); @@ -751,8 +763,6 @@ void migration_test_add_tls(MigrationTestEnv *env) test_precopy_unix_tls_x509_override_host); #endif /* CONFIG_TASN1 */ - migration_test_add("/migration/precopy/tcp/tls/psk/match", - test_precopy_tcp_tls_psk_match); migration_test_add("/migration/precopy/tcp/tls/psk/mismatch", test_precopy_tcp_tls_psk_mismatch); #ifdef CONFIG_TASN1 diff --git a/tests/qtest/npcm7xx_timer-test.c b/tests/qtest/npcm7xx_timer-test.c index 58f58c2f71..43711049ca 100644 --- a/tests/qtest/npcm7xx_timer-test.c +++ b/tests/qtest/npcm7xx_timer-test.c @@ -465,7 +465,6 @@ static void test_periodic_interrupt(gconstpointer test_data) int i; tim_reset(td); - clock_step_next(); tim_write_ticr(td, count); tim_write_tcsr(td, CEN | IE | MODE_PERIODIC | PRESCALE(ps)); diff --git a/tests/tcg/arm/Makefile.target b/tests/tcg/arm/Makefile.target index 06ddf3e04f..99a953b667 100644 --- a/tests/tcg/arm/Makefile.target +++ b/tests/tcg/arm/Makefile.target @@ -20,13 +20,6 @@ ARM_TESTS = hello-arm hello-arm: CFLAGS+=-marm -ffreestanding -fno-stack-protector hello-arm: LDFLAGS+=-nostdlib -# IWMXT floating point extensions -ARM_TESTS += test-arm-iwmmxt -# Clang assembler does not support IWMXT, so use the external assembler. -test-arm-iwmmxt: CFLAGS += -marm -march=iwmmxt -mabi=aapcs -mfpu=fpv4-sp-d16 $(CROSS_CC_HAS_FNIA) -test-arm-iwmmxt: test-arm-iwmmxt.S - $(CC) $(CFLAGS) -Wa,--noexecstack $< -o $@ $(LDFLAGS) - # Float-convert Tests ARM_TESTS += fcvt fcvt: LDFLAGS += -lm diff --git a/tests/tcg/arm/README b/tests/tcg/arm/README index e6307116e2..aceccc127f 100644 --- a/tests/tcg/arm/README +++ b/tests/tcg/arm/README @@ -4,8 +4,3 @@ hello-arm --------- A very simple inline assembly, write syscall based hello world - -test-arm-iwmmxt ---------------- - -A simple test case for older iwmmxt extended ARMs diff --git a/tests/tcg/arm/test-arm-iwmmxt.S b/tests/tcg/arm/test-arm-iwmmxt.S deleted file mode 100644 index d647f9404a..0000000000 --- a/tests/tcg/arm/test-arm-iwmmxt.S +++ /dev/null @@ -1,49 +0,0 @@ -@ Checks whether iwMMXt is functional. -.code 32 -.globl main - -main: -ldr r0, =data0 -ldr r1, =data1 -ldr r2, =data2 -#ifndef FPA -wldrd wr0, [r0, #0] -wldrd wr1, [r0, #8] -wldrd wr2, [r1, #0] -wldrd wr3, [r1, #8] -wsubb wr2, wr2, wr0 -wsubb wr3, wr3, wr1 -wldrd wr0, [r2, #0] -wldrd wr1, [r2, #8] -waddb wr0, wr0, wr2 -waddb wr1, wr1, wr3 -wstrd wr0, [r2, #0] -wstrd wr1, [r2, #8] -#else -ldfe f0, [r0, #0] -ldfe f1, [r0, #8] -ldfe f2, [r1, #0] -ldfe f3, [r1, #8] -adfdp f2, f2, f0 -adfdp f3, f3, f1 -ldfe f0, [r2, #0] -ldfe f1, [r2, #8] -adfd f0, f0, f2 -adfd f1, f1, f3 -stfe f0, [r2, #0] -stfe f1, [r2, #8] -#endif -mov r0, #1 -mov r1, r2 -mov r2, #0x11 -swi #0x900004 -mov r0, #0 -swi #0x900001 - -.data -data0: -.string "aaaabbbbccccdddd" -data1: -.string "bbbbccccddddeeee" -data2: -.string "hvLLWs\x1fsdrs9\x1fNJ-\n" diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index 18d3cf4ae0..688a6be203 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -130,6 +130,13 @@ run-gdbstub-follow-fork-mode-parent: follow-fork-mode --bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \ following parents on fork) +run-gdbstub-late-attach: late-attach + $(call run-test, $@, env LATE_ATTACH_PY=1 $(GDB_SCRIPT) \ + --gdb $(GDB) \ + --qemu $(QEMU) --qargs "$(QEMU_OPTS)" --no-suspend \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/late-attach.py, \ + attaching to a running process) + else run-gdbstub-%: $(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support") @@ -139,7 +146,7 @@ EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \ run-gdbstub-registers run-gdbstub-prot-none \ run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \ run-gdbstub-follow-fork-mode-parent \ - run-gdbstub-qxfer-siginfo-read + run-gdbstub-qxfer-siginfo-read run-gdbstub-late-attach # ARM Compatible Semi Hosting Tests # diff --git a/tests/tcg/multiarch/gdbstub/late-attach.py b/tests/tcg/multiarch/gdbstub/late-attach.py new file mode 100644 index 0000000000..1d40efb5ec --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/late-attach.py @@ -0,0 +1,28 @@ +"""Test attaching GDB to a running process. + +SPDX-License-Identifier: GPL-2.0-or-later +""" +from test_gdbstub import main, report + + +def run_test(): + """Run through the tests one by one""" + try: + phase = gdb.parse_and_eval("phase").string() + except gdb.error: + # Assume the guest did not reach main(). + phase = "start" + + if phase == "start": + gdb.execute("break sigwait") + gdb.execute("continue") + phase = gdb.parse_and_eval("phase").string() + report(phase == "sigwait", "{} == \"sigwait\"".format(phase)) + + gdb.execute("signal SIGUSR1") + + exitcode = int(gdb.parse_and_eval("$_exitcode")) + report(exitcode == 0, "{} == 0".format(exitcode)) + + +main(run_test) diff --git a/tests/tcg/multiarch/late-attach.c b/tests/tcg/multiarch/late-attach.c new file mode 100644 index 0000000000..20a364034b --- /dev/null +++ b/tests/tcg/multiarch/late-attach.c @@ -0,0 +1,41 @@ +/* + * Test attaching GDB to a running process. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include <assert.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +static const char *phase = "start"; + +int main(void) +{ + sigset_t set; + int sig; + + assert(sigfillset(&set) == 0); + assert(sigprocmask(SIG_BLOCK, &set, NULL) == 0); + + /* Let GDB know it can send SIGUSR1. */ + phase = "sigwait"; + if (getenv("LATE_ATTACH_PY")) { + assert(sigwait(&set, &sig) == 0); + if (sig != SIGUSR1) { + fprintf(stderr, "Unexpected signal %d\n", sig); + return EXIT_FAILURE; + } + } + + /* Check that the guest does not see host_interrupt_signal. */ + assert(sigpending(&set) == 0); + for (sig = 1; sig < NSIG; sig++) { + if (sigismember(&set, sig)) { + fprintf(stderr, "Unexpected signal %d\n", sig); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/util/meson.build b/util/meson.build index 5d8bef9891..780b5977a8 100644 --- a/util/meson.build +++ b/util/meson.build @@ -84,6 +84,8 @@ if have_block or have_ga util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c')) util_ss.add(files(f'coroutine-@coroutine_backend@.c')) util_ss.add(files('thread-pool.c', 'qemu-timer.c')) +endif +if have_block or have_ga or have_user util_ss.add(files('qemu-sockets.c')) endif if have_block diff --git a/util/oslib-posix.c b/util/oslib-posix.c index 2bb34dade3..a697c602c6 100644 --- a/util/oslib-posix.c +++ b/util/oslib-posix.c @@ -111,6 +111,21 @@ int qemu_get_thread_id(void) #endif } +int qemu_kill_thread(int tid, int sig) +{ +#if defined(__linux__) + return syscall(__NR_tgkill, getpid(), tid, sig); +#elif defined(__FreeBSD__) + return thr_kill2(getpid(), tid, sig); +#elif defined(__NetBSD__) + return _lwp_kill(tid, sig); +#elif defined(__OpenBSD__) + return thrkill(tid, sig, NULL); +#else + return kill(tid, sig); +#endif +} + int qemu_daemon(int nochdir, int noclose) { return daemon(nochdir, noclose); diff --git a/util/qemu-timer.c b/util/qemu-timer.c index 0e8a453eaa..3243d2c515 100644 --- a/util/qemu-timer.c +++ b/util/qemu-timer.c @@ -675,17 +675,10 @@ int64_t qemu_clock_advance_virtual_time(int64_t dest) { int64_t clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); AioContext *aio_context; - int64_t deadline; - aio_context = qemu_get_aio_context(); - - deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, - QEMU_TIMER_ATTR_ALL); - /* - * A deadline of < 0 indicates this timer is not enabled, so we - * won't get far trying to run it forward. - */ - while (deadline >= 0 && clock < dest) { + while (clock < dest) { + int64_t deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, + QEMU_TIMER_ATTR_ALL); int64_t warp = qemu_soonest_timeout(dest - clock, deadline); qemu_virtual_clock_set_ns(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + warp); @@ -693,9 +686,6 @@ int64_t qemu_clock_advance_virtual_time(int64_t dest) qemu_clock_run_timers(QEMU_CLOCK_VIRTUAL); timerlist_run_timers(aio_context->tlg.tl[QEMU_CLOCK_VIRTUAL]); clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - - deadline = qemu_clock_deadline_ns_all(QEMU_CLOCK_VIRTUAL, - QEMU_TIMER_ATTR_ALL); } qemu_clock_notify(QEMU_CLOCK_VIRTUAL); |