diff options
431 files changed, 13429 insertions, 5338 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index e88ed2c..bc0af3e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -502,13 +502,14 @@ F: include/exec/target_long.h F: include/qemu/accel.h F: include/system/accel-*.h F: include/system/cpus.h -F: include/accel/accel-cpu*.h +F: include/accel/accel-*.h F: accel/accel-*.? F: accel/dummy-cpus.? F: accel/Makefile.objs F: accel/stubs/Makefile.objs F: cpu-common.c F: cpu-target.c +F: qapi/accelerator.json F: system/cpus.c Apple Silicon HVF CPUs @@ -4287,6 +4288,7 @@ F: tests/functional/test_multiprocess.py VFIO-USER: M: John Levon <john.levon@nutanix.com> M: Thanos Makatos <thanos.makatos@nutanix.com> +M: Cédric Le Goater <clg@redhat.com> S: Supported F: docs/interop/vfio-user.rst F: docs/system/devices/vfio-user.rst diff --git a/accel/accel-common.c b/accel/accel-common.c index 591ff4c..850c5ab 100644 --- a/accel/accel-common.c +++ b/accel/accel-common.c @@ -10,7 +10,9 @@ #include "qemu/osdep.h" #include "qemu/accel.h" #include "qemu/target-info.h" +#include "accel/accel-ops.h" #include "accel/accel-cpu.h" +#include "accel/accel-cpu-ops.h" #include "accel-internal.h" /* Lookup AccelClass from opt_name. Returns NULL if not found */ diff --git a/accel/accel-qmp.c b/accel/accel-qmp.c new file mode 100644 index 0000000..5fb70c6 --- /dev/null +++ b/accel/accel-qmp.c @@ -0,0 +1,35 @@ +/* + * QMP commands related to accelerators + * + * Copyright (c) Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/accel.h" +#include "qapi/type-helpers.h" +#include "qapi/qapi-commands-accelerator.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" +#include "hw/core/cpu.h" + +HumanReadableText *qmp_x_accel_stats(Error **errp) +{ + AccelState *accel = current_accel(); + AccelClass *acc = ACCEL_GET_CLASS(accel); + g_autoptr(GString) buf = g_string_new(""); + + if (acc->get_stats) { + acc->get_stats(accel, buf); + } + if (acc->ops->get_vcpu_stats) { + CPUState *cpu; + + CPU_FOREACH(cpu) { + acc->ops->get_vcpu_stats(cpu, buf); + } + } + + return human_readable_text_from_str(buf); +} diff --git a/accel/accel-system.c b/accel/accel-system.c index c54c30f..1e97c64 100644 --- a/accel/accel-system.c +++ b/accel/accel-system.c @@ -25,8 +25,12 @@ #include "qemu/osdep.h" #include "qemu/accel.h" +#include "qapi/qapi-commands-accelerator.h" +#include "monitor/monitor.h" #include "hw/boards.h" -#include "system/accel-ops.h" +#include "hw/core/cpu.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "qemu/error-report.h" #include "accel-internal.h" @@ -101,11 +105,17 @@ void accel_init_ops_interfaces(AccelClass *ac) cpus_register_accel(ops); } +static void accel_ops_class_init(ObjectClass *oc, const void *data) +{ + monitor_register_hmp_info_hrt("accel", qmp_x_accel_stats); +} + static const TypeInfo accel_ops_type_info = { .name = TYPE_ACCEL_OPS, .parent = TYPE_OBJECT, .abstract = true, .class_size = sizeof(AccelOpsClass), + .class_init = accel_ops_class_init, }; static void accel_system_register_types(void) diff --git a/accel/hvf/hvf-accel-ops.c b/accel/hvf/hvf-accel-ops.c index be8724a..d488d6a 100644 --- a/accel/hvf/hvf-accel-ops.c +++ b/accel/hvf/hvf-accel-ops.c @@ -54,10 +54,11 @@ #include "gdbstub/enums.h" #include "exec/cpu-common.h" #include "hw/core/cpu.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "system/hvf.h" #include "system/hvf_int.h" +#include <mach/mach_time.h> HVFState *hvf_state; @@ -118,6 +119,12 @@ static void dummy_signal(int sig) { } +static void do_hvf_get_vcpu_exec_time(CPUState *cpu, run_on_cpu_data arg) +{ + int r = hv_vcpu_get_exec_time(cpu->accel->fd, arg.host_ptr); + assert_hvf_ok(r); +} + static void hvf_vcpu_destroy(CPUState *cpu) { hv_return_t ret = hv_vcpu_destroy(cpu->accel->fd); @@ -347,6 +354,21 @@ static void hvf_remove_all_breakpoints(CPUState *cpu) } } +static void hvf_get_vcpu_stats(CPUState *cpu, GString *buf) +{ + uint64_t time_mach; /* units of mach_absolute_time() */ + + run_on_cpu(cpu, do_hvf_get_vcpu_exec_time, RUN_ON_CPU_HOST_PTR(&time_mach)); + + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + uint64_t time_ns = time_mach * timebase.numer / timebase.denom; + + g_string_append_printf(buf, "HVF cumulative execution time: %llu.%.3llus\n", + time_ns / 1000000000, + (time_ns % 1000000000) / 1000000); +} + static void hvf_accel_ops_class_init(ObjectClass *oc, const void *data) { AccelOpsClass *ops = ACCEL_OPS_CLASS(oc); @@ -365,7 +387,10 @@ static void hvf_accel_ops_class_init(ObjectClass *oc, const void *data) ops->remove_all_breakpoints = hvf_remove_all_breakpoints; ops->update_guest_debug = hvf_update_guest_debug; ops->supports_guest_debug = hvf_arch_supports_guest_debug; + + ops->get_vcpu_stats = hvf_get_vcpu_stats; }; + static const TypeInfo hvf_accel_ops_type = { .name = ACCEL_OPS_NAME("hvf"), diff --git a/accel/hvf/hvf-all.c b/accel/hvf/hvf-all.c index 1fa07c8..e67a810 100644 --- a/accel/hvf/hvf-all.c +++ b/accel/hvf/hvf-all.c @@ -10,6 +10,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "accel/accel-ops.h" #include "system/address-spaces.h" #include "system/memory.h" #include "system/hvf.h" diff --git a/accel/kvm/kvm-accel-ops.c b/accel/kvm/kvm-accel-ops.c index 0eafc90..b709187 100644 --- a/accel/kvm/kvm-accel-ops.c +++ b/accel/kvm/kvm-accel-ops.c @@ -16,7 +16,7 @@ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/kvm.h" #include "system/kvm_int.h" #include "system/runstate.h" diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index a106d1b..890d5ea 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -32,6 +32,7 @@ #include "system/runstate.h" #include "system/cpus.h" #include "system/accel-blocker.h" +#include "accel/accel-ops.h" #include "qemu/bswap.h" #include "exec/tswap.h" #include "system/memory.h" @@ -2606,8 +2607,7 @@ static int kvm_init(AccelState *as, MachineState *ms) { MachineClass *mc = MACHINE_GET_CLASS(ms); static const char upgrade_note[] = - "Please upgrade to at least kernel 2.6.29 or recent kvm-kmod\n" - "(see http://sourceforge.net/projects/kvm).\n"; + "Please upgrade to at least kernel 4.5.\n"; const struct { const char *name; int num; diff --git a/accel/meson.build b/accel/meson.build index 5290931..25b0f10 100644 --- a/accel/meson.build +++ b/accel/meson.build @@ -1,6 +1,6 @@ common_ss.add(files('accel-common.c')) specific_ss.add(files('accel-target.c')) -system_ss.add(files('accel-system.c', 'accel-blocker.c')) +system_ss.add(files('accel-system.c', 'accel-blocker.c', 'accel-qmp.c')) user_ss.add(files('accel-user.c')) subdir('tcg') diff --git a/accel/qtest/qtest.c b/accel/qtest/qtest.c index 2b83126..1d4337d 100644 --- a/accel/qtest/qtest.c +++ b/accel/qtest/qtest.c @@ -18,7 +18,8 @@ #include "qemu/option.h" #include "qemu/config-file.h" #include "qemu/accel.h" -#include "system/accel-ops.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/qtest.h" #include "system/cpus.h" #include "qemu/guest-random.h" diff --git a/accel/tcg/internal-common.h b/accel/tcg/internal-common.h index 77a3a06..6adfeef 100644 --- a/accel/tcg/internal-common.h +++ b/accel/tcg/internal-common.h @@ -139,6 +139,6 @@ G_NORETURN void cpu_io_recompile(CPUState *cpu, uintptr_t retaddr); void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr); void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr); -void tcg_dump_stats(GString *buf); +void tcg_get_stats(AccelState *accel, GString *buf); #endif diff --git a/accel/tcg/meson.build b/accel/tcg/meson.build index 575e92b..002aa8f 100644 --- a/accel/tcg/meson.build +++ b/accel/tcg/meson.build @@ -11,6 +11,7 @@ tcg_ss.add(files( 'tcg-runtime-gvec.c', 'tb-maint.c', 'tcg-all.c', + 'tcg-stats.c', 'translate-all.c', 'translator.c', )) diff --git a/accel/tcg/monitor.c b/accel/tcg/monitor.c index e7ed728..be5c195 100644 --- a/accel/tcg/monitor.c +++ b/accel/tcg/monitor.c @@ -7,205 +7,13 @@ */ #include "qemu/osdep.h" -#include "qemu/accel.h" -#include "qemu/qht.h" #include "qapi/error.h" #include "qapi/type-helpers.h" #include "qapi/qapi-commands-machine.h" #include "monitor/monitor.h" -#include "system/cpu-timers.h" -#include "exec/icount.h" #include "system/tcg.h" #include "tcg/tcg.h" #include "internal-common.h" -#include "tb-context.h" - - -static void dump_drift_info(GString *buf) -{ - if (!icount_enabled()) { - return; - } - - g_string_append_printf(buf, "Host - Guest clock %"PRIi64" ms\n", - (cpu_get_clock() - icount_get()) / SCALE_MS); - if (icount_align_option) { - g_string_append_printf(buf, "Max guest delay %"PRIi64" ms\n", - -max_delay / SCALE_MS); - g_string_append_printf(buf, "Max guest advance %"PRIi64" ms\n", - max_advance / SCALE_MS); - } else { - g_string_append_printf(buf, "Max guest delay NA\n"); - g_string_append_printf(buf, "Max guest advance NA\n"); - } -} - -static void dump_accel_info(GString *buf) -{ - AccelState *accel = current_accel(); - bool one_insn_per_tb = object_property_get_bool(OBJECT(accel), - "one-insn-per-tb", - &error_fatal); - - g_string_append_printf(buf, "Accelerator settings:\n"); - g_string_append_printf(buf, "one-insn-per-tb: %s\n\n", - one_insn_per_tb ? "on" : "off"); -} - -static void print_qht_statistics(struct qht_stats hst, GString *buf) -{ - uint32_t hgram_opts; - size_t hgram_bins; - char *hgram; - - if (!hst.head_buckets) { - return; - } - g_string_append_printf(buf, "TB hash buckets %zu/%zu " - "(%0.2f%% head buckets used)\n", - hst.used_head_buckets, hst.head_buckets, - (double)hst.used_head_buckets / - hst.head_buckets * 100); - - hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS; - hgram_opts |= QDIST_PR_100X | QDIST_PR_PERCENT; - if (qdist_xmax(&hst.occupancy) - qdist_xmin(&hst.occupancy) == 1) { - hgram_opts |= QDIST_PR_NODECIMAL; - } - hgram = qdist_pr(&hst.occupancy, 10, hgram_opts); - g_string_append_printf(buf, "TB hash occupancy %0.2f%% avg chain occ. " - "Histogram: %s\n", - qdist_avg(&hst.occupancy) * 100, hgram); - g_free(hgram); - - hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS; - hgram_bins = qdist_xmax(&hst.chain) - qdist_xmin(&hst.chain); - if (hgram_bins > 10) { - hgram_bins = 10; - } else { - hgram_bins = 0; - hgram_opts |= QDIST_PR_NODECIMAL | QDIST_PR_NOBINRANGE; - } - hgram = qdist_pr(&hst.chain, hgram_bins, hgram_opts); - g_string_append_printf(buf, "TB hash avg chain %0.3f buckets. " - "Histogram: %s\n", - qdist_avg(&hst.chain), hgram); - g_free(hgram); -} - -struct tb_tree_stats { - size_t nb_tbs; - size_t host_size; - size_t target_size; - size_t max_target_size; - size_t direct_jmp_count; - size_t direct_jmp2_count; - size_t cross_page; -}; - -static gboolean tb_tree_stats_iter(gpointer key, gpointer value, gpointer data) -{ - const TranslationBlock *tb = value; - struct tb_tree_stats *tst = data; - - tst->nb_tbs++; - tst->host_size += tb->tc.size; - tst->target_size += tb->size; - if (tb->size > tst->max_target_size) { - tst->max_target_size = tb->size; - } - if (tb->page_addr[1] != -1) { - tst->cross_page++; - } - if (tb->jmp_reset_offset[0] != TB_JMP_OFFSET_INVALID) { - tst->direct_jmp_count++; - if (tb->jmp_reset_offset[1] != TB_JMP_OFFSET_INVALID) { - tst->direct_jmp2_count++; - } - } - return false; -} - -static void tlb_flush_counts(size_t *pfull, size_t *ppart, size_t *pelide) -{ - CPUState *cpu; - size_t full = 0, part = 0, elide = 0; - - CPU_FOREACH(cpu) { - full += qatomic_read(&cpu->neg.tlb.c.full_flush_count); - part += qatomic_read(&cpu->neg.tlb.c.part_flush_count); - elide += qatomic_read(&cpu->neg.tlb.c.elide_flush_count); - } - *pfull = full; - *ppart = part; - *pelide = elide; -} - -static void tcg_dump_flush_info(GString *buf) -{ - size_t flush_full, flush_part, flush_elide; - - g_string_append_printf(buf, "TB flush count %u\n", - qatomic_read(&tb_ctx.tb_flush_count)); - g_string_append_printf(buf, "TB invalidate count %u\n", - qatomic_read(&tb_ctx.tb_phys_invalidate_count)); - - tlb_flush_counts(&flush_full, &flush_part, &flush_elide); - g_string_append_printf(buf, "TLB full flushes %zu\n", flush_full); - g_string_append_printf(buf, "TLB partial flushes %zu\n", flush_part); - g_string_append_printf(buf, "TLB elided flushes %zu\n", flush_elide); -} - -static void dump_exec_info(GString *buf) -{ - struct tb_tree_stats tst = {}; - struct qht_stats hst; - size_t nb_tbs; - - tcg_tb_foreach(tb_tree_stats_iter, &tst); - nb_tbs = tst.nb_tbs; - /* XXX: avoid using doubles ? */ - g_string_append_printf(buf, "Translation buffer state:\n"); - /* - * Report total code size including the padding and TB structs; - * otherwise users might think "-accel tcg,tb-size" is not honoured. - * For avg host size we use the precise numbers from tb_tree_stats though. - */ - g_string_append_printf(buf, "gen code size %zu/%zu\n", - tcg_code_size(), tcg_code_capacity()); - g_string_append_printf(buf, "TB count %zu\n", nb_tbs); - g_string_append_printf(buf, "TB avg target size %zu max=%zu bytes\n", - nb_tbs ? tst.target_size / nb_tbs : 0, - tst.max_target_size); - g_string_append_printf(buf, "TB avg host size %zu bytes " - "(expansion ratio: %0.1f)\n", - nb_tbs ? tst.host_size / nb_tbs : 0, - tst.target_size ? - (double)tst.host_size / tst.target_size : 0); - g_string_append_printf(buf, "cross page TB count %zu (%zu%%)\n", - tst.cross_page, - nb_tbs ? (tst.cross_page * 100) / nb_tbs : 0); - g_string_append_printf(buf, "direct jump count %zu (%zu%%) " - "(2 jumps=%zu %zu%%)\n", - tst.direct_jmp_count, - nb_tbs ? (tst.direct_jmp_count * 100) / nb_tbs : 0, - tst.direct_jmp2_count, - nb_tbs ? (tst.direct_jmp2_count * 100) / nb_tbs : 0); - - qht_statistics_init(&tb_ctx.htable, &hst); - print_qht_statistics(hst, buf); - qht_statistics_destroy(&hst); - - g_string_append_printf(buf, "\nStatistics:\n"); - tcg_dump_flush_info(buf); -} - -void tcg_dump_stats(GString *buf) -{ - dump_accel_info(buf); - dump_exec_info(buf); - dump_drift_info(buf); -} HumanReadableText *qmp_x_query_jit(Error **errp) { diff --git a/accel/tcg/tcg-accel-ops-rr.c b/accel/tcg/tcg-accel-ops-rr.c index a578698..6eec5c9 100644 --- a/accel/tcg/tcg-accel-ops-rr.c +++ b/accel/tcg/tcg-accel-ops-rr.c @@ -302,8 +302,6 @@ static void *rr_cpu_thread_fn(void *arg) rr_deal_with_unplugged_cpus(); } - rcu_unregister_thread(); - g_assert_not_reached(); } diff --git a/accel/tcg/tcg-accel-ops.c b/accel/tcg/tcg-accel-ops.c index 279dbfa..3b0d7d2 100644 --- a/accel/tcg/tcg-accel-ops.c +++ b/accel/tcg/tcg-accel-ops.c @@ -26,7 +26,8 @@ */ #include "qemu/osdep.h" -#include "system/accel-ops.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/tcg.h" #include "system/replay.h" #include "exec/icount.h" diff --git a/accel/tcg/tcg-all.c b/accel/tcg/tcg-all.c index 5904582..5125e1a 100644 --- a/accel/tcg/tcg-all.c +++ b/accel/tcg/tcg-all.c @@ -39,6 +39,8 @@ #ifndef CONFIG_USER_ONLY #include "hw/boards.h" #endif +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "accel/tcg/cpu-ops.h" #include "internal-common.h" @@ -241,6 +243,7 @@ static void tcg_accel_class_init(ObjectClass *oc, const void *data) ac->init_machine = tcg_init_machine; ac->cpu_common_realize = tcg_exec_realizefn; ac->cpu_common_unrealize = tcg_exec_unrealizefn; + ac->get_stats = tcg_get_stats; ac->allowed = &tcg_allowed; ac->gdbstub_supported_sstep_flags = tcg_gdbstub_supported_sstep_flags; diff --git a/accel/tcg/tcg-stats.c b/accel/tcg/tcg-stats.c new file mode 100644 index 0000000..ced5dec --- /dev/null +++ b/accel/tcg/tcg-stats.c @@ -0,0 +1,219 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * QEMU TCG statistics + * + * Copyright (c) 2003-2005 Fabrice Bellard + */ + +#include "qemu/osdep.h" +#include "qemu/accel.h" +#include "qemu/qht.h" +#include "qapi/error.h" +#include "system/cpu-timers.h" +#include "exec/icount.h" +#include "hw/core/cpu.h" +#include "tcg/tcg.h" +#include "internal-common.h" +#include "tb-context.h" +#include <math.h> + +static void dump_drift_info(GString *buf) +{ + if (!icount_enabled()) { + return; + } + + g_string_append_printf(buf, "Host - Guest clock %"PRIi64" ms\n", + (cpu_get_clock() - icount_get()) / SCALE_MS); + if (icount_align_option) { + g_string_append_printf(buf, "Max guest delay %"PRIi64" ms\n", + -max_delay / SCALE_MS); + g_string_append_printf(buf, "Max guest advance %"PRIi64" ms\n", + max_advance / SCALE_MS); + } else { + g_string_append_printf(buf, "Max guest delay NA\n"); + g_string_append_printf(buf, "Max guest advance NA\n"); + } +} + +static void dump_accel_info(AccelState *accel, GString *buf) +{ + bool one_insn_per_tb = object_property_get_bool(OBJECT(accel), + "one-insn-per-tb", + &error_fatal); + + g_string_append_printf(buf, "Accelerator settings:\n"); + g_string_append_printf(buf, "one-insn-per-tb: %s\n\n", + one_insn_per_tb ? "on" : "off"); +} + +static void print_qht_statistics(struct qht_stats hst, GString *buf) +{ + uint32_t hgram_opts; + size_t hgram_bins; + char *hgram; + double avg; + + if (!hst.head_buckets) { + return; + } + g_string_append_printf(buf, "TB hash buckets %zu/%zu " + "(%0.2f%% head buckets used)\n", + hst.used_head_buckets, hst.head_buckets, + (double)hst.used_head_buckets / + hst.head_buckets * 100); + + hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS; + hgram_opts |= QDIST_PR_100X | QDIST_PR_PERCENT; + if (qdist_xmax(&hst.occupancy) - qdist_xmin(&hst.occupancy) == 1) { + hgram_opts |= QDIST_PR_NODECIMAL; + } + hgram = qdist_pr(&hst.occupancy, 10, hgram_opts); + avg = qdist_avg(&hst.occupancy); + if (!isnan(avg)) { + g_string_append_printf(buf, "TB hash occupancy " + "%0.2f%% avg chain occ. " + "Histogram: %s\n", + avg * 100, hgram); + } + g_free(hgram); + + hgram_opts = QDIST_PR_BORDER | QDIST_PR_LABELS; + hgram_bins = qdist_xmax(&hst.chain) - qdist_xmin(&hst.chain); + if (hgram_bins > 10) { + hgram_bins = 10; + } else { + hgram_bins = 0; + hgram_opts |= QDIST_PR_NODECIMAL | QDIST_PR_NOBINRANGE; + } + hgram = qdist_pr(&hst.chain, hgram_bins, hgram_opts); + avg = qdist_avg(&hst.chain); + if (!isnan(avg)) { + g_string_append_printf(buf, "TB hash avg chain %0.3f buckets. " + "Histogram: %s\n", + avg, hgram); + } + g_free(hgram); +} + +struct tb_tree_stats { + size_t nb_tbs; + size_t host_size; + size_t target_size; + size_t max_target_size; + size_t direct_jmp_count; + size_t direct_jmp2_count; + size_t cross_page; +}; + +static gboolean tb_tree_stats_iter(gpointer key, gpointer value, gpointer data) +{ + const TranslationBlock *tb = value; + struct tb_tree_stats *tst = data; + + tst->nb_tbs++; + tst->host_size += tb->tc.size; + tst->target_size += tb->size; + if (tb->size > tst->max_target_size) { + tst->max_target_size = tb->size; + } +#ifndef CONFIG_USER_ONLY + if (tb->page_addr[1] != -1) { + tst->cross_page++; + } +#endif + if (tb->jmp_reset_offset[0] != TB_JMP_OFFSET_INVALID) { + tst->direct_jmp_count++; + if (tb->jmp_reset_offset[1] != TB_JMP_OFFSET_INVALID) { + tst->direct_jmp2_count++; + } + } + return false; +} + +static void tlb_flush_counts(size_t *pfull, size_t *ppart, size_t *pelide) +{ + CPUState *cpu; + size_t full = 0, part = 0, elide = 0; + + CPU_FOREACH(cpu) { + full += qatomic_read(&cpu->neg.tlb.c.full_flush_count); + part += qatomic_read(&cpu->neg.tlb.c.part_flush_count); + elide += qatomic_read(&cpu->neg.tlb.c.elide_flush_count); + } + *pfull = full; + *ppart = part; + *pelide = elide; +} + +static void tcg_dump_flush_info(GString *buf) +{ + size_t flush_full, flush_part, flush_elide; + + g_string_append_printf(buf, "TB flush count %u\n", + qatomic_read(&tb_ctx.tb_flush_count)); + g_string_append_printf(buf, "TB invalidate count %u\n", + qatomic_read(&tb_ctx.tb_phys_invalidate_count)); + + tlb_flush_counts(&flush_full, &flush_part, &flush_elide); + g_string_append_printf(buf, "TLB full flushes %zu\n", flush_full); + g_string_append_printf(buf, "TLB partial flushes %zu\n", flush_part); + g_string_append_printf(buf, "TLB elided flushes %zu\n", flush_elide); +} + +static void dump_exec_info(GString *buf) +{ + struct tb_tree_stats tst = {}; + struct qht_stats hst; + size_t nb_tbs; + + tcg_tb_foreach(tb_tree_stats_iter, &tst); + nb_tbs = tst.nb_tbs; + /* XXX: avoid using doubles ? */ + g_string_append_printf(buf, "Translation buffer state:\n"); + /* + * Report total code size including the padding and TB structs; + * otherwise users might think "-accel tcg,tb-size" is not honoured. + * For avg host size we use the precise numbers from tb_tree_stats though. + */ + g_string_append_printf(buf, "gen code size %zu/%zu\n", + tcg_code_size(), tcg_code_capacity()); + g_string_append_printf(buf, "TB count %zu\n", nb_tbs); + g_string_append_printf(buf, "TB avg target size %zu max=%zu bytes\n", + nb_tbs ? tst.target_size / nb_tbs : 0, + tst.max_target_size); + g_string_append_printf(buf, "TB avg host size %zu bytes " + "(expansion ratio: %0.1f)\n", + nb_tbs ? tst.host_size / nb_tbs : 0, + tst.target_size ? + (double)tst.host_size / tst.target_size : 0); + g_string_append_printf(buf, "cross page TB count %zu (%zu%%)\n", + tst.cross_page, + nb_tbs ? (tst.cross_page * 100) / nb_tbs : 0); + g_string_append_printf(buf, "direct jump count %zu (%zu%%) " + "(2 jumps=%zu %zu%%)\n", + tst.direct_jmp_count, + nb_tbs ? (tst.direct_jmp_count * 100) / nb_tbs : 0, + tst.direct_jmp2_count, + nb_tbs ? (tst.direct_jmp2_count * 100) / nb_tbs : 0); + + qht_statistics_init(&tb_ctx.htable, &hst); + print_qht_statistics(hst, buf); + qht_statistics_destroy(&hst); + + g_string_append_printf(buf, "\nStatistics:\n"); + tcg_dump_flush_info(buf); +} + +void tcg_get_stats(AccelState *accel, GString *buf) +{ + dump_accel_info(accel, buf); + dump_exec_info(buf); + dump_drift_info(buf); +} + +void tcg_dump_stats(GString *buf) +{ + tcg_get_stats(current_accel(), buf); +} diff --git a/accel/xen/xen-all.c b/accel/xen/xen-all.c index bd0ff64..97377d6 100644 --- a/accel/xen/xen-all.c +++ b/accel/xen/xen-all.c @@ -19,7 +19,8 @@ #include "chardev/char.h" #include "qemu/accel.h" #include "accel/dummy-cpus.h" -#include "system/accel-ops.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "system/xen.h" #include "system/runstate.h" diff --git a/backends/confidential-guest-support.c b/backends/confidential-guest-support.c index 8ff7bfa..156dd15 100644 --- a/backends/confidential-guest-support.c +++ b/backends/confidential-guest-support.c @@ -14,15 +14,58 @@ #include "qemu/osdep.h" #include "system/confidential-guest-support.h" +#include "qapi/error.h" OBJECT_DEFINE_ABSTRACT_TYPE(ConfidentialGuestSupport, confidential_guest_support, CONFIDENTIAL_GUEST_SUPPORT, OBJECT) +static bool check_support(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary) +{ + /* Default: no support. */ + return false; +} + +static int set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp) +{ + error_setg(errp, + "Setting confidential guest state is not supported for this platform"); + return -1; +} + +static int set_guest_policy(ConfidentialGuestPolicyType policy_type, + uint64_t policy, + void *policy_data1, uint32_t policy_data1_size, + void *policy_data2, uint32_t policy_data2_size, + Error **errp) +{ + error_setg(errp, + "Setting confidential guest policy is not supported for this platform"); + return -1; +} + +static int get_mem_map_entry(int index, ConfidentialGuestMemoryMapEntry *entry, + Error **errp) +{ + error_setg( + errp, + "Obtaining the confidential guest memory map is not supported for this platform"); + return -1; +} + static void confidential_guest_support_class_init(ObjectClass *oc, const void *data) { + ConfidentialGuestSupportClass *cgsc = CONFIDENTIAL_GUEST_SUPPORT_CLASS(oc); + cgsc->check_support = check_support; + cgsc->set_guest_state = set_guest_state; + cgsc->set_guest_policy = set_guest_policy; + cgsc->get_mem_map_entry = get_mem_map_entry; } static void confidential_guest_support_init(Object *obj) diff --git a/backends/igvm-cfg.c b/backends/igvm-cfg.c new file mode 100644 index 0000000..45df63e --- /dev/null +++ b/backends/igvm-cfg.c @@ -0,0 +1,51 @@ +/* + * QEMU IGVM interface + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins <roy.hopkins@randomman.co.uk> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "system/igvm-cfg.h" +#include "igvm.h" +#include "qom/object_interfaces.h" + +static char *get_igvm(Object *obj, Error **errp) +{ + IgvmCfg *igvm = IGVM_CFG(obj); + return g_strdup(igvm->filename); +} + +static void set_igvm(Object *obj, const char *value, Error **errp) +{ + IgvmCfg *igvm = IGVM_CFG(obj); + g_free(igvm->filename); + igvm->filename = g_strdup(value); +} + +OBJECT_DEFINE_TYPE_WITH_INTERFACES(IgvmCfg, igvm_cfg, IGVM_CFG, OBJECT, + { TYPE_USER_CREATABLE }, { NULL }) + +static void igvm_cfg_class_init(ObjectClass *oc, const void *data) +{ + IgvmCfgClass *igvmc = IGVM_CFG_CLASS(oc); + + object_class_property_add_str(oc, "file", get_igvm, set_igvm); + object_class_property_set_description(oc, "file", + "Set the IGVM filename to use"); + + igvmc->process = qigvm_process_file; +} + +static void igvm_cfg_init(Object *obj) +{ +} + +static void igvm_cfg_finalize(Object *obj) +{ +} diff --git a/backends/igvm.c b/backends/igvm.c new file mode 100644 index 0000000..9ad4158 --- /dev/null +++ b/backends/igvm.c @@ -0,0 +1,988 @@ +/* + * QEMU IGVM configuration backend for guests + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins <roy.hopkins@randomman.co.uk> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" + +#include "igvm.h" +#include "qapi/error.h" +#include "system/memory.h" +#include "system/address-spaces.h" +#include "hw/core/cpu.h" + +#include <igvm/igvm.h> +#include <igvm/igvm_defs.h> + +typedef struct QIgvmParameterData { + QTAILQ_ENTRY(QIgvmParameterData) next; + uint8_t *data; + uint32_t size; + uint32_t index; +} QIgvmParameterData; + +/* + * Some directives are specific to particular confidential computing platforms. + * Define required types for each of those platforms here. + */ + +/* SEV/SEV-ES/SEV-SNP */ + +/* + * These structures are defined in "SEV Secure Nested Paging Firmware ABI + * Specification" Rev 1.58, section 8.18. + */ +struct QEMU_PACKED sev_id_block { + uint8_t ld[48]; + uint8_t family_id[16]; + uint8_t image_id[16]; + uint32_t version; + uint32_t guest_svn; + uint64_t policy; +}; + +struct QEMU_PACKED sev_id_authentication { + uint32_t id_key_alg; + uint32_t auth_key_algo; + uint8_t reserved[56]; + uint8_t id_block_sig[512]; + uint8_t id_key[1028]; + uint8_t reserved2[60]; + uint8_t id_key_sig[512]; + uint8_t author_key[1028]; + uint8_t reserved3[892]; +}; + +#define IGVM_SEV_ID_BLOCK_VERSION 1 + +/* + * QIgvm contains the information required during processing + * of a single IGVM file. + */ +typedef struct QIgvm { + IgvmHandle file; + ConfidentialGuestSupport *cgs; + ConfidentialGuestSupportClass *cgsc; + uint32_t compatibility_mask; + unsigned current_header_index; + QTAILQ_HEAD(, QIgvmParameterData) parameter_data; + IgvmPlatformType platform_type; + + /* + * SEV-SNP platforms can contain an ID block and authentication + * that should be verified by the guest. + */ + struct sev_id_block *id_block; + struct sev_id_authentication *id_auth; + + /* Define the guest policy for SEV guests */ + uint64_t sev_policy; + + /* These variables keep track of contiguous page regions */ + IGVM_VHS_PAGE_DATA region_prev_page_data; + uint64_t region_start; + unsigned region_start_index; + unsigned region_last_index; + unsigned region_page_count; +} QIgvm; + +static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_parameter_area(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_parameter_insert(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_directive_environment_info(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_required_memory(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); +static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, + Error **errp); +static int qigvm_initialization_guest_policy(QIgvm *ctx, + const uint8_t *header_data, + Error **errp); + +struct QIGVMHandler { + uint32_t type; + uint32_t section; + int (*handler)(QIgvm *ctx, const uint8_t *header_data, Error **errp); +}; + +static struct QIGVMHandler handlers[] = { + { IGVM_VHT_PAGE_DATA, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_page_data }, + { IGVM_VHT_VP_CONTEXT, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_vp_context }, + { IGVM_VHT_PARAMETER_AREA, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_parameter_area }, + { IGVM_VHT_PARAMETER_INSERT, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_parameter_insert }, + { IGVM_VHT_MEMORY_MAP, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_memory_map }, + { IGVM_VHT_VP_COUNT_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_vp_count }, + { IGVM_VHT_ENVIRONMENT_INFO_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_environment_info }, + { IGVM_VHT_REQUIRED_MEMORY, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_required_memory }, + { IGVM_VHT_SNP_ID_BLOCK, IGVM_HEADER_SECTION_DIRECTIVE, + qigvm_directive_snp_id_block }, + { IGVM_VHT_GUEST_POLICY, IGVM_HEADER_SECTION_INITIALIZATION, + qigvm_initialization_guest_policy }, +}; + +static int qigvm_handler(QIgvm *ctx, uint32_t type, Error **errp) +{ + size_t handler; + IgvmHandle header_handle; + const uint8_t *header_data; + int result; + + for (handler = 0; handler < G_N_ELEMENTS(handlers); handler++) { + if (handlers[handler].type != type) { + continue; + } + header_handle = igvm_get_header(ctx->file, handlers[handler].section, + ctx->current_header_index); + if (header_handle < 0) { + error_setg( + errp, + "IGVM file is invalid: Failed to read directive header (code: %d)", + (int)header_handle); + return -1; + } + header_data = igvm_get_buffer(ctx->file, header_handle) + + sizeof(IGVM_VHS_VARIABLE_HEADER); + result = handlers[handler].handler(ctx, header_data, errp); + igvm_free_buffer(ctx->file, header_handle); + return result; + } + error_setg(errp, + "IGVM: Unknown header type encountered when processing file: " + "(type 0x%X)", + type); + return -1; +} + +static void *qigvm_prepare_memory(QIgvm *ctx, uint64_t addr, uint64_t size, + int region_identifier, Error **errp) +{ + ERRP_GUARD(); + MemoryRegion *igvm_pages = NULL; + Int128 gpa_region_size; + MemoryRegionSection mrs = + memory_region_find(get_system_memory(), addr, size); + if (mrs.mr) { + if (!memory_region_is_ram(mrs.mr)) { + memory_region_unref(mrs.mr); + error_setg( + errp, + "Processing of IGVM file failed: Could not prepare memory " + "at address 0x%lX due to existing non-RAM region", + addr); + return NULL; + } + + gpa_region_size = int128_make64(size); + if (int128_lt(mrs.size, gpa_region_size)) { + memory_region_unref(mrs.mr); + error_setg( + errp, + "Processing of IGVM file failed: Could not prepare memory " + "at address 0x%lX: region size exceeded", + addr); + return NULL; + } + return qemu_map_ram_ptr(mrs.mr->ram_block, mrs.offset_within_region); + } else { + /* + * The region_identifier is the is the index of the IGVM directive that + * contains the page with the lowest GPA in the region. This will + * generate a unique region name. + */ + g_autofree char *region_name = + g_strdup_printf("igvm.%X", region_identifier); + igvm_pages = g_new0(MemoryRegion, 1); + if (ctx->cgs && ctx->cgs->require_guest_memfd) { + if (!memory_region_init_ram_guest_memfd(igvm_pages, NULL, + region_name, size, errp)) { + return NULL; + } + } else { + if (!memory_region_init_ram(igvm_pages, NULL, region_name, size, + errp)) { + return NULL; + } + } + memory_region_add_subregion(get_system_memory(), addr, igvm_pages); + return memory_region_get_ram_ptr(igvm_pages); + } +} + +static int qigvm_type_to_cgs_type(IgvmPageDataType memory_type, bool unmeasured, + bool zero) +{ + switch (memory_type) { + case IGVM_PAGE_DATA_TYPE_NORMAL: { + if (unmeasured) { + return CGS_PAGE_TYPE_UNMEASURED; + } else { + return zero ? CGS_PAGE_TYPE_ZERO : CGS_PAGE_TYPE_NORMAL; + } + } + case IGVM_PAGE_DATA_TYPE_SECRETS: + return CGS_PAGE_TYPE_SECRETS; + case IGVM_PAGE_DATA_TYPE_CPUID_DATA: + return CGS_PAGE_TYPE_CPUID; + case IGVM_PAGE_DATA_TYPE_CPUID_XF: + return CGS_PAGE_TYPE_CPUID; + default: + return -1; + } +} + +static bool qigvm_page_attrs_equal(IgvmHandle igvm, unsigned header_index, + const IGVM_VHS_PAGE_DATA *page_1, + const IGVM_VHS_PAGE_DATA *page_2) +{ + IgvmHandle data_handle1, data_handle2; + + /* + * If one page has data and the other doesn't then this results in different + * page types: NORMAL vs ZERO. + */ + data_handle1 = igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, + header_index - 1); + data_handle2 = + igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, header_index); + if ((data_handle1 == IGVMAPI_NO_DATA || + data_handle2 == IGVMAPI_NO_DATA) && + data_handle1 != data_handle2) { + return false; + } + return ((*(const uint32_t *)&page_1->flags == + *(const uint32_t *)&page_2->flags) && + (page_1->data_type == page_2->data_type) && + (page_1->compatibility_mask == page_2->compatibility_mask)); +} + +static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index, + uint64_t gpa_start, unsigned page_count, + const IgvmPageDataFlags *flags, + const IgvmPageDataType page_type, + Error **errp) +{ + uint8_t *region; + IgvmHandle data_handle; + const void *data; + uint32_t data_size; + unsigned page_index; + bool zero = true; + const uint64_t page_size = flags->is_2mb_page ? 0x200000 : 0x1000; + int result; + int cgs_page_type; + + region = qigvm_prepare_memory(ctx, gpa_start, page_count * page_size, + start_index, errp); + if (!region) { + return -1; + } + + for (page_index = 0; page_index < page_count; page_index++) { + data_handle = igvm_get_header_data( + ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, page_index + start_index); + if (data_handle == IGVMAPI_NO_DATA) { + /* No data indicates a zero page */ + memset(®ion[page_index * page_size], 0, page_size); + } else if (data_handle < 0) { + error_setg( + errp, + "IGVM file contains invalid page data for directive with " + "index %d", + page_index + start_index); + return -1; + } else { + zero = false; + data_size = igvm_get_buffer_size(ctx->file, data_handle); + if (data_size < page_size) { + memset(®ion[page_index * page_size], 0, page_size); + } else if (data_size > page_size) { + error_setg(errp, + "IGVM file contains page data with invalid size for " + "directive with index %d", + page_index + start_index); + return -1; + } + data = igvm_get_buffer(ctx->file, data_handle); + memcpy(®ion[page_index * page_size], data, data_size); + igvm_free_buffer(ctx->file, data_handle); + } + } + + /* + * If a confidential guest support object is provided then use it to set the + * guest state. + */ + if (ctx->cgs) { + cgs_page_type = + qigvm_type_to_cgs_type(page_type, flags->unmeasured, zero); + if (cgs_page_type < 0) { + error_setg(errp, + "Invalid page type in IGVM file. Directives: %d to %d, " + "page type: %d", + start_index, start_index + page_count, page_type); + return -1; + } + + result = ctx->cgsc->set_guest_state( + gpa_start, region, page_size * page_count, cgs_page_type, 0, errp); + if (result < 0) { + return result; + } + } + return 0; +} + +static int qigvm_process_mem_page(QIgvm *ctx, + const IGVM_VHS_PAGE_DATA *page_data, + Error **errp) +{ + if (page_data) { + if (ctx->region_page_count == 0) { + ctx->region_start = page_data->gpa; + ctx->region_start_index = ctx->current_header_index; + } else { + if (!qigvm_page_attrs_equal(ctx->file, ctx->current_header_index, + page_data, + &ctx->region_prev_page_data) || + ((ctx->region_prev_page_data.gpa + + (ctx->region_prev_page_data.flags.is_2mb_page ? 0x200000 : + 0x1000)) != + page_data->gpa) || + (ctx->region_last_index != (ctx->current_header_index - 1))) { + /* End of current region */ + if (qigvm_process_mem_region( + ctx, ctx->region_start_index, ctx->region_start, + ctx->region_page_count, + &ctx->region_prev_page_data.flags, + ctx->region_prev_page_data.data_type, errp) < 0) { + return -1; + } + ctx->region_page_count = 0; + ctx->region_start = page_data->gpa; + ctx->region_start_index = ctx->current_header_index; + } + } + memcpy(&ctx->region_prev_page_data, page_data, + sizeof(ctx->region_prev_page_data)); + ctx->region_last_index = ctx->current_header_index; + ctx->region_page_count++; + } else { + if (ctx->region_page_count > 0) { + if (qigvm_process_mem_region( + ctx, ctx->region_start_index, ctx->region_start, + ctx->region_page_count, &ctx->region_prev_page_data.flags, + ctx->region_prev_page_data.data_type, errp) < 0) { + return -1; + } + ctx->region_page_count = 0; + } + } + return 0; +} + +static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PAGE_DATA *page_data = + (const IGVM_VHS_PAGE_DATA *)header_data; + if (page_data->compatibility_mask & ctx->compatibility_mask) { + return qigvm_process_mem_page(ctx, page_data, errp); + } + return 0; +} + +static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_VP_CONTEXT *vp_context = + (const IGVM_VHS_VP_CONTEXT *)header_data; + IgvmHandle data_handle; + uint8_t *data; + int result; + + if (!(vp_context->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + /* + * A confidential guest support object must be provided for setting + * a VP context. + */ + if (!ctx->cgs) { + error_setg( + errp, + "A VP context is present in the IGVM file but is not supported " + "by the current system."); + return -1; + } + + data_handle = igvm_get_header_data(ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, + ctx->current_header_index); + if (data_handle < 0) { + error_setg(errp, "Invalid VP context in IGVM file. Error code: %X", + data_handle); + return -1; + } + + data = (uint8_t *)igvm_get_buffer(ctx->file, data_handle); + result = ctx->cgsc->set_guest_state( + vp_context->gpa, data, igvm_get_buffer_size(ctx->file, data_handle), + CGS_PAGE_TYPE_VMSA, vp_context->vp_index, errp); + igvm_free_buffer(ctx->file, data_handle); + if (result < 0) { + return result; + } + return 0; +} + +static int qigvm_directive_parameter_area(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER_AREA *param_area = + (const IGVM_VHS_PARAMETER_AREA *)header_data; + QIgvmParameterData *param_entry; + + param_entry = g_new0(QIgvmParameterData, 1); + param_entry->size = param_area->number_of_bytes; + param_entry->index = param_area->parameter_area_index; + param_entry->data = g_malloc0(param_entry->size); + + QTAILQ_INSERT_TAIL(&ctx->parameter_data, param_entry, next); + return 0; +} + +static int qigvm_directive_parameter_insert(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER_INSERT *param = + (const IGVM_VHS_PARAMETER_INSERT *)header_data; + QIgvmParameterData *param_entry; + int result; + void *region; + + if (!(param->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + region = qigvm_prepare_memory(ctx, param->gpa, param_entry->size, + ctx->current_header_index, errp); + if (!region) { + return -1; + } + memcpy(region, param_entry->data, param_entry->size); + g_free(param_entry->data); + param_entry->data = NULL; + + /* + * If a confidential guest support object is provided then use it to + * set the guest state. + */ + if (ctx->cgs) { + result = ctx->cgsc->set_guest_state(param->gpa, region, + param_entry->size, + CGS_PAGE_TYPE_UNMEASURED, 0, + errp); + if (result < 0) { + return -1; + } + } + } + } + return 0; +} + +static int qigvm_cmp_mm_entry(const void *a, const void *b) +{ + const IGVM_VHS_MEMORY_MAP_ENTRY *entry_a = + (const IGVM_VHS_MEMORY_MAP_ENTRY *)a; + const IGVM_VHS_MEMORY_MAP_ENTRY *entry_b = + (const IGVM_VHS_MEMORY_MAP_ENTRY *)b; + if (entry_a->starting_gpa_page_number < entry_b->starting_gpa_page_number) { + return -1; + } else if (entry_a->starting_gpa_page_number > + entry_b->starting_gpa_page_number) { + return 1; + } else { + return 0; + } +} + +static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + int max_entry_count; + int entry = 0; + IGVM_VHS_MEMORY_MAP_ENTRY *mm_entry; + ConfidentialGuestMemoryMapEntry cgmm_entry; + int retval = 0; + + if (!ctx->cgs) { + error_setg(errp, + "IGVM file contains a memory map but this is not supported " + "by the current system."); + return -1; + } + + /* Find the parameter area that should hold the memory map */ + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + max_entry_count = + param_entry->size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY); + mm_entry = (IGVM_VHS_MEMORY_MAP_ENTRY *)param_entry->data; + + retval = ctx->cgsc->get_mem_map_entry(entry, &cgmm_entry, errp); + while (retval == 0) { + if (entry > max_entry_count) { + error_setg( + errp, + "IGVM: guest memory map size exceeds parameter area defined in IGVM file"); + return -1; + } + mm_entry[entry].starting_gpa_page_number = cgmm_entry.gpa >> 12; + mm_entry[entry].number_of_pages = cgmm_entry.size >> 12; + + switch (cgmm_entry.type) { + case CGS_MEM_RAM: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_MEMORY; + break; + case CGS_MEM_RESERVED: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + case CGS_MEM_ACPI: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + case CGS_MEM_NVS: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PERSISTENT; + break; + case CGS_MEM_UNUSABLE: + mm_entry[entry].entry_type = + IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; + break; + } + retval = + ctx->cgsc->get_mem_map_entry(++entry, &cgmm_entry, errp); + } + if (retval < 0) { + return retval; + } + /* The entries need to be sorted */ + qsort(mm_entry, entry, sizeof(IGVM_VHS_MEMORY_MAP_ENTRY), + qigvm_cmp_mm_entry); + + break; + } + } + return 0; +} + +static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + uint32_t *vp_count; + CPUState *cpu; + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + vp_count = (uint32_t *)(param_entry->data + param->byte_offset); + *vp_count = 0; + CPU_FOREACH(cpu) + { + (*vp_count)++; + } + break; + } + } + return 0; +} + +static int qigvm_directive_environment_info(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; + QIgvmParameterData *param_entry; + IgvmEnvironmentInfo *environmental_state; + + QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) + { + if (param_entry->index == param->parameter_area_index) { + environmental_state = + (IgvmEnvironmentInfo *)(param_entry->data + param->byte_offset); + environmental_state->memory_is_shared = 1; + break; + } + } + return 0; +} + +static int qigvm_directive_required_memory(QIgvm *ctx, + const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_REQUIRED_MEMORY *mem = + (const IGVM_VHS_REQUIRED_MEMORY *)header_data; + uint8_t *region; + int result; + + if (!(mem->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + region = qigvm_prepare_memory(ctx, mem->gpa, mem->number_of_bytes, + ctx->current_header_index, errp); + if (!region) { + return -1; + } + if (ctx->cgs) { + result = + ctx->cgsc->set_guest_state(mem->gpa, region, mem->number_of_bytes, + CGS_PAGE_TYPE_REQUIRED_MEMORY, 0, errp); + if (result < 0) { + return result; + } + } + return 0; +} + +static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, + Error **errp) +{ + const IGVM_VHS_SNP_ID_BLOCK *igvm_id = + (const IGVM_VHS_SNP_ID_BLOCK *)header_data; + + if (!(igvm_id->compatibility_mask & ctx->compatibility_mask)) { + return 0; + } + + if (ctx->id_block) { + error_setg(errp, "IGVM: Multiple ID blocks encountered " + "in IGVM file."); + return -1; + } + ctx->id_block = g_new0(struct sev_id_block, 1); + ctx->id_auth = g_new0(struct sev_id_authentication, 1); + + memcpy(ctx->id_block->family_id, igvm_id->family_id, + sizeof(ctx->id_block->family_id)); + memcpy(ctx->id_block->image_id, igvm_id->image_id, + sizeof(ctx->id_block->image_id)); + ctx->id_block->guest_svn = igvm_id->guest_svn; + ctx->id_block->version = IGVM_SEV_ID_BLOCK_VERSION; + memcpy(ctx->id_block->ld, igvm_id->ld, sizeof(ctx->id_block->ld)); + + ctx->id_auth->id_key_alg = igvm_id->id_key_algorithm; + assert(sizeof(igvm_id->id_key_signature) <= + sizeof(ctx->id_auth->id_block_sig)); + memcpy(ctx->id_auth->id_block_sig, &igvm_id->id_key_signature, + sizeof(igvm_id->id_key_signature)); + + ctx->id_auth->auth_key_algo = igvm_id->author_key_algorithm; + assert(sizeof(igvm_id->author_key_signature) <= + sizeof(ctx->id_auth->id_key_sig)); + memcpy(ctx->id_auth->id_key_sig, &igvm_id->author_key_signature, + sizeof(igvm_id->author_key_signature)); + + /* + * SEV and IGVM public key structure population are slightly different. + * See SEV Secure Nested Paging Firmware ABI Specification, Chapter 10. + */ + *((uint32_t *)ctx->id_auth->id_key) = igvm_id->id_public_key.curve; + memcpy(&ctx->id_auth->id_key[4], &igvm_id->id_public_key.qx, 72); + memcpy(&ctx->id_auth->id_key[76], &igvm_id->id_public_key.qy, 72); + + *((uint32_t *)ctx->id_auth->author_key) = + igvm_id->author_public_key.curve; + memcpy(&ctx->id_auth->author_key[4], &igvm_id->author_public_key.qx, + 72); + memcpy(&ctx->id_auth->author_key[76], &igvm_id->author_public_key.qy, + 72); + + return 0; +} + +static int qigvm_initialization_guest_policy(QIgvm *ctx, + const uint8_t *header_data, Error **errp) +{ + const IGVM_VHS_GUEST_POLICY *guest = + (const IGVM_VHS_GUEST_POLICY *)header_data; + + if (guest->compatibility_mask & ctx->compatibility_mask) { + ctx->sev_policy = guest->policy; + } + return 0; +} + +static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) +{ + int32_t header_count; + unsigned header_index; + IgvmHandle header_handle; + IGVM_VHS_SUPPORTED_PLATFORM *platform; + uint32_t compatibility_mask_sev = 0; + uint32_t compatibility_mask_sev_es = 0; + uint32_t compatibility_mask_sev_snp = 0; + uint32_t compatibility_mask = 0; + + header_count = igvm_header_count(ctx->file, IGVM_HEADER_SECTION_PLATFORM); + if (header_count < 0) { + error_setg(errp, + "Invalid platform header count in IGVM file. Error code: %X", + header_count); + return -1; + } + + for (header_index = 0; header_index < (unsigned)header_count; + header_index++) { + IgvmVariableHeaderType typ = igvm_get_header_type( + ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); + if (typ == IGVM_VHT_SUPPORTED_PLATFORM) { + header_handle = igvm_get_header( + ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); + if (header_handle < 0) { + error_setg(errp, + "Invalid platform header in IGVM file. " + "Index: %d, Error code: %X", + header_index, header_handle); + return -1; + } + platform = + (IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(ctx->file, + header_handle) + + sizeof( + IGVM_VHS_VARIABLE_HEADER)); + if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV_ES) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV_ES, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev_es = platform->compatibility_mask; + } + } else if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev = platform->compatibility_mask; + } + } else if ((platform->platform_type == + IGVM_PLATFORM_TYPE_SEV_SNP) && + ctx->cgs) { + if (ctx->cgsc->check_support( + CGS_PLATFORM_SEV_SNP, platform->platform_version, + platform->highest_vtl, platform->shared_gpa_boundary)) { + compatibility_mask_sev_snp = platform->compatibility_mask; + } + } else if (platform->platform_type == IGVM_PLATFORM_TYPE_NATIVE) { + compatibility_mask = platform->compatibility_mask; + } + igvm_free_buffer(ctx->file, header_handle); + } + } + /* Choose the strongest supported isolation technology */ + if (compatibility_mask_sev_snp != 0) { + ctx->compatibility_mask = compatibility_mask_sev_snp; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_SNP; + } else if (compatibility_mask_sev_es != 0) { + ctx->compatibility_mask = compatibility_mask_sev_es; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_ES; + } else if (compatibility_mask_sev != 0) { + ctx->compatibility_mask = compatibility_mask_sev; + ctx->platform_type = IGVM_PLATFORM_TYPE_SEV; + } else if (compatibility_mask != 0) { + ctx->compatibility_mask = compatibility_mask; + ctx->platform_type = IGVM_PLATFORM_TYPE_NATIVE; + } else { + error_setg( + errp, + "IGVM file does not describe a compatible supported platform"); + return -1; + } + return 0; +} + +static int qigvm_handle_policy(QIgvm *ctx, Error **errp) +{ + if (ctx->platform_type == IGVM_PLATFORM_TYPE_SEV_SNP) { + int id_block_len = 0; + int id_auth_len = 0; + if (ctx->id_block) { + ctx->id_block->policy = ctx->sev_policy; + id_block_len = sizeof(struct sev_id_block); + id_auth_len = sizeof(struct sev_id_authentication); + } + return ctx->cgsc->set_guest_policy(GUEST_POLICY_SEV, ctx->sev_policy, + ctx->id_block, id_block_len, + ctx->id_auth, id_auth_len, errp); + } + return 0; +} + +static IgvmHandle qigvm_file_init(char *filename, Error **errp) +{ + IgvmHandle igvm; + g_autofree uint8_t *buf = NULL; + unsigned long len; + g_autoptr(GError) gerr = NULL; + + if (!g_file_get_contents(filename, (gchar **)&buf, &len, &gerr)) { + error_setg(errp, "Unable to load %s: %s", filename, gerr->message); + return -1; + } + + igvm = igvm_new_from_binary(buf, len); + if (igvm < 0) { + error_setg(errp, "Unable to parse IGVM file %s: %d", filename, igvm); + return -1; + } + return igvm; +} + +int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, + bool onlyVpContext, Error **errp) +{ + int32_t header_count; + QIgvmParameterData *parameter; + int retval = -1; + QIgvm ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.file = qigvm_file_init(cfg->filename, errp); + if (ctx.file < 0) { + return -1; + } + + /* + * The ConfidentialGuestSupport object is optional and allows a confidential + * guest platform to perform extra processing, such as page measurement, on + * IGVM directives. + */ + ctx.cgs = cgs; + ctx.cgsc = cgs ? CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(cgs) : NULL; + + /* + * Check that the IGVM file provides configuration for the current + * platform + */ + if (qigvm_supported_platform_compat_mask(&ctx, errp) < 0) { + goto cleanup; + } + + header_count = igvm_header_count(ctx.file, IGVM_HEADER_SECTION_DIRECTIVE); + if (header_count <= 0) { + error_setg( + errp, "Invalid directive header count in IGVM file. Error code: %X", + header_count); + goto cleanup; + } + + QTAILQ_INIT(&ctx.parameter_data); + + for (ctx.current_header_index = 0; + ctx.current_header_index < (unsigned)header_count; + ctx.current_header_index++) { + IgvmVariableHeaderType type = igvm_get_header_type( + ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index); + if (!onlyVpContext || (type == IGVM_VHT_VP_CONTEXT)) { + if (qigvm_handler(&ctx, type, errp) < 0) { + goto cleanup_parameters; + } + } + } + + /* + * If only processing the VP context then we don't need to process + * any more of the file. + */ + if (onlyVpContext) { + retval = 0; + goto cleanup_parameters; + } + + header_count = + igvm_header_count(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION); + if (header_count < 0) { + error_setg( + errp, + "Invalid initialization header count in IGVM file. Error code: %X", + header_count); + goto cleanup_parameters; + } + + for (ctx.current_header_index = 0; + ctx.current_header_index < (unsigned)header_count; + ctx.current_header_index++) { + IgvmVariableHeaderType type = + igvm_get_header_type(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION, + ctx.current_header_index); + if (qigvm_handler(&ctx, type, errp) < 0) { + goto cleanup_parameters; + } + } + + /* + * Contiguous pages of data with compatible flags are grouped together in + * order to reduce the number of memory regions we create. Make sure the + * last group is processed with this call. + */ + retval = qigvm_process_mem_page(&ctx, NULL, errp); + + if (retval == 0) { + retval = qigvm_handle_policy(&ctx, errp); + } + +cleanup_parameters: + QTAILQ_FOREACH(parameter, &ctx.parameter_data, next) + { + g_free(parameter->data); + parameter->data = NULL; + } + g_free(ctx.id_block); + g_free(ctx.id_auth); + +cleanup: + igvm_free(ctx.file); + + return retval; +} diff --git a/backends/igvm.h b/backends/igvm.h new file mode 100644 index 0000000..a4abab0 --- /dev/null +++ b/backends/igvm.h @@ -0,0 +1,22 @@ +/* + * QEMU IGVM configuration backend for Confidential Guests + * + * Copyright (C) 2023-2024 SUSE + * + * Authors: + * Roy Hopkins <roy.hopkins@randomman.co.uk> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef BACKENDS_IGVM_H +#define BACKENDS_IGVM_H + +#include "system/confidential-guest-support.h" +#include "system/igvm-cfg.h" +#include "qapi/error.h" + +int qigvm_process_file(IgvmCfg *igvm, ConfidentialGuestSupport *cgs, + bool onlyVpContext, Error **errp); + +#endif diff --git a/backends/meson.build b/backends/meson.build index 9b88d22..60021f4 100644 --- a/backends/meson.build +++ b/backends/meson.build @@ -34,6 +34,11 @@ if have_vhost_user_crypto endif system_ss.add(when: gio, if_true: files('dbus-vmstate.c')) system_ss.add(when: 'CONFIG_SGX', if_true: files('hostmem-epc.c')) +if igvm.found() + system_ss.add(igvm) + system_ss.add(files('igvm-cfg.c'), igvm) + system_ss.add(files('igvm.c'), igvm) +endif system_ss.add(when: 'CONFIG_SPDM_SOCKET', if_true: files('spdm-socket.c')) @@ -431,7 +431,7 @@ BlockDriverState *bdrv_new(void) bs->block_status_cache = g_new0(BdrvBlockStatusCache, 1); for (i = 0; i < bdrv_drain_all_count; i++) { - bdrv_drained_begin(bs); + bdrv_do_drained_begin_quiesce(bs, NULL); } QTAILQ_INSERT_TAIL(&all_bdrv_states, bs, bs_list); @@ -1721,14 +1721,12 @@ bdrv_open_driver(BlockDriverState *bs, BlockDriver *drv, const char *node_name, open_failed: bs->drv = NULL; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); if (bs->file != NULL) { bdrv_unref_child(bs, bs->file); assert(!bs->file); } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_free(bs->opaque); bs->opaque = NULL; @@ -3572,9 +3570,8 @@ out: * * All block nodes must be drained. */ -int bdrv_set_backing_hd_drained(BlockDriverState *bs, - BlockDriverState *backing_hd, - Error **errp) +int bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd, + Error **errp) { int ret; Transaction *tran = tran_new(); @@ -3596,21 +3593,6 @@ out: return ret; } -int bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd, - Error **errp) -{ - int ret; - GLOBAL_STATE_CODE(); - - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); - ret = bdrv_set_backing_hd_drained(bs, backing_hd, errp); - bdrv_graph_wrunlock(); - bdrv_drain_all_end(); - - return ret; -} - /* * Opens the backing file for a BlockDriverState if not yet open * @@ -3636,7 +3618,8 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, Error *local_err = NULL; GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + + bdrv_graph_rdlock_main_loop(); if (bs->backing != NULL) { goto free_exit; @@ -3717,7 +3700,11 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, /* Hook up the backing file link; drop our reference, bs owns the * backing_hd reference now */ + bdrv_graph_rdunlock_main_loop(); + bdrv_graph_wrlock_drained(); ret = bdrv_set_backing_hd(bs, backing_hd, errp); + bdrv_graph_wrunlock(); + bdrv_graph_rdlock_main_loop(); bdrv_unref(backing_hd); if (ret < 0) { @@ -3729,6 +3716,7 @@ int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, free_exit: g_free(backing_filename); qobject_unref(tmp_parent_options); + bdrv_graph_rdunlock_main_loop(); return ret; } @@ -3778,13 +3766,12 @@ done: return bs; } -static BdrvChild *bdrv_open_child_common(const char *filename, - QDict *options, const char *bdref_key, - BlockDriverState *parent, - const BdrvChildClass *child_class, - BdrvChildRole child_role, - bool allow_none, bool parse_filename, - Error **errp) +static BdrvChild * GRAPH_UNLOCKED +bdrv_open_child_common(const char *filename, QDict *options, + const char *bdref_key, BlockDriverState *parent, + const BdrvChildClass *child_class, + BdrvChildRole child_role, bool allow_none, + bool parse_filename, Error **errp) { BlockDriverState *bs; BdrvChild *child; @@ -3797,12 +3784,10 @@ static BdrvChild *bdrv_open_child_common(const char *filename, return NULL; } - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); child = bdrv_attach_child(parent, bs, bdref_key, child_class, child_role, errp); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return child; } @@ -5160,7 +5145,7 @@ static void GRAPH_UNLOCKED bdrv_reopen_abort(BDRVReopenState *reopen_state) } -static void bdrv_close(BlockDriverState *bs) +static void GRAPH_UNLOCKED bdrv_close(BlockDriverState *bs) { BdrvAioNotifier *ban, *ban_next; BdrvChild *child, *next; @@ -5180,8 +5165,7 @@ static void bdrv_close(BlockDriverState *bs) bs->drv = NULL; } - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); QLIST_FOREACH_SAFE(child, &bs->children, next, next) { bdrv_unref_child(bs, child); } @@ -5189,7 +5173,6 @@ static void bdrv_close(BlockDriverState *bs) assert(!bs->backing); assert(!bs->file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_free(bs->opaque); bs->opaque = NULL; @@ -5515,8 +5498,7 @@ int bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top, assert(!bs_new->backing); bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); child = bdrv_attach_child_noperm(bs_new, bs_top, "backing", &child_of_bds, bdrv_backing_role(bs_new), @@ -5537,7 +5519,6 @@ out: bdrv_refresh_limits(bs_top, NULL, NULL); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return ret; } @@ -7076,31 +7057,25 @@ bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) return 0; } +/* All block nodes must be drained. */ int bdrv_inactivate(BlockDriverState *bs, Error **errp) { int ret; GLOBAL_STATE_CODE(); - bdrv_drain_all_begin(); - bdrv_graph_rdlock_main_loop(); - if (bdrv_has_bds_parent(bs, true)) { error_setg(errp, "Node has active parent node"); - ret = -EPERM; - goto out; + return -EPERM; } ret = bdrv_inactivate_recurse(bs, true); if (ret < 0) { error_setg_errno(errp, -ret, "Failed to inactivate node"); - goto out; + return ret; } -out: - bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_end(); - return ret; + return 0; } int bdrv_inactivate_all(void) diff --git a/block/backup.c b/block/backup.c index 909027c..d4713fa 100644 --- a/block/backup.c +++ b/block/backup.c @@ -498,12 +498,10 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs, block_copy_set_speed(bcs, speed); /* Required permissions are taken by copy-before-write filter target */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return &job->common; diff --git a/block/blklogwrites.c b/block/blklogwrites.c index 70ac76f..aa1f888 100644 --- a/block/blklogwrites.c +++ b/block/blklogwrites.c @@ -281,11 +281,9 @@ static int blk_log_writes_open(BlockDriverState *bs, QDict *options, int flags, ret = 0; fail_log: if (ret < 0) { - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, s->log_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); s->log_file = NULL; qemu_mutex_destroy(&s->mutex); } @@ -298,12 +296,10 @@ static void blk_log_writes_close(BlockDriverState *bs) { BDRVBlkLogWritesState *s = bs->opaque; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, s->log_file); s->log_file = NULL; bdrv_graph_wrunlock(); - bdrv_drain_all_end(); qemu_mutex_destroy(&s->mutex); } diff --git a/block/blkverify.c b/block/blkverify.c index 3a71f74..72efcbe 100644 --- a/block/blkverify.c +++ b/block/blkverify.c @@ -151,12 +151,10 @@ static void blkverify_close(BlockDriverState *bs) { BDRVBlkverifyState *s = bs->opaque; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, s->test_file); s->test_file = NULL; bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } static int64_t coroutine_fn GRAPH_RDLOCK diff --git a/block/block-backend.c b/block/block-backend.c index 68209bb..f8d6ba6 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -889,11 +889,9 @@ void blk_remove_bs(BlockBackend *blk) root = blk->root; blk->root = NULL; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_root_unref_child(root); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } /* @@ -906,8 +904,7 @@ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) GLOBAL_STATE_CODE(); bdrv_ref(bs); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); if ((bs->open_flags & BDRV_O_INACTIVE) && blk_can_inactivate(blk)) { blk->disable_perm = true; @@ -922,7 +919,6 @@ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, perm, shared_perm, blk, errp); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); if (blk->root == NULL) { return -EPERM; } diff --git a/block/commit.c b/block/commit.c index 6c4b736..0d9e1a1 100644 --- a/block/commit.c +++ b/block/commit.c @@ -68,7 +68,7 @@ static int commit_prepare(Job *job) s->backing_mask_protocol); } -static void commit_abort(Job *job) +static void GRAPH_UNLOCKED commit_abort(Job *job) { CommitBlockJob *s = container_of(job, CommitBlockJob, common.job); BlockDriverState *top_bs = blk_bs(s->top); @@ -392,8 +392,7 @@ void commit_start(const char *job_id, BlockDriverState *bs, * this is the responsibility of the interface (i.e. whoever calls * commit_start()). */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); s->base_overlay = bdrv_find_overlay(top, base); assert(s->base_overlay); @@ -425,21 +424,18 @@ void commit_start(const char *job_id, BlockDriverState *bs, iter_shared_perms, errp); if (ret < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } } if (bdrv_freeze_backing_chain(commit_top_bs, base, errp) < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } s->chain_frozen = true; ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); if (ret < 0) { goto fail; @@ -518,28 +514,32 @@ int bdrv_commit(BlockDriverState *bs) Error *local_err = NULL; GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); if (!drv) return -ENOMEDIUM; + bdrv_graph_rdlock_main_loop(); + backing_file_bs = bdrv_cow_bs(bs); if (!backing_file_bs) { - return -ENOTSUP; + ret = -ENOTSUP; + goto out; } if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, NULL) || bdrv_op_is_blocked(backing_file_bs, BLOCK_OP_TYPE_COMMIT_TARGET, NULL)) { - return -EBUSY; + ret = -EBUSY; + goto out; } ro = bdrv_is_read_only(backing_file_bs); if (ro) { if (bdrv_reopen_set_read_only(backing_file_bs, false, NULL)) { - return -EACCES; + ret = -EACCES; + goto out; } } @@ -563,8 +563,14 @@ int bdrv_commit(BlockDriverState *bs) goto ro_cleanup; } + bdrv_graph_rdunlock_main_loop(); + + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort); bdrv_set_backing_hd(bs, commit_top_bs, &error_abort); + bdrv_graph_wrunlock(); + + bdrv_graph_rdlock_main_loop(); ret = blk_insert_bs(backing, backing_file_bs, &local_err); if (ret < 0) { @@ -639,9 +645,14 @@ int bdrv_commit(BlockDriverState *bs) ret = 0; ro_cleanup: blk_unref(backing); + + bdrv_graph_rdunlock_main_loop(); + bdrv_graph_wrlock_drained(); if (bdrv_cow_bs(bs) != backing_file_bs) { bdrv_set_backing_hd(bs, backing_file_bs, &error_abort); } + bdrv_graph_wrunlock(); + bdrv_graph_rdlock_main_loop(); bdrv_unref(commit_top_bs); blk_unref(src); @@ -650,5 +661,8 @@ ro_cleanup: bdrv_reopen_set_read_only(backing_file_bs, true, NULL); } +out: + bdrv_graph_rdunlock_main_loop(); + return ret; } diff --git a/block/file-posix.c b/block/file-posix.c index 9b5f08c..8c73867 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -2564,9 +2564,9 @@ static inline bool raw_check_linux_aio(BDRVRawState *s) } #endif -static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, - uint64_t bytes, QEMUIOVector *qiov, int type, - int flags) +static int coroutine_fn GRAPH_RDLOCK +raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, uint64_t bytes, + QEMUIOVector *qiov, int type, int flags) { BDRVRawState *s = bs->opaque; RawPosixAIOData acb; @@ -2625,7 +2625,7 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, int64_t *offset_ptr, ret = raw_thread_pool_submit(handle_aiocb_rw, &acb); if (ret == 0 && (flags & BDRV_REQ_FUA)) { /* TODO Use pwritev2() instead if it's available */ - ret = raw_co_flush_to_disk(bs); + ret = bdrv_co_flush(bs); } goto out; /* Avoid the compiler err of unused label */ @@ -2660,16 +2660,16 @@ out: return ret; } -static int coroutine_fn raw_co_preadv(BlockDriverState *bs, int64_t offset, - int64_t bytes, QEMUIOVector *qiov, - BdrvRequestFlags flags) +static int coroutine_fn GRAPH_RDLOCK +raw_co_preadv(BlockDriverState *bs, int64_t offset, int64_t bytes, + QEMUIOVector *qiov, BdrvRequestFlags flags) { return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_READ, flags); } -static int coroutine_fn raw_co_pwritev(BlockDriverState *bs, int64_t offset, - int64_t bytes, QEMUIOVector *qiov, - BdrvRequestFlags flags) +static int coroutine_fn GRAPH_RDLOCK +raw_co_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes, + QEMUIOVector *qiov, BdrvRequestFlags flags) { return raw_co_prw(bs, &offset, bytes, qiov, QEMU_AIO_WRITE, flags); } @@ -3606,10 +3606,11 @@ static int coroutine_fn raw_co_zone_mgmt(BlockDriverState *bs, BlockZoneOp op, #endif #if defined(CONFIG_BLKZONED) -static int coroutine_fn raw_co_zone_append(BlockDriverState *bs, - int64_t *offset, - QEMUIOVector *qiov, - BdrvRequestFlags flags) { +static int coroutine_fn GRAPH_RDLOCK +raw_co_zone_append(BlockDriverState *bs, + int64_t *offset, + QEMUIOVector *qiov, + BdrvRequestFlags flags) { assert(flags == 0); int64_t zone_size_mask = bs->bl.zone_size - 1; int64_t iov_len = 0; diff --git a/block/graph-lock.c b/block/graph-lock.c index c81162b..b731947 100644 --- a/block/graph-lock.c +++ b/block/graph-lock.c @@ -34,6 +34,17 @@ static QemuMutex aio_context_list_lock; static int has_writer; /* + * Many write-locked sections are also drained sections. There is a convenience + * wrapper bdrv_graph_wrlock_drained() which begins a drained section before + * acquiring the lock. This variable here is used so bdrv_graph_wrunlock() knows + * if it also needs to end such a drained section. It needs to be a counter, + * because the aio_poll() call in bdrv_graph_wrlock() might re-enter + * bdrv_graph_wrlock_drained(). And note that aio_bh_poll() in + * bdrv_graph_wrunlock() might also re-enter a write-locked section. + */ +static int wrlock_quiesced_counter; + +/* * A reader coroutine could move from an AioContext to another. * If this happens, there is no problem from the point of view of * counters. The problem is that the total count becomes @@ -112,8 +123,14 @@ void no_coroutine_fn bdrv_graph_wrlock(void) assert(!qatomic_read(&has_writer)); assert(!qemu_in_coroutine()); - /* Make sure that constantly arriving new I/O doesn't cause starvation */ - bdrv_drain_all_begin_nopoll(); + bool need_drain = wrlock_quiesced_counter == 0; + + if (need_drain) { + /* + * Make sure that constantly arriving new I/O doesn't cause starvation + */ + bdrv_drain_all_begin_nopoll(); + } /* * reader_count == 0: this means writer will read has_reader as 1 @@ -139,7 +156,18 @@ void no_coroutine_fn bdrv_graph_wrlock(void) smp_mb(); } while (reader_count() >= 1); - bdrv_drain_all_end(); + if (need_drain) { + bdrv_drain_all_end(); + } +} + +void no_coroutine_fn bdrv_graph_wrlock_drained(void) +{ + GLOBAL_STATE_CODE(); + + bdrv_drain_all_begin(); + wrlock_quiesced_counter++; + bdrv_graph_wrlock(); } void no_coroutine_fn bdrv_graph_wrunlock(void) @@ -168,6 +196,12 @@ void no_coroutine_fn bdrv_graph_wrunlock(void) * progress. */ aio_bh_poll(qemu_get_aio_context()); + + if (wrlock_quiesced_counter > 0) { + bdrv_drain_all_end(); + wrlock_quiesced_counter--; + } + } void coroutine_fn bdrv_graph_co_rdlock(void) @@ -361,7 +361,7 @@ static void bdrv_do_drained_begin(BlockDriverState *bs, BdrvChild *parent, GLOBAL_STATE_CODE(); /* Stop things in parent-to-child order */ - if (qatomic_fetch_inc(&bs->quiesce_counter) == 0) { + if (bs->quiesce_counter++ == 0) { GRAPH_RDLOCK_GUARD_MAINLOOP(); bdrv_parent_drained_begin(bs, parent); if (bs->drv && bs->drv->bdrv_drain_begin) { @@ -401,8 +401,6 @@ bdrv_drained_begin(BlockDriverState *bs) */ static void bdrv_do_drained_end(BlockDriverState *bs, BdrvChild *parent) { - int old_quiesce_counter; - IO_OR_GS_CODE(); if (qemu_in_coroutine()) { @@ -415,8 +413,7 @@ static void bdrv_do_drained_end(BlockDriverState *bs, BdrvChild *parent) assert(bs->quiesce_counter > 0); /* Re-enable things in child-to-parent order */ - old_quiesce_counter = qatomic_fetch_dec(&bs->quiesce_counter); - if (old_quiesce_counter == 1) { + if (--bs->quiesce_counter == 0) { GRAPH_RDLOCK_GUARD_MAINLOOP(); if (bs->drv && bs->drv->bdrv_drain_end) { bs->drv->bdrv_drain_end(bs); diff --git a/block/mirror.c b/block/mirror.c index 6e8caf4..b344182 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -761,10 +761,14 @@ static int mirror_exit_common(Job *job) bdrv_graph_rdlock_main_loop(); bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing, &error_abort); + bdrv_graph_rdunlock_main_loop(); if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) { BlockDriverState *backing; - BlockDriverState *unfiltered_target = bdrv_skip_filters(target_bs); + BlockDriverState *unfiltered_target; + + bdrv_graph_wrlock_drained(); + unfiltered_target = bdrv_skip_filters(target_bs); backing = s->sync_mode == MIRROR_SYNC_MODE_NONE ? src : s->base; if (bdrv_cow_bs(unfiltered_target) != backing) { @@ -775,16 +779,18 @@ static int mirror_exit_common(Job *job) ret = -EPERM; } } + bdrv_graph_wrunlock(); } else if (!abort && s->backing_mode == MIRROR_OPEN_BACKING_CHAIN) { + bdrv_graph_rdlock_main_loop(); assert(!bdrv_backing_chain_next(target_bs)); ret = bdrv_open_backing_file(bdrv_skip_filters(target_bs), NULL, "backing", &local_err); + bdrv_graph_rdunlock_main_loop(); if (ret < 0) { error_report_err(local_err); local_err = NULL; } } - bdrv_graph_rdunlock_main_loop(); if (s->should_complete && !abort) { BlockDriverState *to_replace = s->to_replace ?: src; @@ -2014,15 +2020,13 @@ static BlockJob *mirror_start_job( */ bdrv_disable_dirty_bitmap(s->dirty_bitmap); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); ret = block_job_add_bdrv(&s->common, "source", bs, 0, BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ, errp); if (ret < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } @@ -2068,19 +2072,16 @@ static BlockJob *mirror_start_job( iter_shared_perms, errp); if (ret < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } } if (bdrv_freeze_backing_chain(mirror_top_bs, target, errp) < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); QTAILQ_INIT(&s->ops_in_flight); diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index 6919a49..282d1c3 100644 --- a/block/monitor/block-hmp-cmds.c +++ b/block/monitor/block-hmp-cmds.c @@ -144,7 +144,7 @@ void hmp_drive_del(Monitor *mon, const QDict *qdict) Error *local_err = NULL; GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + bdrv_graph_rdlock_main_loop(); bs = bdrv_find_node(id); if (bs) { @@ -152,29 +152,31 @@ void hmp_drive_del(Monitor *mon, const QDict *qdict) if (local_err) { error_report_err(local_err); } - return; + goto unlock; } blk = blk_by_name(id); if (!blk) { error_report("Device '%s' not found", id); - return; + goto unlock; } if (!blk_legacy_dinfo(blk)) { error_report("Deleting device added with blockdev-add" " is not supported"); - return; + goto unlock; } bs = blk_bs(blk); if (bs) { if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_DRIVE_DEL, &local_err)) { error_report_err(local_err); - return; + goto unlock; } + bdrv_graph_rdunlock_main_loop(); blk_remove_bs(blk); + bdrv_graph_rdlock_main_loop(); } /* Make the BlockBackend and the attached BlockDriverState anonymous */ @@ -191,6 +193,9 @@ void hmp_drive_del(Monitor *mon, const QDict *qdict) } else { blk_unref(blk); } + +unlock: + bdrv_graph_rdunlock_main_loop(); } void hmp_commit(Monitor *mon, const QDict *qdict) diff --git a/block/qapi.c b/block/qapi.c index 2c50a6b..12fbf8d 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -51,6 +51,8 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk, ImageInfo *backing_info; BlockDriverState *backing; BlockDeviceInfo *info; + BlockdevChildList **children_list_tail; + BdrvChild *child; if (!bs->drv) { error_setg(errp, "Block device %s is ejected", bs->node_name); @@ -73,8 +75,14 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk, .no_flush = !!(bs->open_flags & BDRV_O_NO_FLUSH), }; - if (bs->node_name[0]) { - info->node_name = g_strdup(bs->node_name); + info->node_name = g_strdup(bs->node_name); + + children_list_tail = &info->children; + QLIST_FOREACH(child, &bs->children, next) { + BlockdevChild *child_ref = g_new0(BlockdevChild, 1); + child_ref->child = g_strdup(child->name); + child_ref->node_name = g_strdup(child->bs->node_name); + QAPI_LIST_APPEND(children_list_tail, child_ref); } backing = bdrv_cow_bs(bs); diff --git a/block/qcow2.c b/block/qcow2.c index 45451a7..4aa9f9e 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -2823,11 +2823,9 @@ qcow2_do_close(BlockDriverState *bs, bool close_data_file) if (close_data_file && has_data_file(bs)) { GLOBAL_STATE_CODE(); bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, s->data_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); s->data_file = NULL; bdrv_graph_rdlock_main_loop(); } diff --git a/block/quorum.c b/block/quorum.c index cc3bc5f..76a4feb 100644 --- a/block/quorum.c +++ b/block/quorum.c @@ -1037,8 +1037,7 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags, close_exit: /* cleanup on error */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); for (i = 0; i < s->num_children; i++) { if (!opened[i]) { continue; @@ -1046,7 +1045,6 @@ close_exit: bdrv_unref_child(bs, s->children[i]); } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_free(s->children); g_free(opened); exit: @@ -1059,13 +1057,11 @@ static void quorum_close(BlockDriverState *bs) BDRVQuorumState *s = bs->opaque; int i; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); for (i = 0; i < s->num_children; i++) { bdrv_unref_child(bs, s->children[i]); } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_free(s->children); } diff --git a/block/replication.c b/block/replication.c index 0879718..3a431e9 100644 --- a/block/replication.c +++ b/block/replication.c @@ -364,14 +364,15 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable, BlockReopenQueue *reopen_queue = NULL; GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + bdrv_graph_rdlock_main_loop(); /* * s->hidden_disk and s->secondary_disk may not be set yet, as they will * only be set after the children are writable. */ hidden_disk = bs->file->bs->backing; secondary_disk = hidden_disk->bs->backing; + bdrv_graph_rdunlock_main_loop(); if (writable) { s->orig_hidden_read_only = bdrv_is_read_only(hidden_disk->bs); @@ -540,8 +541,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, return; } - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_ref(hidden_disk->bs); s->hidden_disk = bdrv_attach_child(bs, hidden_disk->bs, "hidden disk", @@ -550,7 +550,6 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, if (local_err) { error_propagate(errp, local_err); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return; } @@ -561,7 +560,6 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, if (local_err) { error_propagate(errp, local_err); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return; } @@ -574,14 +572,12 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, !check_top_bs(top_bs, bs)) { error_setg(errp, "No top_bs or it is invalid"); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); reopen_backing_file(bs, false, NULL); return; } bdrv_op_block_all(top_bs, s->blocker); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); s->backup_job = backup_job_create( NULL, s->secondary_disk->bs, s->hidden_disk->bs, @@ -656,14 +652,12 @@ static void replication_done(void *opaque, int ret) if (ret == 0) { s->stage = BLOCK_REPLICATION_DONE; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, s->secondary_disk); s->secondary_disk = NULL; bdrv_unref_child(bs, s->hidden_disk); s->hidden_disk = NULL; bdrv_graph_wrunlock(); - bdrv_drain_all_end(); s->error = 0; } else { diff --git a/block/snapshot.c b/block/snapshot.c index 28c9c43..bd9d759 100644 --- a/block/snapshot.c +++ b/block/snapshot.c @@ -291,11 +291,9 @@ int bdrv_snapshot_goto(BlockDriverState *bs, } /* .bdrv_open() will re-attach it */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, fallback); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); ret = bdrv_snapshot_goto(fallback_bs, snapshot_id, errp); memset(bs->opaque, 0, drv->instance_size); diff --git a/block/stream.c b/block/stream.c index f5441f2..c0616b6 100644 --- a/block/stream.c +++ b/block/stream.c @@ -51,7 +51,7 @@ static int coroutine_fn stream_populate(BlockBackend *blk, return blk_co_preadv(blk, offset, bytes, NULL, BDRV_REQ_PREFETCH); } -static int stream_prepare(Job *job) +static int GRAPH_UNLOCKED stream_prepare(Job *job) { StreamBlockJob *s = container_of(job, StreamBlockJob, common.job); BlockDriverState *unfiltered_bs; @@ -73,12 +73,11 @@ static int stream_prepare(Job *job) s->cor_filter_bs = NULL; /* - * bdrv_set_backing_hd() requires that the unfiltered_bs and the COW child - * of unfiltered_bs is drained. Drain already here and use - * bdrv_set_backing_hd_drained() instead because the polling during - * drained_begin() might change the graph, and if we do this only later, we - * may end up working with the wrong base node (or it might even have gone - * away by the time we want to use it). + * bdrv_set_backing_hd() requires that all block nodes are drained. Drain + * already here, because the polling during drained_begin() might change the + * graph, and if we do this only later, we may end up working with the wrong + * base node (or it might even have gone away by the time we want to use + * it). */ if (unfiltered_bs_cow) { bdrv_ref(unfiltered_bs_cow); @@ -105,7 +104,7 @@ static int stream_prepare(Job *job) } bdrv_graph_wrlock(); - bdrv_set_backing_hd_drained(unfiltered_bs, base, &local_err); + bdrv_set_backing_hd(unfiltered_bs, base, &local_err); bdrv_graph_wrunlock(); /* @@ -371,12 +370,10 @@ void stream_start(const char *job_id, BlockDriverState *bs, * already have our own plans. Also don't allow resize as the image size is * queried only at the job start and then cached. */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); if (block_job_add_bdrv(&s->common, "active node", bs, 0, basic_flags | BLK_PERM_WRITE, errp)) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } @@ -397,12 +394,10 @@ void stream_start(const char *job_id, BlockDriverState *bs, basic_flags, errp); if (ret < 0) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); goto fail; } } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); s->base_overlay = base_overlay; s->above_base = above_base; diff --git a/block/vmdk.c b/block/vmdk.c index 89a7250..7b98deb 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -271,8 +271,7 @@ static void vmdk_free_extents(BlockDriverState *bs) BDRVVmdkState *s = bs->opaque; VmdkExtent *e; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); for (i = 0; i < s->num_extents; i++) { e = &s->extents[i]; g_free(e->l1_table); @@ -284,7 +283,6 @@ static void vmdk_free_extents(BlockDriverState *bs) } } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_free(s->extents); } @@ -1231,9 +1229,11 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options, extent_role |= BDRV_CHILD_METADATA; } + bdrv_graph_rdunlock_main_loop(); extent_file = bdrv_open_child(extent_path, options, extent_opt_prefix, bs, &child_of_bds, extent_role, false, &local_err); + bdrv_graph_rdlock_main_loop(); g_free(extent_path); if (!extent_file) { error_propagate(errp, local_err); @@ -1249,11 +1249,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options, 0, 0, 0, 0, 0, &extent, errp); if (ret < 0) { bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, extent_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_graph_rdlock_main_loop(); goto out; } @@ -1270,11 +1268,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options, g_free(buf); if (ret) { bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, extent_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_graph_rdlock_main_loop(); goto out; } @@ -1283,11 +1279,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options, ret = vmdk_open_se_sparse(bs, extent_file, bs->open_flags, errp); if (ret) { bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, extent_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_graph_rdlock_main_loop(); goto out; } @@ -1295,11 +1289,9 @@ vmdk_parse_extents(const char *desc, BlockDriverState *bs, QDict *options, } else { error_setg(errp, "Unsupported extent type '%s'", type); bdrv_graph_rdunlock_main_loop(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(bs, extent_file); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_graph_rdlock_main_loop(); ret = -ENOTSUP; goto out; @@ -1362,13 +1354,13 @@ static int vmdk_open(BlockDriverState *bs, QDict *options, int flags, BDRVVmdkState *s = bs->opaque; uint32_t magic; - GRAPH_RDLOCK_GUARD_MAINLOOP(); - ret = bdrv_open_file_child(NULL, options, "file", bs, errp); if (ret < 0) { return ret; } + GRAPH_RDLOCK_GUARD_MAINLOOP(); + buf = vmdk_read_desc(bs->file, 0, errp); if (!buf) { return -EINVAL; @@ -1421,7 +1421,7 @@ static void external_snapshot_action(TransactionAction *action, bdrv_graph_rdunlock_main_loop(); /* Paired with .clean() */ bdrv_drained_begin(state->old_bs); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + bdrv_graph_rdlock_main_loop(); /* Make sure the associated bs did not change with the drain. */ check_bs = bdrv_lookup_bs(device, node_name, errp); @@ -1430,18 +1430,18 @@ static void external_snapshot_action(TransactionAction *action, error_setg(errp, "Block node of device '%s' unexpectedly changed", device); } /* else errp is already set */ - return; + goto unlock; } if (!bdrv_is_inserted(state->old_bs)) { error_setg(errp, "Device '%s' has no medium", bdrv_get_device_or_node_name(state->old_bs)); - return; + goto unlock; } if (bdrv_op_is_blocked(state->old_bs, BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, errp)) { - return; + goto unlock; } if (!bdrv_is_read_only(state->old_bs)) { @@ -1449,7 +1449,7 @@ static void external_snapshot_action(TransactionAction *action, if (ret < 0) { error_setg_errno(errp, -ret, "Write to node '%s' failed", bdrv_get_device_or_node_name(state->old_bs)); - return; + goto unlock; } } @@ -1461,13 +1461,13 @@ static void external_snapshot_action(TransactionAction *action, if (node_name && !snapshot_node_name) { error_setg(errp, "New overlay node-name missing"); - return; + goto unlock; } if (snapshot_node_name && bdrv_lookup_bs(snapshot_node_name, snapshot_node_name, NULL)) { error_setg(errp, "New overlay node-name already in use"); - return; + goto unlock; } flags = state->old_bs->open_flags; @@ -1480,7 +1480,7 @@ static void external_snapshot_action(TransactionAction *action, int64_t size = bdrv_getlength(state->old_bs); if (size < 0) { error_setg_errno(errp, -size, "bdrv_getlength failed"); - return; + goto unlock; } bdrv_refresh_filename(state->old_bs); @@ -1491,7 +1491,7 @@ static void external_snapshot_action(TransactionAction *action, if (local_err) { error_propagate(errp, local_err); - return; + goto unlock; } } @@ -1507,7 +1507,7 @@ static void external_snapshot_action(TransactionAction *action, /* We will manually add the backing_hd field to the bs later */ if (!state->new_bs) { - return; + goto unlock; } /* @@ -1518,22 +1518,22 @@ static void external_snapshot_action(TransactionAction *action, bdrv_get_cumulative_perm(state->new_bs, &perm, &shared); if (perm & BLK_PERM_CONSISTENT_READ) { error_setg(errp, "The overlay is already in use"); - return; + goto unlock; } if (state->new_bs->drv->is_filter) { error_setg(errp, "Filters cannot be used as overlays"); - return; + goto unlock; } if (bdrv_cow_child(state->new_bs)) { error_setg(errp, "The overlay already has a backing image"); - return; + goto unlock; } if (!state->new_bs->drv->supports_backing) { error_setg(errp, "The overlay does not support backing images"); - return; + goto unlock; } /* @@ -1546,17 +1546,23 @@ static void external_snapshot_action(TransactionAction *action, * to keep this working. */ if (bdrv_is_inactive(state->old_bs) && !bdrv_is_inactive(state->new_bs)) { + bdrv_graph_rdunlock_main_loop(); + bdrv_drain_all_begin(); + bdrv_graph_rdlock_main_loop(); ret = bdrv_inactivate(state->new_bs, errp); + bdrv_drain_all_end(); if (ret < 0) { - return; + goto unlock; } } ret = bdrv_append(state->new_bs, state->old_bs, errp); if (ret < 0) { - return; + goto unlock; } state->overlay_appended = true; +unlock: + bdrv_graph_rdunlock_main_loop(); } static void external_snapshot_commit(void *opaque) @@ -1580,10 +1586,18 @@ static void external_snapshot_abort(void *opaque) AioContext *tmp_context; int ret; + bdrv_graph_wrlock_drained(); + aio_context = bdrv_get_aio_context(state->old_bs); - bdrv_ref(state->old_bs); /* we can't let bdrv_set_backind_hd() - close state->old_bs; we need it */ + /* + * Note that state->old_bs would not disappear during the + * write-locked section, because the unref from + * bdrv_set_backing_hd() only happens at the end of the write-locked + * section. However, just be explicit about keeping a reference and + * don't rely on that implicit detail. + */ + bdrv_ref(state->old_bs); bdrv_set_backing_hd(state->new_bs, NULL, &error_abort); /* @@ -1593,16 +1607,14 @@ static void external_snapshot_abort(void *opaque) */ tmp_context = bdrv_get_aio_context(state->old_bs); if (aio_context != tmp_context) { - ret = bdrv_try_change_aio_context(state->old_bs, - aio_context, NULL, NULL); + ret = bdrv_try_change_aio_context_locked(state->old_bs, + aio_context, NULL, + NULL); assert(ret == 0); } - bdrv_drained_begin(state->new_bs); - bdrv_graph_wrlock(); bdrv_replace_node(state->new_bs, state->old_bs, &error_abort); bdrv_graph_wrunlock(); - bdrv_drained_end(state->new_bs); bdrv_unref(state->old_bs); /* bdrv_replace_node() ref'ed old_bs */ } @@ -1770,7 +1782,10 @@ static void drive_backup_action(DriveBackup *backup, } if (set_backing_hd) { - if (bdrv_set_backing_hd(target_bs, source, errp) < 0) { + bdrv_graph_wrlock_drained(); + ret = bdrv_set_backing_hd(target_bs, source, errp); + bdrv_graph_wrunlock(); + if (ret < 0) { goto unref; } } @@ -3511,10 +3526,10 @@ void qmp_blockdev_del(const char *node_name, Error **errp) void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp) { + BlockDriverState *bs; int ret; GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); if (!node_name) { if (active) { @@ -3525,19 +3540,30 @@ void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp) error_setg_errno(errp, -ret, "Failed to inactivate all nodes"); } } + return; + } + + if (!active) { + bdrv_drain_all_begin(); + } + bdrv_graph_rdlock_main_loop(); + + bs = bdrv_find_node(node_name); + if (!bs) { + error_setg(errp, "Failed to find node with node-name='%s'", + node_name); + goto unlock; + } + if (active) { + bdrv_activate(bs, errp); } else { - BlockDriverState *bs = bdrv_find_node(node_name); - if (!bs) { - error_setg(errp, "Failed to find node with node-name='%s'", - node_name); - return; - } + bdrv_inactivate(bs, errp); + } - if (active) { - bdrv_activate(bs, errp); - } else { - bdrv_inactivate(bs, errp); - } +unlock: + bdrv_graph_rdunlock_main_loop(); + if (!active) { + bdrv_drain_all_end(); } } @@ -3561,8 +3587,7 @@ void qmp_x_blockdev_change(const char *parent, const char *child, BlockDriverState *parent_bs, *new_bs = NULL; BdrvChild *p_child; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); parent_bs = bdrv_lookup_bs(parent, parent, errp); if (!parent_bs) { @@ -3599,7 +3624,6 @@ void qmp_x_blockdev_change(const char *parent, const char *child, out: bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } BlockJobInfoList *qmp_query_block_jobs(Error **errp) @@ -198,8 +198,7 @@ void block_job_remove_all_bdrv(BlockJob *job) * one to make sure that such a concurrent access does not attempt * to process an already freed BdrvChild. */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); while (job->nodes) { GSList *l = job->nodes; BdrvChild *c = l->data; @@ -212,7 +211,6 @@ void block_job_remove_all_bdrv(BlockJob *job) g_slist_free_1(l); } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } bool block_job_has_bdrv(BlockJob *job, BlockDriverState *bs) @@ -498,8 +496,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, int ret; GLOBAL_STATE_CODE(); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); if (job_id == NULL && !(flags & JOB_INTERNAL)) { job_id = bdrv_get_device_name(bs); @@ -509,7 +506,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, flags, cb, opaque, errp); if (job == NULL) { bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return NULL; } @@ -548,12 +544,10 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); return job; fail: bdrv_graph_wrunlock(); - bdrv_drain_all_end(); job_early_fail(&job->job); return NULL; } diff --git a/bsd-user/main.c b/bsd-user/main.c index d0cc8e0..7e5d4bb 100644 --- a/bsd-user/main.c +++ b/bsd-user/main.c @@ -38,6 +38,7 @@ #include "qemu/plugin.h" #include "user/guest-base.h" #include "user/page-protection.h" +#include "accel/accel-ops.h" #include "tcg/startup.h" #include "qemu/timer.h" #include "qemu/envlist.h" diff --git a/cpu-target.c b/cpu-target.c index 1c90a30..772e354 100644 --- a/cpu-target.c +++ b/cpu-target.c @@ -19,10 +19,9 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "exec/cpu-common.h" -#include "exec/tswap.h" #include "exec/replay-core.h" #include "exec/log.h" #include "hw/core/cpu.h" @@ -85,9 +84,3 @@ void cpu_abort(CPUState *cpu, const char *fmt, ...) #endif abort(); } - -#undef target_big_endian -bool target_big_endian(void) -{ - return TARGET_BIG_ENDIAN; -} diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index 2d8e5ca..0790e50 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -247,3 +247,22 @@ The multifd VFIO device state transfer is controlled by "x-migration-multifd-transfer" VFIO device property. This property defaults to AUTO, which means that VFIO device state transfer via multifd channels is attempted in configurations that otherwise support it. + +Since the target QEMU needs to load device state buffers in-order it needs to +queue incoming buffers until they can be loaded into the device. +This means that a malicious QEMU source could theoretically cause the target +QEMU to allocate unlimited amounts of memory for such buffers-in-flight. + +The "x-migration-max-queued-buffers-size" property allows capping the total size +of these VFIO device state buffers queued at the destination. + +Because a malicious QEMU source causing OOM on the target is not expected to be +a realistic threat in most of VFIO live migration use cases and the right value +depends on the particular setup by default this queued buffers size limit is +disabled by setting it to UINT64_MAX. + +Some host platforms (like ARM64) require that VFIO device config is loaded only +after all iterables were loaded, during non-iterables loading phase. +Such interlocking is controlled by "x-migration-load-config-after-iter" VFIO +device property, which in its default setting (AUTO) does so only on platforms +that actually require it. diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst index 231cc0f..dfdbeac 100644 --- a/docs/devel/qapi-code-gen.rst +++ b/docs/devel/qapi-code-gen.rst @@ -876,25 +876,35 @@ structuring content. Headings and subheadings ~~~~~~~~~~~~~~~~~~~~~~~~ -A free-form documentation comment containing a line which starts with -some ``=`` symbols and then a space defines a section heading:: +Free-form documentation does not start with ``@SYMBOL`` and can contain +arbitrary rST markup. Headings can be marked up using the standard rST +syntax:: ## - # = This is a top level heading + # ************************* + # This is a level 2 heading + # ************************* # # This is a free-form comment which will go under the # top level heading. ## ## - # == This is a second level heading + # This is a third level heading + # ============================== + # + # Level 4 + # _______ + # + # Level 5 + # ^^^^^^^ + # + # Level 6 + # """"""" ## -A heading line must be the first line of the documentation -comment block. - -Section headings must always be correctly nested, so you can only -define a third-level heading inside a second-level heading, and so on. +Level 1 headings are reserved for use by the generated documentation +page itself, leaving level 2 as the highest level that should be used. Documentation markup diff --git a/docs/devel/qapi-domain.rst b/docs/devel/qapi-domain.rst index 1123872..b71890f 100644 --- a/docs/devel/qapi-domain.rst +++ b/docs/devel/qapi-domain.rst @@ -242,6 +242,37 @@ Example:: } +``:return-nodesc:`` +------------------- + +Document the return type of a QAPI command, without an accompanying +description. + +:availability: This field list is only available in the body of the + Command directive. +:syntax: ``:return-nodesc: type`` +:type: `sphinx.util.docfields.Field + <https://pydoc.dev/sphinx/latest/sphinx.util.docfields.Field.html?private=1>`_ + + +Example:: + + .. qapi:command:: query-replay + :since: 5.2 + + Retrieve the record/replay information. It includes current + instruction count which may be used for ``replay-break`` and + ``replay-seek`` commands. + + :return-nodesc: ReplayInfo + + .. qmp-example:: + + -> { "execute": "query-replay" } + <- { "return": { + "mode": "play", "filename": "log.rr", "icount": 220414 } + } + ``:value:`` ----------- diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index dc8c441..b673753 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -351,7 +351,7 @@ Writing procedural macros ''''''''''''''''''''''''' By conventions, procedural macros are split in two functions, one -returning ``Result<proc_macro2::TokenStream, MacroError>`` with the body of +returning ``Result<proc_macro2::TokenStream, syn::Error>`` with the body of the procedural macro, and the second returning ``proc_macro::TokenStream`` which is the actual procedural macro. The former's name is the same as the latter with the ``_or_error`` suffix. The code for the latter is more @@ -361,18 +361,19 @@ from the type after ``as`` in the invocation of ``parse_macro_input!``:: #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_object_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } The ``qemu_api_macros`` crate has utility functions to examine a ``DeriveInput`` and perform common checks (e.g. looking for a struct -with named fields). These functions return ``Result<..., MacroError>`` +with named fields). These functions return ``Result<..., syn::Error>`` and can be used easily in the procedural macro function:: fn derive_object_or_error(input: DeriveInput) -> - Result<proc_macro2::TokenStream, MacroError> + Result<proc_macro2::TokenStream, Error> { is_c_repr(&input, "#[derive(Object)]")?; diff --git a/docs/devel/tracing.rst b/docs/devel/tracing.rst index 043bed7..f4557ee 100644 --- a/docs/devel/tracing.rst +++ b/docs/devel/tracing.rst @@ -76,7 +76,7 @@ The "io/trace.h" file must be created manually with an #include of the corresponding "trace/trace-<subdir>.h" file that will be generated in the builddir:: - $ echo '#include "trace/trace-io.h"' >io/trace.h + $ (echo '/* SPDX-License-Identifier: GPL-2.0-or-later */' ; echo '#include "trace/trace-io.h"') >io/trace.h While it is possible to include a trace.h file from outside a source file's own sub-directory, this is discouraged in general. It is strongly preferred that diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json index 745d21d..6bbe2cc 100644 --- a/docs/interop/firmware.json +++ b/docs/interop/firmware.json @@ -11,7 +11,9 @@ # later. See the COPYING file in the top-level directory. ## -# = Firmware +# ******** +# Firmware +# ******** ## { 'pragma': { @@ -57,10 +59,17 @@ # # @memory: The firmware is to be mapped into memory. # +# @igvm: The firmware is defined by a file conforming to the IGVM +# specification and mapped into memory according to directives +# defined in the file. This is similar to @memory but may +# include additional processing defined by the IGVM file +# including initial CPU state or population of metadata into +# the guest address space. Since: 10.1 +# # Since: 3.0 ## { 'enum' : 'FirmwareDevice', - 'data' : [ 'flash', 'kernel', 'memory' ] } + 'data' : [ 'flash', 'kernel', 'memory', 'igvm' ] } ## # @FirmwareArchitecture: @@ -378,6 +387,24 @@ 'data' : { 'filename' : 'str' } } ## +# @FirmwareMappingIgvm: +# +# Describes loading and mapping properties for the firmware executable, +# when @FirmwareDevice is @igvm. +# +# @filename: Identifies the IGVM file containing the firmware executable +# along with other information used to configure the initial +# state of the guest. The IGVM file may be shared by multiple +# virtual machine definitions. This corresponds to creating +# an object on the command line with "-object igvm-cfg, +# file=@filename". +# +# Since: 10.1 +## +{ 'struct' : 'FirmwareMappingIgvm', + 'data' : { 'filename' : 'str' } } + +## # @FirmwareMapping: # # Provides a discriminated structure for firmware to describe its @@ -393,7 +420,8 @@ 'discriminator' : 'device', 'data' : { 'flash' : 'FirmwareMappingFlash', 'kernel' : 'FirmwareMappingKernel', - 'memory' : 'FirmwareMappingMemory' } } + 'memory' : 'FirmwareMappingMemory', + 'igvm' : 'FirmwareMappingIgvm' } } ## # @Firmware: diff --git a/docs/interop/qemu-ga-ref.rst b/docs/interop/qemu-ga-ref.rst index 25f6e24..ea6652a 100644 --- a/docs/interop/qemu-ga-ref.rst +++ b/docs/interop/qemu-ga-ref.rst @@ -2,5 +2,4 @@ QEMU Guest Agent Protocol Reference =================================== .. qapi-doc:: qga/qapi-schema.json - :transmogrify: :namespace: QGA diff --git a/docs/interop/qemu-qmp-ref.rst b/docs/interop/qemu-qmp-ref.rst index 3bc1ca1..f0ce39a 100644 --- a/docs/interop/qemu-qmp-ref.rst +++ b/docs/interop/qemu-qmp-ref.rst @@ -7,5 +7,4 @@ QEMU QMP Reference Manual :local: .. qapi-doc:: qapi/qapi-schema.json - :transmogrify: :namespace: QMP diff --git a/docs/interop/qemu-storage-daemon-qmp-ref.rst b/docs/interop/qemu-storage-daemon-qmp-ref.rst index dc7bde2..4dbb6a2 100644 --- a/docs/interop/qemu-storage-daemon-qmp-ref.rst +++ b/docs/interop/qemu-storage-daemon-qmp-ref.rst @@ -5,5 +5,4 @@ QEMU Storage Daemon QMP Reference Manual :local: .. qapi-doc:: storage-daemon/qapi/qapi-schema.json - :transmogrify: :namespace: QSD diff --git a/docs/interop/vhost-user.json b/docs/interop/vhost-user.json index b6ade9e..095eb99 100644 --- a/docs/interop/vhost-user.json +++ b/docs/interop/vhost-user.json @@ -10,7 +10,9 @@ # later. See the COPYING file in the top-level directory. ## -# = vhost user backend discovery & capabilities +# ******************************************* +# vhost user backend discovery & capabilities +# ******************************************* ## ## diff --git a/docs/sphinx/qapi_domain.py b/docs/sphinx/qapi_domain.py index ebc46a7..f561dc4 100644 --- a/docs/sphinx/qapi_domain.py +++ b/docs/sphinx/qapi_domain.py @@ -532,6 +532,14 @@ class QAPICommand(QAPIObject): names=("return",), can_collapse=True, ), + # :return-nodesc: TypeName + CompatField( + "returnvalue", + label=_("Return"), + names=("return-nodesc",), + bodyrolename="type", + has_arg=False, + ), ] ) diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py index 8011ac9..c2f09ba 100644 --- a/docs/sphinx/qapidoc.py +++ b/docs/sphinx/qapidoc.py @@ -64,8 +64,6 @@ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, switch_source_input from sphinx.util.nodes import nested_parse_with_titles -from qapidoc_legacy import QAPISchemaGenRSTVisitor # type: ignore - if TYPE_CHECKING: from typing import ( @@ -218,6 +216,11 @@ class Transmogrifier: typ = self.format_type(member) self.add_field(kind, member.name, body, info, typ) + @staticmethod + def reformat_arobase(text: str) -> str: + """ reformats @var to ``var`` """ + return re.sub(r"@([\w-]+)", r"``\1``", text) + # Transmogrification helpers def visit_paragraph(self, section: QAPIDoc.Section) -> None: @@ -255,22 +258,28 @@ class Transmogrifier: def visit_returns(self, section: QAPIDoc.Section) -> None: assert isinstance(self.entity, QAPISchemaCommand) rtype = self.entity.ret_type - # q_empty can produce None, but we won't be documenting anything - # without an explicit return statement in the doc block, and we - # should not have any such explicit statements when there is no - # return value. + # return statements will not be present (and won't be + # autogenerated) for any command that doesn't return + # *something*, so rtype will always be defined here. assert rtype typ = self.format_type(rtype) assert typ - assert section.text - self.add_field("return", typ, section.text, section.info) + + if section.text: + self.add_field("return", typ, section.text, section.info) + else: + self.add_lines(f":return-nodesc: {typ}", section.info) def visit_errors(self, section: QAPIDoc.Section) -> None: - # FIXME: the formatting for errors may be inconsistent and may - # or may not require different newline placement to ensure - # proper rendering as a nested list. - self.add_lines(f":error:\n{section.text}", section.info) + # If the section text does not start with a space, it means text + # began on the same line as the "Error:" string and we should + # not insert a newline in this case. + if section.text[0].isspace(): + text = f":error:\n{section.text}" + else: + text = f":error: {section.text}" + self.add_lines(text, section.info) def preamble(self, ent: QAPISchemaDefinition) -> None: """ @@ -357,8 +366,7 @@ class Transmogrifier: # Add sections in source order: for i, section in enumerate(sections): - # @var is translated to ``var``: - section.text = re.sub(r"@([\w-]+)", r"``\1``", section.text) + section.text = self.reformat_arobase(section.text) if section.kind == QAPIDoc.Kind.PLAIN: self.visit_paragraph(section) @@ -393,44 +401,9 @@ class Transmogrifier: self.ensure_blank_line() def visit_freeform(self, doc: QAPIDoc) -> None: - # TODO: Once the old qapidoc transformer is deprecated, freeform - # sections can be updated to pure rST, and this transformed removed. - # - # For now, translate our micro-format into rST. Code adapted - # from Peter Maydell's freeform(). - assert len(doc.all_sections) == 1, doc.all_sections body = doc.all_sections[0] - text = body.text - info = doc.info - - if re.match(r"=+ ", text): - # Section/subsection heading (if present, will always be the - # first line of the block) - (heading, _, text) = text.partition("\n") - (leader, _, heading) = heading.partition(" ") - # Implicit +1 for heading in the containing .rst doc - level = len(leader) + 1 - - # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#sections - markers = ' #*=_^"' - overline = level <= 2 - marker = markers[level] - - self.ensure_blank_line() - # This credits all 2 or 3 lines to the single source line. - if overline: - self.add_line(marker * len(heading), info) - self.add_line(heading, info) - self.add_line(marker * len(heading), info) - self.ensure_blank_line() - - # Eat blank line(s) and advance info - trimmed = text.lstrip("\n") - text = trimmed - info = info.next_line(len(text) - len(trimmed) + 1) - - self.add_lines(text, info) + self.add_lines(self.reformat_arobase(body.text), doc.info) self.ensure_blank_line() def visit_entity(self, ent: QAPISchemaDefinition) -> None: @@ -504,15 +477,9 @@ class QAPIDocDirective(NestedDirective): option_spec = { "qapifile": directives.unchanged_required, "namespace": directives.unchanged, - "transmogrify": directives.flag, } has_content = False - def new_serialno(self) -> str: - """Return a unique new ID string suitable for use as a node's ID""" - env = self.state.document.settings.env - return "qapidoc-%d" % env.new_serialno("qapidoc") - def transmogrify(self, schema: QAPISchema) -> nodes.Element: logger.info("Transmogrifying QAPI to rST ...") vis = Transmogrifier() @@ -590,21 +557,10 @@ class QAPIDocDirective(NestedDirective): outfile.write(f" {rcol}") outfile.write("\n") - def legacy(self, schema: QAPISchema) -> nodes.Element: - vis = QAPISchemaGenRSTVisitor(self) - vis.visit_begin(schema) - for doc in schema.docs: - if doc.symbol: - vis.symbol(doc, schema.lookup_entity(doc.symbol)) - else: - vis.freeform(doc) - return vis.get_document_node() # type: ignore - def run(self) -> Sequence[nodes.Node]: env = self.state.document.settings.env qapifile = env.config.qapidoc_srctree + "/" + self.arguments[0] qapidir = os.path.dirname(qapifile) - transmogrify = "transmogrify" in self.options try: schema = QAPISchema(qapifile) @@ -617,11 +573,7 @@ class QAPIDocDirective(NestedDirective): # so they are displayed nicely to the user raise ExtensionError(str(err)) from err - if transmogrify: - contentnode = self.transmogrify(schema) - else: - contentnode = self.legacy(schema) - + contentnode = self.transmogrify(schema) return contentnode.children diff --git a/docs/sphinx/qapidoc_legacy.py b/docs/sphinx/qapidoc_legacy.py deleted file mode 100644 index 13520f4..0000000 --- a/docs/sphinx/qapidoc_legacy.py +++ /dev/null @@ -1,440 +0,0 @@ -# coding=utf-8 -# type: ignore -# -# QEMU qapidoc QAPI file parsing extension -# -# Copyright (c) 2020 Linaro -# -# This work is licensed under the terms of the GNU GPLv2 or later. -# See the COPYING file in the top-level directory. - -""" -qapidoc is a Sphinx extension that implements the qapi-doc directive - -The purpose of this extension is to read the documentation comments -in QAPI schema files, and insert them all into the current document. - -It implements one new rST directive, "qapi-doc::". -Each qapi-doc:: directive takes one argument, which is the -pathname of the schema file to process, relative to the source tree. - -The docs/conf.py file must set the qapidoc_srctree config value to -the root of the QEMU source tree. - -The Sphinx documentation on writing extensions is at: -https://www.sphinx-doc.org/en/master/development/index.html -""" - -import re -import textwrap - -from docutils import nodes -from docutils.statemachine import ViewList -from qapi.error import QAPISemError -from qapi.gen import QAPISchemaVisitor -from qapi.parser import QAPIDoc - - -def dedent(text: str) -> str: - # Adjust indentation to make description text parse as paragraph. - - lines = text.splitlines(True) - if re.match(r"\s+", lines[0]): - # First line is indented; description started on the line after - # the name. dedent the whole block. - return textwrap.dedent(text) - - # Descr started on same line. Dedent line 2+. - return lines[0] + textwrap.dedent("".join(lines[1:])) - - -class QAPISchemaGenRSTVisitor(QAPISchemaVisitor): - """A QAPI schema visitor which generates docutils/Sphinx nodes - - This class builds up a tree of docutils/Sphinx nodes corresponding - to documentation for the various QAPI objects. To use it, first - create a QAPISchemaGenRSTVisitor object, and call its - visit_begin() method. Then you can call one of the two methods - 'freeform' (to add documentation for a freeform documentation - chunk) or 'symbol' (to add documentation for a QAPI symbol). These - will cause the visitor to build up the tree of document - nodes. Once you've added all the documentation via 'freeform' and - 'symbol' method calls, you can call 'get_document_nodes' to get - the final list of document nodes (in a form suitable for returning - from a Sphinx directive's 'run' method). - """ - def __init__(self, sphinx_directive): - self._cur_doc = None - self._sphinx_directive = sphinx_directive - self._top_node = nodes.section() - self._active_headings = [self._top_node] - - def _make_dlitem(self, term, defn): - """Return a dlitem node with the specified term and definition. - - term should be a list of Text and literal nodes. - defn should be one of: - - a string, which will be handed to _parse_text_into_node - - a list of Text and literal nodes, which will be put into - a paragraph node - """ - dlitem = nodes.definition_list_item() - dlterm = nodes.term('', '', *term) - dlitem += dlterm - if defn: - dldef = nodes.definition() - if isinstance(defn, list): - dldef += nodes.paragraph('', '', *defn) - else: - self._parse_text_into_node(defn, dldef) - dlitem += dldef - return dlitem - - def _make_section(self, title): - """Return a section node with optional title""" - section = nodes.section(ids=[self._sphinx_directive.new_serialno()]) - if title: - section += nodes.title(title, title) - return section - - def _nodes_for_ifcond(self, ifcond, with_if=True): - """Return list of Text, literal nodes for the ifcond - - Return a list which gives text like ' (If: condition)'. - If with_if is False, we don't return the "(If: " and ")". - """ - - doc = ifcond.docgen() - if not doc: - return [] - doc = nodes.literal('', doc) - if not with_if: - return [doc] - - nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')] - nodelist.append(doc) - nodelist.append(nodes.Text(')')) - return nodelist - - def _nodes_for_one_member(self, member): - """Return list of Text, literal nodes for this member - - Return a list of doctree nodes which give text like - 'name: type (optional) (If: ...)' suitable for use as the - 'term' part of a definition list item. - """ - term = [nodes.literal('', member.name)] - if member.type.doc_type(): - term.append(nodes.Text(': ')) - term.append(nodes.literal('', member.type.doc_type())) - if member.optional: - term.append(nodes.Text(' (optional)')) - if member.ifcond.is_present(): - term.extend(self._nodes_for_ifcond(member.ifcond)) - return term - - def _nodes_for_variant_when(self, branches, variant): - """Return list of Text, literal nodes for variant 'when' clause - - Return a list of doctree nodes which give text like - 'when tagname is variant (If: ...)' suitable for use in - the 'branches' part of a definition list. - """ - term = [nodes.Text(' when '), - nodes.literal('', branches.tag_member.name), - nodes.Text(' is '), - nodes.literal('', '"%s"' % variant.name)] - if variant.ifcond.is_present(): - term.extend(self._nodes_for_ifcond(variant.ifcond)) - return term - - def _nodes_for_members(self, doc, what, base=None, branches=None): - """Return list of doctree nodes for the table of members""" - dlnode = nodes.definition_list() - for section in doc.args.values(): - term = self._nodes_for_one_member(section.member) - # TODO drop fallbacks when undocumented members are outlawed - if section.text: - defn = dedent(section.text) - else: - defn = [nodes.Text('Not documented')] - - dlnode += self._make_dlitem(term, defn) - - if base: - dlnode += self._make_dlitem([nodes.Text('The members of '), - nodes.literal('', base.doc_type())], - None) - - if branches: - for v in branches.variants: - if v.type.name == 'q_empty': - continue - assert not v.type.is_implicit() - term = [nodes.Text('The members of '), - nodes.literal('', v.type.doc_type())] - term.extend(self._nodes_for_variant_when(branches, v)) - dlnode += self._make_dlitem(term, None) - - if not dlnode.children: - return [] - - section = self._make_section(what) - section += dlnode - return [section] - - def _nodes_for_enum_values(self, doc): - """Return list of doctree nodes for the table of enum values""" - seen_item = False - dlnode = nodes.definition_list() - for section in doc.args.values(): - termtext = [nodes.literal('', section.member.name)] - if section.member.ifcond.is_present(): - termtext.extend(self._nodes_for_ifcond(section.member.ifcond)) - # TODO drop fallbacks when undocumented members are outlawed - if section.text: - defn = dedent(section.text) - else: - defn = [nodes.Text('Not documented')] - - dlnode += self._make_dlitem(termtext, defn) - seen_item = True - - if not seen_item: - return [] - - section = self._make_section('Values') - section += dlnode - return [section] - - def _nodes_for_arguments(self, doc, arg_type): - """Return list of doctree nodes for the arguments section""" - if arg_type and not arg_type.is_implicit(): - assert not doc.args - section = self._make_section('Arguments') - dlnode = nodes.definition_list() - dlnode += self._make_dlitem( - [nodes.Text('The members of '), - nodes.literal('', arg_type.name)], - None) - section += dlnode - return [section] - - return self._nodes_for_members(doc, 'Arguments') - - def _nodes_for_features(self, doc): - """Return list of doctree nodes for the table of features""" - seen_item = False - dlnode = nodes.definition_list() - for section in doc.features.values(): - dlnode += self._make_dlitem( - [nodes.literal('', section.member.name)], dedent(section.text)) - seen_item = True - - if not seen_item: - return [] - - section = self._make_section('Features') - section += dlnode - return [section] - - def _nodes_for_sections(self, doc): - """Return list of doctree nodes for additional sections""" - nodelist = [] - for section in doc.sections: - if section.kind == QAPIDoc.Kind.TODO: - # Hide TODO: sections - continue - - if section.kind == QAPIDoc.Kind.PLAIN: - # Sphinx cannot handle sectionless titles; - # Instead, just append the results to the prior section. - container = nodes.container() - self._parse_text_into_node(section.text, container) - nodelist += container.children - continue - - snode = self._make_section(section.kind.name.title()) - self._parse_text_into_node(dedent(section.text), snode) - nodelist.append(snode) - return nodelist - - def _nodes_for_if_section(self, ifcond): - """Return list of doctree nodes for the "If" section""" - nodelist = [] - if ifcond.is_present(): - snode = self._make_section('If') - snode += nodes.paragraph( - '', '', *self._nodes_for_ifcond(ifcond, with_if=False) - ) - nodelist.append(snode) - return nodelist - - def _add_doc(self, typ, sections): - """Add documentation for a command/object/enum... - - We assume we're documenting the thing defined in self._cur_doc. - typ is the type of thing being added ("Command", "Object", etc) - - sections is a list of nodes for sections to add to the definition. - """ - - doc = self._cur_doc - snode = nodes.section(ids=[self._sphinx_directive.new_serialno()]) - snode += nodes.title('', '', *[nodes.literal(doc.symbol, doc.symbol), - nodes.Text(' (' + typ + ')')]) - self._parse_text_into_node(doc.body.text, snode) - for s in sections: - if s is not None: - snode += s - self._add_node_to_current_heading(snode) - - def visit_enum_type(self, name, info, ifcond, features, members, prefix): - doc = self._cur_doc - self._add_doc('Enum', - self._nodes_for_enum_values(doc) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_object_type(self, name, info, ifcond, features, - base, members, branches): - doc = self._cur_doc - if base and base.is_implicit(): - base = None - self._add_doc('Object', - self._nodes_for_members(doc, 'Members', base, branches) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_alternate_type(self, name, info, ifcond, features, - alternatives): - doc = self._cur_doc - self._add_doc('Alternate', - self._nodes_for_members(doc, 'Members') - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_command(self, name, info, ifcond, features, arg_type, - ret_type, gen, success_response, boxed, allow_oob, - allow_preconfig, coroutine): - doc = self._cur_doc - self._add_doc('Command', - self._nodes_for_arguments(doc, arg_type) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def visit_event(self, name, info, ifcond, features, arg_type, boxed): - doc = self._cur_doc - self._add_doc('Event', - self._nodes_for_arguments(doc, arg_type) - + self._nodes_for_features(doc) - + self._nodes_for_sections(doc) - + self._nodes_for_if_section(ifcond)) - - def symbol(self, doc, entity): - """Add documentation for one symbol to the document tree - - This is the main entry point which causes us to add documentation - nodes for a symbol (which could be a 'command', 'object', 'event', - etc). We do this by calling 'visit' on the schema entity, which - will then call back into one of our visit_* methods, depending - on what kind of thing this symbol is. - """ - self._cur_doc = doc - entity.visit(self) - self._cur_doc = None - - def _start_new_heading(self, heading, level): - """Start a new heading at the specified heading level - - Create a new section whose title is 'heading' and which is placed - in the docutils node tree as a child of the most recent level-1 - heading. Subsequent document sections (commands, freeform doc chunks, - etc) will be placed as children of this new heading section. - """ - if len(self._active_headings) < level: - raise QAPISemError(self._cur_doc.info, - 'Level %d subheading found outside a ' - 'level %d heading' - % (level, level - 1)) - snode = self._make_section(heading) - self._active_headings[level - 1] += snode - self._active_headings = self._active_headings[:level] - self._active_headings.append(snode) - return snode - - def _add_node_to_current_heading(self, node): - """Add the node to whatever the current active heading is""" - self._active_headings[-1] += node - - def freeform(self, doc): - """Add a piece of 'freeform' documentation to the document tree - - A 'freeform' document chunk doesn't relate to any particular - symbol (for instance, it could be an introduction). - - If the freeform document starts with a line of the form - '= Heading text', this is a section or subsection heading, with - the heading level indicated by the number of '=' signs. - """ - - # QAPIDoc documentation says free-form documentation blocks - # must have only a body section, nothing else. - assert not doc.sections - assert not doc.args - assert not doc.features - self._cur_doc = doc - - text = doc.body.text - if re.match(r'=+ ', text): - # Section/subsection heading (if present, will always be - # the first line of the block) - (heading, _, text) = text.partition('\n') - (leader, _, heading) = heading.partition(' ') - node = self._start_new_heading(heading, len(leader)) - if text == '': - return - else: - node = nodes.container() - - self._parse_text_into_node(text, node) - self._cur_doc = None - - def _parse_text_into_node(self, doctext, node): - """Parse a chunk of QAPI-doc-format text into the node - - The doc comment can contain most inline rST markup, including - bulleted and enumerated lists. - As an extra permitted piece of markup, @var will be turned - into ``var``. - """ - - # Handle the "@var means ``var`` case - doctext = re.sub(r'@([\w-]+)', r'``\1``', doctext) - - rstlist = ViewList() - for line in doctext.splitlines(): - # The reported line number will always be that of the start line - # of the doc comment, rather than the actual location of the error. - # Being more precise would require overhaul of the QAPIDoc class - # to track lines more exactly within all the sub-parts of the doc - # comment, as well as counting lines here. - rstlist.append(line, self._cur_doc.info.fname, - self._cur_doc.info.line) - # Append a blank line -- in some cases rST syntax errors get - # attributed to the line after one with actual text, and if there - # isn't anything in the ViewList corresponding to that then Sphinx - # 1.6's AutodocReporter will then misidentify the source/line location - # in the error message (usually attributing it to the top-level - # .rst file rather than the offending .json file). The extra blank - # line won't affect the rendered output. - rstlist.append("", self._cur_doc.info.fname, self._cur_doc.info.line) - self._sphinx_directive.do_parse(rstlist, node) - - def get_document_node(self): - """Return the root docutils node which makes up the document""" - return self._top_node diff --git a/docs/system/devices/net.rst b/docs/system/devices/net.rst index a3efbdc..4d787c3 100644 --- a/docs/system/devices/net.rst +++ b/docs/system/devices/net.rst @@ -85,13 +85,59 @@ passt doesn't require any capability or privilege. passt has better performance than ``-net user``, full IPv6 support and better security as it's a daemon that is not executed in QEMU context. -passt can be connected to QEMU either by using a socket -(``-netdev stream``) or using the vhost-user interface (``-netdev vhost-user``). +passt_ can be used in the same way as the user backend (using ``-net passt``, +``-netdev passt`` or ``-nic passt``) or it can be launched manually and +connected to QEMU either by using a socket (``-netdev stream``) or by using +the vhost-user interface (``-netdev vhost-user``). + +Using ``-netdev stream`` or ``-netdev vhost-user`` will allow the user to +enable functionalities not available through the passt backend interface +(like migration). + See `passt(1)`_ for more details on passt. .. _passt: https://passt.top/ .. _passt(1): https://passt.top/builds/latest/web/passt.1.html +To use the passt backend interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is no need to start the daemon as QEMU will do it for you. + +By default, passt will be started in the socket-based mode. + +.. parsed-literal:: + |qemu_system| [...OPTIONS...] -nic passt + + (qemu) info network + e1000e.0: index=0,type=nic,model=e1000e,macaddr=52:54:00:12:34:56 + \ #net071: index=0,type=passt,stream,connected to pid 24846 + +.. parsed-literal:: + |qemu_system| [...OPTIONS...] -net nic -net passt,tcp-ports=10001,udp-ports=10001 + + (qemu) info network + hub 0 + \ hub0port1: #net136: index=0,type=passt,stream,connected to pid 25204 + \ hub0port0: e1000e.0: index=0,type=nic,model=e1000e,macaddr=52:54:00:12:34:56 + +.. parsed-literal:: + |qemu_system| [...OPTIONS...] -netdev passt,id=netdev0 -device virtio-net,mac=9a:2b:2c:2d:2e:2f,id=virtio0,netdev=netdev0 + + (qemu) info network + virtio0: index=0,type=nic,model=virtio-net-pci,macaddr=9a:2b:2c:2d:2e:2f + \ netdev0: index=0,type=passt,stream,connected to pid 25428 + +To use the vhost-based interface, add the ``vhost-user=on`` parameter and +select the virtio-net device: + +.. parsed-literal:: + |qemu_system| [...OPTIONS...] -nic passt,model=virtio,vhost-user=on + + (qemu) info network + virtio-net-pci.0: index=0,type=nic,model=virtio-net-pci,macaddr=52:54:00:12:34:56 + \ #net006: index=0,type=passt,vhost-user,connected to pid 25731 + To use socket based passt interface: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/system/i386/amd-memory-encryption.rst b/docs/system/i386/amd-memory-encryption.rst index 748f509..6c23f35 100644 --- a/docs/system/i386/amd-memory-encryption.rst +++ b/docs/system/i386/amd-memory-encryption.rst @@ -1,3 +1,5 @@ +.. _amd-sev: + AMD Secure Encrypted Virtualization (SEV) ========================================= diff --git a/docs/system/igvm.rst b/docs/system/igvm.rst new file mode 100644 index 0000000..79508d9 --- /dev/null +++ b/docs/system/igvm.rst @@ -0,0 +1,173 @@ +Independent Guest Virtual Machine (IGVM) support +================================================ + +IGVM files are designed to encapsulate all the information required to launch a +virtual machine on any given virtualization stack in a deterministic way. This +allows the cryptographic measurement of initial guest state for Confidential +Guests to be calculated when the IGVM file is built, allowing a relying party to +verify the initial state of a guest via a remote attestation. + +Although IGVM files are designed with Confidential Computing in mind, they can +also be used to configure non-confidential guests. Multiple platforms can be +defined by a single IGVM file, allowing a single IGVM file to configure a +virtual machine that can run on, for example, TDX, SEV and non-confidential +hosts. + +QEMU supports IGVM files through the user-creatable ``igvm-cfg`` object. This +object is used to define the filename of the IGVM file to process. A reference +to the object is added to the ``-machine`` to configure the virtual machine +to use the IGVM file for configuration. + +Confidential platform support is provided through the use of +the ``ConfidentialGuestSupport`` object. If the virtual machine provides an +instance of this object then this is used by the IGVM loader to configure the +isolation properties of the directives within the file. + +Further Information on IGVM +--------------------------- + +Information about the IGVM format, including links to the format specification +and documentation for the Rust and C libraries can be found at the project +repository: + +https://github.com/microsoft/igvm + + +Supported Platforms +------------------- + +Currently, IGVM files can be provided for Confidential Guests on host systems +that support AMD SEV, SEV-ES and SEV-SNP with KVM. IGVM files can also be +provided for non-confidential guests. + + +Limitations when using IGVM with AMD SEV, SEV-ES and SEV-SNP +------------------------------------------------------------ + +IGVM files configure the initial state of the guest using a set of directives. +Not every directive is supported by every Confidential Guest type. For example, +AMD SEV does not support encrypted save state regions, therefore setting the +initial CPU state using IGVM for SEV is not possible. When an IGVM file contains +directives that are not supported for the active platform, an error is generated +and the guest launch is aborted. + +The table below describes the list of directives that are supported for SEV, +SEV-ES, SEV-SNP and non-confidential platforms. + +.. list-table:: SEV, SEV-ES, SEV-SNP & non-confidential Supported Directives + :widths: 35 65 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_PAGE_DATA + - ``NORMAL`` zero, measured and unmeasured page types are supported. Other + page types result in an error. + * - IGVM_VHT_PARAMETER_AREA + - + * - IGVM_VHT_PARAMETER_INSERT + - + * - IGVM_VHT_VP_COUNT_PARAMETER + - The guest parameter page is populated with the CPU count. + * - IGVM_VHT_ENVIRONMENT_INFO_PARAMETER + - The ``memory_is_shared`` parameter is set to 1 in the guest parameter + page. + +.. list-table:: Additional SEV, SEV-ES & SEV_SNP Supported Directives + :widths: 25 75 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_MEMORY_MAP + - The memory map page is populated using entries from the E820 table. + * - IGVM_VHT_REQUIRED_MEMORY + - Ensures memory is available in the guest at the specified range. + +.. list-table:: Additional SEV-ES & SEV-SNP Supported Directives + :widths: 25 75 + :header-rows: 1 + + * - IGVM directive + - Notes + * - IGVM_VHT_VP_CONTEXT + - Setting of the initial CPU state for the boot CPU and additional CPUs is + supported with limitations on the fields that can be provided in the + VMSA. See below for details on which fields are supported. + +Initial CPU state with VMSA +--------------------------- + +The initial state of guest CPUs can be defined in the IGVM file for AMD SEV-ES +and SEV-SNP. The state data is provided as a VMSA structure as defined in Table +B-4 in the AMD64 Architecture Programmer's Manual, Volume 2 [1]. + +The IGVM VMSA is translated to CPU state in QEMU which is then synchronized +by KVM to the guest VMSA during the launch process where it contributes to the +launch measurement. See :ref:`amd-sev` for details on the launch process and +guest launch measurement. + +It is important that no information is lost or changed when translating the +VMSA provided by the IGVM file into the VSMA that is used to launch the guest. +Therefore, QEMU restricts the VMSA fields that can be provided in the IGVM +VMSA structure to the following registers: + +RAX, RCX, RDX, RBX, RBP, RSI, RDI, R8-R15, RSP, RIP, CS, DS, ES, FS, GS, SS, +CR0, CR3, CR4, XCR0, EFER, PAT, GDT, IDT, LDTR, TR, DR6, DR7, RFLAGS, X87_FCW, +MXCSR. + +When processing the IGVM file, QEMU will check if any fields other than the +above are non-zero and generate an error if this is the case. + +KVM uses a hardcoded GPA of 0xFFFFFFFFF000 for the VMSA. When an IGVM file +defines initial CPU state, the GPA for each VMSA must match this hardcoded +value. + +Firmware Images with IGVM +------------------------- + +When an IGVM filename is specified for a Confidential Guest Support object it +overrides the default handling of system firmware: the firmware image, such as +an OVMF binary should be contained as a payload of the IGVM file and not +provided as a flash drive or via the ``-bios`` parameter. The default QEMU +firmware is not automatically populated into the guest memory space. + +If an IGVM file is provided along with either the ``-bios`` parameter or pflash +devices then an error is displayed and the guest startup is aborted. + +Running a guest configured using IGVM +------------------------------------- + +To run a guest configured with IGVM you firstly need to generate an IGVM file +that contains a guest configuration compatible with the platform you are +targeting. + +The ``buildigvm`` tool [2] is an example of a tool that can be used to generate +IGVM files for non-confidential X86 platforms as well as for SEV, SEV-ES and +SEV-SNP confidential platforms. + +Example using this tool to generate an IGVM file for AMD SEV-SNP:: + + buildigvm --firmware /path/to/OVMF.fd --output sev-snp.igvm \ + --cpucount 4 sev-snp + +To run a guest configured with the generated IGVM you need to add an +``igvm-cfg`` object and refer to it from the ``-machine`` parameter: + +Example (for AMD SEV):: + + qemu-system-x86_64 \ + <other parameters> \ + -machine ...,confidential-guest-support=sev0,igvm-cfg=igvm0 \ + -object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \ + -object igvm-cfg,id=igvm0,file=/path/to/sev-snp.igvm + +References +---------- + +[1] AMD64 Architecture Programmer's Manual, Volume 2: System Programming + Rev 3.41 + https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf + +[2] ``buildigvm`` - A tool to build example IGVM files containing OVMF firmware + https://github.com/roy-hopkins/buildigvm
\ No newline at end of file diff --git a/docs/system/index.rst b/docs/system/index.rst index 718e9d3..427b020 100644 --- a/docs/system/index.rst +++ b/docs/system/index.rst @@ -38,5 +38,6 @@ or Hypervisor.Framework. security multi-process confidential-guest-support + igvm vm-templating sriov diff --git a/docs/system/introduction.rst b/docs/system/introduction.rst index 338d374..4cd46b5 100644 --- a/docs/system/introduction.rst +++ b/docs/system/introduction.rst @@ -81,7 +81,7 @@ may not be optimal for modern systems. For a non-x86 system where we emulate a broad range of machine types, the command lines are generally more explicit in defining the machine -and boot behaviour. You will find often find example command lines in +and boot behaviour. You will often find example command lines in the :ref:`system-targets-ref` section of the manual. While the project doesn't want to discourage users from using the diff --git a/docs/system/target-i386.rst b/docs/system/target-i386.rst index 43b09c7..2374391 100644 --- a/docs/system/target-i386.rst +++ b/docs/system/target-i386.rst @@ -37,6 +37,4 @@ OS requirements ~~~~~~~~~~~~~~~ On x86_64 hosts, the default set of CPU features enabled by the KVM -accelerator require the host to be running Linux v4.5 or newer. Red Hat -Enterprise Linux 7 is also supported, since the required -functionality was backported. +accelerator require the host to be running Linux v4.5 or newer. diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst index 3653adb..5e7b850 100644 --- a/docs/tools/qemu-img.rst +++ b/docs/tools/qemu-img.rst @@ -256,7 +256,7 @@ Parameters to snapshot subcommand: .. option:: -l - Lists all snapshots in the given image + Lists all snapshots in the given image (default action) Command description: @@ -419,7 +419,7 @@ Command description: 4 Error on reading data -.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps [--skip-broken-bitmaps]] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-B BACKING_FILE [-F BACKING_FMT]] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME +.. option:: convert [--object OBJECTDEF] [--image-opts] [--target-image-opts] [--target-is-zero] [--bitmaps [--skip-broken-bitmaps]] [-U] [-C] [-c] [-p] [-q] [-n] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-O OUTPUT_FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-o OPTIONS] [-l SNAPSHOT_PARAM] [-S SPARSE_SIZE] [-r RATE_LIMIT] [-m NUM_COROUTINES] [-W] FILENAME [FILENAME2 [...]] OUTPUT_FILENAME Convert the disk image *FILENAME* or a snapshot *SNAPSHOT_PARAM* to disk image *OUTPUT_FILENAME* using format *OUTPUT_FMT*. It can @@ -467,11 +467,11 @@ Command description: ``--skip-broken-bitmaps`` is also specified to copy only the consistent bitmaps. -.. option:: create [--object OBJECTDEF] [-q] [-f FMT] [-b BACKING_FILE [-F BACKING_FMT]] [-u] [-o OPTIONS] FILENAME [SIZE] +.. option:: create [-f FMT] [-o FMT_OPTS] [-b BACKING_FILE [-B BACKING_FMT]] [-u] [-q] [--object OBJDEF] FILE [SIZE] - Create the new disk image *FILENAME* of size *SIZE* and format - *FMT*. Depending on the file format, you can add one or more *OPTIONS* - that enable additional features of this format. + Create the new disk image *FILE* of size *SIZE* and format + *FMT*. Depending on the file format, you can add one or more *FMT_OPTS* + options that enable additional features of this format. If the option *BACKING_FILE* is specified, then the image will record only the differences from *BACKING_FILE*. No size needs to be specified in @@ -479,7 +479,7 @@ Command description: ``commit`` monitor command (or ``qemu-img commit``). If a relative path name is given, the backing file is looked up relative to - the directory containing *FILENAME*. + the directory containing *FILE*. Note that a given backing file will be opened to check that it is valid. Use the ``-u`` option to enable unsafe backing file mode, which means that the @@ -663,11 +663,11 @@ Command description: bitmap support, or 0 if bitmaps are supported but there is nothing to copy. -.. option:: snapshot [--object OBJECTDEF] [--image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME +.. option:: snapshot [--object OBJECTDEF] [-f FMT | --image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME List, apply, create or delete snapshots in image *FILENAME*. -.. option:: rebase [--object OBJECTDEF] [--image-opts] [-U] [-q] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-p] [-u] [-c] -b BACKING_FILE [-F BACKING_FMT] FILENAME +.. option:: rebase [--object OBJECTDEF] [--image-opts] [-U] [-q] [-f FMT] [-t CACHE] [-T SRC_CACHE] [-p] [-u] [-c] -b BACKING_FILE [-B BACKING_FMT] FILENAME Changes the backing file of an image. Only the formats ``qcow2`` and ``qed`` support changing the backing file. diff --git a/gdbstub/system.c b/gdbstub/system.c index 8a32d8e..5be0d3c 100644 --- a/gdbstub/system.c +++ b/gdbstub/system.c @@ -19,7 +19,8 @@ #include "gdbstub/commands.h" #include "exec/hwaddr.h" #include "exec/tb-flush.h" -#include "system/accel-ops.h" +#include "accel/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "system/runstate.h" #include "system/replay.h" diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index d797922..6142f60 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -267,6 +267,18 @@ ERST .cmd = hmp_info_sync_profile, }, + { + .name = "accel", + .args_type = "", + .params = "", + .help = "show accelerator info", + }, + +SRST + ``info accel`` + Show accelerator info. +ERST + SRST ``info sync-profile [-m|-n]`` [*max*] Show synchronization profiling info, up to *max* entries (default: 10), diff --git a/hmp-commands.hx b/hmp-commands.hx index 06746f0..d0e4f35 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -1287,6 +1287,9 @@ ERST .name = "netdev_add", .args_type = "netdev:O", .params = "[user|tap|socket|stream|dgram|vde|bridge|hubport|netmap|vhost-user" +#ifdef CONFIG_PASST + "|passt" +#endif #ifdef CONFIG_AF_XDP "|af-xdp" #endif diff --git a/hw/acpi/acpi-pci-hotplug-stub.c b/hw/acpi/acpi-pci-hotplug-stub.c index b7bc6e4..d58ea72 100644 --- a/hw/acpi/acpi-pci-hotplug-stub.c +++ b/hw/acpi/acpi-pci-hotplug-stub.c @@ -4,7 +4,7 @@ const VMStateDescription vmstate_acpi_pcihp_pci_status; -void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus, +void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, MemoryRegion *address_space_io, uint16_t io_base) { } diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c index f8f93a9..1e685f9 100644 --- a/hw/acpi/aml-build.c +++ b/hw/acpi/aml-build.c @@ -160,7 +160,7 @@ void crs_replace_with_free_ranges(GPtrArray *ranges, */ static void crs_range_merge(GPtrArray *range) { - GPtrArray *tmp = g_ptr_array_new_with_free_func(crs_range_free); + g_autoptr(GPtrArray) tmp = g_ptr_array_new_with_free_func(crs_range_free); CrsRangeEntry *entry; uint64_t range_base, range_limit; int i; @@ -191,7 +191,6 @@ static void crs_range_merge(GPtrArray *range) entry = g_ptr_array_index(tmp, i); crs_range_insert(range, entry->base, entry->limit); } - g_ptr_array_free(tmp, true); } static void @@ -2153,6 +2152,7 @@ void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms, int64_t socket_id = -1, cluster_id = -1, core_id = -1; uint32_t socket_offset = 0, cluster_offset = 0, core_offset = 0; uint32_t pptt_start = table_data->len; + uint32_t root_offset; int n; AcpiTable table = { .sig = "PPTT", .rev = 2, .oem_id = oem_id, .oem_table_id = oem_table_id }; @@ -2160,6 +2160,18 @@ void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms, acpi_table_begin(&table, table_data); /* + * Build a root node for all the processor nodes. Otherwise when + * building a multi-socket system each socket tree is separated + * and will be hard for the OS like Linux to know whether the + * system is homogeneous. + */ + root_offset = table_data->len - pptt_start; + build_processor_hierarchy_node(table_data, + (1 << 0) | /* Physical package */ + (1 << 4), /* Identical Implementation */ + 0, 0, NULL, 0); + + /* * This works with the assumption that cpus[n].props.*_id has been * sorted from top to down levels in mc->possible_cpu_arch_ids(). * Otherwise, the unexpected and duplicated containers will be @@ -2173,8 +2185,9 @@ void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms, core_id = -1; socket_offset = table_data->len - pptt_start; build_processor_hierarchy_node(table_data, - (1 << 0), /* Physical package */ - 0, socket_id, NULL, 0); + (1 << 0) | /* Physical package */ + (1 << 4), /* Identical Implementation */ + root_offset, socket_id, NULL, 0); } if (mc->smp_props.clusters_supported && mc->smp_props.has_clusters) { @@ -2184,7 +2197,8 @@ void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms, core_id = -1; cluster_offset = table_data->len - pptt_start; build_processor_hierarchy_node(table_data, - (0 << 0), /* Not a physical package */ + (0 << 0) | /* Not a physical package */ + (1 << 4), /* Identical Implementation */ socket_offset, cluster_id, NULL, 0); } } else { @@ -2202,7 +2216,8 @@ void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms, core_id = cpus->cpus[n].props.core_id; core_offset = table_data->len - pptt_start; build_processor_hierarchy_node(table_data, - (0 << 0), /* Not a physical package */ + (0 << 0) | /* Not a physical package */ + (1 << 4), /* Identical Implementation */ cluster_offset, core_id, NULL, 0); } diff --git a/hw/acpi/bios-linker-loader.c b/hw/acpi/bios-linker-loader.c index 1080618..c9ffe44 100644 --- a/hw/acpi/bios-linker-loader.c +++ b/hw/acpi/bios-linker-loader.c @@ -22,8 +22,6 @@ #include "hw/acpi/bios-linker-loader.h" #include "hw/nvram/fw_cfg.h" -#include "qemu/bswap.h" - /* * Linker/loader is a paravirtualized interface that passes commands to guest. * The commands can be used to request guest to diff --git a/hw/acpi/generic_event_device.c b/hw/acpi/generic_event_device.c index 7a62f8d..95682b7 100644 --- a/hw/acpi/generic_event_device.c +++ b/hw/acpi/generic_event_device.c @@ -12,10 +12,13 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "hw/acpi/acpi.h" +#include "hw/acpi/pcihp.h" #include "hw/acpi/generic_event_device.h" +#include "hw/pci/pci.h" #include "hw/irq.h" #include "hw/mem/pc-dimm.h" #include "hw/mem/nvdimm.h" +#include "hw/pci/pci_device.h" #include "hw/qdev-properties.h" #include "migration/vmstate.h" #include "qemu/error-report.h" @@ -26,6 +29,7 @@ static const uint32_t ged_supported_events[] = { ACPI_GED_PWR_DOWN_EVT, ACPI_GED_NVDIMM_HOTPLUG_EVT, ACPI_GED_CPU_HOTPLUG_EVT, + ACPI_GED_PCI_HOTPLUG_EVT, }; /* @@ -121,6 +125,12 @@ void build_ged_aml(Aml *table, const char *name, HotplugHandler *hotplug_dev, aml_notify(aml_name("\\_SB.NVDR"), aml_int(0x80))); break; + case ACPI_GED_PCI_HOTPLUG_EVT: + aml_append(if_ctx, + aml_acquire(aml_name("\\_SB.PCI0.BLCK"), 0xFFFF)); + aml_append(if_ctx, aml_call0("\\_SB.PCI0.PCNT")); + aml_append(if_ctx, aml_release(aml_name("\\_SB.PCI0.BLCK"))); + break; default: /* * Please make sure all the events in ged_supported_events[] @@ -227,6 +237,14 @@ static const MemoryRegionOps ged_regs_ops = { }, }; +static void acpi_ged_device_pre_plug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { + acpi_pcihp_device_pre_plug_cb(hotplug_dev, dev, errp); + } +} + static void acpi_ged_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { @@ -240,6 +258,8 @@ static void acpi_ged_device_plug_cb(HotplugHandler *hotplug_dev, } } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { acpi_cpu_plug_cb(hotplug_dev, &s->cpuhp_state, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { + acpi_pcihp_device_plug_cb(hotplug_dev, &s->pcihp_state, dev, errp); } else { error_setg(errp, "virt: device plug request for unsupported device" " type: %s", object_get_typename(OBJECT(dev))); @@ -256,6 +276,9 @@ static void acpi_ged_unplug_request_cb(HotplugHandler *hotplug_dev, acpi_memory_unplug_request_cb(hotplug_dev, &s->memhp_state, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { acpi_cpu_unplug_request_cb(hotplug_dev, &s->cpuhp_state, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { + acpi_pcihp_device_unplug_request_cb(hotplug_dev, &s->pcihp_state, + dev, errp); } else { error_setg(errp, "acpi: device unplug request for unsupported device" " type: %s", object_get_typename(OBJECT(dev))); @@ -271,6 +294,8 @@ static void acpi_ged_unplug_cb(HotplugHandler *hotplug_dev, acpi_memory_unplug_cb(&s->memhp_state, dev, errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { acpi_cpu_unplug_cb(&s->cpuhp_state, dev, errp); + } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { + acpi_pcihp_device_unplug_cb(hotplug_dev, &s->pcihp_state, dev, errp); } else { error_setg(errp, "acpi: device unplug for unsupported device" " type: %s", object_get_typename(OBJECT(dev))); @@ -299,6 +324,8 @@ static void acpi_ged_send_event(AcpiDeviceIf *adev, AcpiEventStatusBits ev) sel = ACPI_GED_NVDIMM_HOTPLUG_EVT; } else if (ev & ACPI_CPU_HOTPLUG_STATUS) { sel = ACPI_GED_CPU_HOTPLUG_EVT; + } else if (ev & ACPI_PCI_HOTPLUG_STATUS) { + sel = ACPI_GED_PCI_HOTPLUG_EVT; } else { /* Unknown event. Return without generating interrupt. */ warn_report("GED: Unsupported event %d. No irq injected", ev); @@ -318,6 +345,10 @@ static void acpi_ged_send_event(AcpiDeviceIf *adev, AcpiEventStatusBits ev) static const Property acpi_ged_properties[] = { DEFINE_PROP_UINT32("ged-event", AcpiGedState, ged_event_bitmap, 0), + DEFINE_PROP_BOOL(ACPI_PM_PROP_ACPI_PCIHP_BRIDGE, AcpiGedState, + pcihp_state.use_acpi_hotplug_bridge, 0), + DEFINE_PROP_LINK("bus", AcpiGedState, pcihp_state.root, + TYPE_PCI_BUS, PCIBus *), }; static const VMStateDescription vmstate_memhp_state = { @@ -386,6 +417,25 @@ static const VMStateDescription vmstate_ghes_state = { } }; +static bool pcihp_needed(void *opaque) +{ + AcpiGedState *s = opaque; + return s->pcihp_state.use_acpi_hotplug_bridge; +} + +static const VMStateDescription vmstate_pcihp_state = { + .name = "acpi-ged/pcihp", + .version_id = 1, + .minimum_version_id = 1, + .needed = pcihp_needed, + .fields = (const VMStateField[]) { + VMSTATE_PCI_HOTPLUG(pcihp_state, + AcpiGedState, + NULL, NULL), + VMSTATE_END_OF_LIST() + } +}; + static const VMStateDescription vmstate_acpi_ged = { .name = "acpi-ged", .version_id = 1, @@ -398,6 +448,7 @@ static const VMStateDescription vmstate_acpi_ged = { &vmstate_memhp_state, &vmstate_cpuhp_state, &vmstate_ghes_state, + &vmstate_pcihp_state, NULL } }; @@ -406,9 +457,13 @@ static void acpi_ged_realize(DeviceState *dev, Error **errp) { SysBusDevice *sbd = SYS_BUS_DEVICE(dev); AcpiGedState *s = ACPI_GED(dev); + AcpiPciHpState *pcihp_state = &s->pcihp_state; uint32_t ged_events; int i; + if (pcihp_state->use_acpi_hotplug_bridge) { + s->ged_event_bitmap |= ACPI_GED_PCI_HOTPLUG_EVT; + } ged_events = ctpop32(s->ged_event_bitmap); for (i = 0; i < ARRAY_SIZE(ged_supported_events) && ged_events; i++) { @@ -428,6 +483,13 @@ static void acpi_ged_realize(DeviceState *dev, Error **errp) cpu_hotplug_hw_init(&s->container_cpuhp, OBJECT(dev), &s->cpuhp_state, 0); break; + case ACPI_GED_PCI_HOTPLUG_EVT: + memory_region_init(&s->container_pcihp, OBJECT(dev), + ACPI_PCIHP_REGION_NAME, ACPI_PCIHP_SIZE); + sysbus_init_mmio(sbd, &s->container_pcihp); + acpi_pcihp_init(OBJECT(s), &s->pcihp_state, + &s->container_pcihp, 0); + qbus_set_hotplug_handler(BUS(s->pcihp_state.root), OBJECT(dev)); } ged_events--; } @@ -469,20 +531,34 @@ static void acpi_ged_initfn(Object *obj) sysbus_init_mmio(sbd, &ged_st->regs); } +static void ged_reset_hold(Object *obj, ResetType type) +{ + AcpiGedState *s = ACPI_GED(obj); + + if (s->pcihp_state.use_acpi_hotplug_bridge) { + acpi_pcihp_reset(&s->pcihp_state); + } +} + static void acpi_ged_class_init(ObjectClass *class, const void *data) { DeviceClass *dc = DEVICE_CLASS(class); HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(class); AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(class); + ResettableClass *rc = RESETTABLE_CLASS(class); + AcpiGedClass *gedc = ACPI_GED_CLASS(class); dc->desc = "ACPI Generic Event Device"; device_class_set_props(dc, acpi_ged_properties); dc->vmsd = &vmstate_acpi_ged; dc->realize = acpi_ged_realize; + hc->pre_plug = acpi_ged_device_pre_plug_cb; hc->plug = acpi_ged_device_plug_cb; hc->unplug_request = acpi_ged_unplug_request_cb; hc->unplug = acpi_ged_unplug_cb; + resettable_class_set_parent_phases(rc, NULL, ged_reset_hold, NULL, + &gedc->parent_phases); adevc->ospm_status = acpi_ged_ospm_status; adevc->send_event = acpi_ged_send_event; @@ -494,6 +570,7 @@ static const TypeInfo acpi_ged_info = { .instance_size = sizeof(AcpiGedState), .instance_init = acpi_ged_initfn, .class_init = acpi_ged_class_init, + .class_size = sizeof(AcpiGedClass), .interfaces = (const InterfaceInfo[]) { { TYPE_HOTPLUG_HANDLER }, { TYPE_ACPI_DEVICE_IF }, diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c index 967b674..2b3b493 100644 --- a/hw/acpi/ich9.c +++ b/hw/acpi/ich9.c @@ -322,9 +322,10 @@ void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm, qemu_irq sci_irq) } if (pm->acpi_pci_hotplug.use_acpi_hotplug_bridge) { + object_property_set_link(OBJECT(lpc_pci), "bus", + OBJECT(pci_get_bus(lpc_pci)), &error_abort); acpi_pcihp_init(OBJECT(lpc_pci), &pm->acpi_pci_hotplug, - pci_get_bus(lpc_pci), pci_address_space_io(lpc_pci), ACPI_PCIHP_ADDR_ICH9); @@ -428,6 +429,10 @@ void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm) object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE, &pm->pm_io_base, OBJ_PROP_FLAG_READ); + object_property_add_link(obj, "bus", TYPE_PCI_BUS, + (Object **)&pm->acpi_pci_hotplug.root, + object_property_allow_set_link, + OBJ_PROP_LINK_STRONG); object_property_add(obj, ACPI_PM_PROP_GPE0_BLK, "uint32", ich9_pm_get_gpe0_blk, NULL, NULL, pm); diff --git a/hw/acpi/pci-bridge.c b/hw/acpi/pci-bridge.c index 7baa703..394a919 100644 --- a/hw/acpi/pci-bridge.c +++ b/hw/acpi/pci-bridge.c @@ -35,3 +35,57 @@ void build_pci_bridge_aml(AcpiDevAmlIf *adev, Aml *scope) } } } + +Aml *build_pci_bridge_edsm(void) +{ + Aml *method, *ifctx; + Aml *zero = aml_int(0); + Aml *func = aml_arg(2); + Aml *ret = aml_local(0); + Aml *aidx = aml_local(1); + Aml *params = aml_arg(4); + + method = aml_method("EDSM", 5, AML_SERIALIZED); + + /* get supported functions */ + ifctx = aml_if(aml_equal(func, zero)); + { + /* 1: have supported functions */ + /* 7: support for function 7 */ + const uint8_t caps = 1 | BIT(7); + build_append_pci_dsm_func0_common(ifctx, ret); + aml_append(ifctx, aml_store(aml_int(caps), aml_index(ret, zero))); + aml_append(ifctx, aml_return(ret)); + } + aml_append(method, ifctx); + + /* handle specific functions requests */ + /* + * PCI Firmware Specification 3.1 + * 4.6.7. _DSM for Naming a PCI or PCI Express Device Under + * Operating Systems + */ + ifctx = aml_if(aml_equal(func, aml_int(7))); + { + Aml *pkg = aml_package(2); + aml_append(pkg, zero); + /* optional, if not impl. should return null string */ + aml_append(pkg, aml_string("%s", "")); + aml_append(ifctx, aml_store(pkg, ret)); + + /* + * IASL is fine when initializing Package with computational data, + * however it makes guest unhappy /it fails to process such AML/. + * So use runtime assignment to set acpi-index after initializer + * to make OSPM happy. + */ + aml_append(ifctx, + aml_store(aml_derefof(aml_index(params, aml_int(0))), aidx)); + aml_append(ifctx, aml_store(aidx, aml_index(ret, zero))); + aml_append(ifctx, aml_return(ret)); + } + aml_append(method, ifctx); + + return method; +} + diff --git a/hw/acpi/pci.c b/hw/acpi/pci.c index d511a85..2228f12 100644 --- a/hw/acpi/pci.c +++ b/hw/acpi/pci.c @@ -301,3 +301,53 @@ void build_srat_generic_affinity_structures(GArray *table_data) object_child_foreach_recursive(object_get_root(), build_acpi_generic_port, table_data); } + +Aml *build_pci_host_bridge_osc_method(bool enable_native_pcie_hotplug) +{ + Aml *if_ctx; + Aml *if_ctx2; + Aml *else_ctx; + Aml *method; + Aml *a_cwd1 = aml_name("CDW1"); + Aml *a_ctrl = aml_local(0); + + method = aml_method("_OSC", 4, AML_NOTSERIALIZED); + aml_append(method, aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1")); + + if_ctx = aml_if(aml_equal( + aml_arg(0), aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766"))); + aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2")); + aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3")); + + aml_append(if_ctx, aml_store(aml_name("CDW3"), a_ctrl)); + + /* + * Always allow native PME, AER (no dependencies) + * Allow SHPC (PCI bridges can have SHPC controller) + * Disable PCIe Native Hot-plug if ACPI PCI Hot-plug is enabled. + */ + aml_append(if_ctx, aml_and(a_ctrl, + aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl)); + + if_ctx2 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(1)))); + /* Unknown revision */ + aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x08), a_cwd1)); + aml_append(if_ctx, if_ctx2); + + if_ctx2 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), a_ctrl))); + /* Capabilities bits were masked */ + aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x10), a_cwd1)); + aml_append(if_ctx, if_ctx2); + + /* Update DWORD3 in the buffer */ + aml_append(if_ctx, aml_store(a_ctrl, aml_name("CDW3"))); + aml_append(method, if_ctx); + + else_ctx = aml_else(); + /* Unrecognized UUID */ + aml_append(else_ctx, aml_or(a_cwd1, aml_int(4), a_cwd1)); + aml_append(method, else_ctx); + + aml_append(method, aml_return(aml_arg(3))); + return method; +} diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c index 497281a..4922bbc 100644 --- a/hw/acpi/pcihp.c +++ b/hw/acpi/pcihp.c @@ -26,7 +26,8 @@ #include "qemu/osdep.h" #include "hw/acpi/pcihp.h" - +#include "hw/acpi/aml-build.h" +#include "hw/acpi/acpi_aml_interface.h" #include "hw/pci-host/i440fx.h" #include "hw/pci/pci.h" #include "hw/pci/pci_bridge.h" @@ -39,9 +40,9 @@ #include "migration/vmstate.h" #include "qapi/error.h" #include "qom/qom-qobject.h" +#include "qobject/qnum.h" #include "trace.h" -#define ACPI_PCIHP_SIZE 0x0018 #define PCI_UP_BASE 0x0000 #define PCI_DOWN_BASE 0x0004 #define PCI_EJ_BASE 0x0008 @@ -97,10 +98,10 @@ static void *acpi_set_bsel(PCIBus *bus, void *opaque) return info; } -static void acpi_set_pci_info(bool has_bridge_hotplug) +static void acpi_set_pci_info(AcpiPciHpState *s) { static bool bsel_is_set; - Object *host = acpi_get_i386_pci_host(); + bool has_bridge_hotplug = s->use_acpi_hotplug_bridge; PCIBus *bus; BSELInfo info = { .bsel_alloc = ACPI_PCIHP_BSEL_DEFAULT, .has_bridge_hotplug = has_bridge_hotplug }; @@ -110,11 +111,8 @@ static void acpi_set_pci_info(bool has_bridge_hotplug) } bsel_is_set = true; - if (!host) { - return; - } - bus = PCI_HOST_BRIDGE(host)->bus; + bus = s->root; if (bus) { /* Scan all PCI buses. Set property to enable acpi based hotplug. */ pci_for_each_bus_depth_first(bus, acpi_set_bsel, NULL, &info); @@ -264,7 +262,7 @@ static void acpi_pcihp_update(AcpiPciHpState *s) void acpi_pcihp_reset(AcpiPciHpState *s) { - acpi_set_pci_info(s->use_acpi_hotplug_bridge); + acpi_set_pci_info(s); acpi_pcihp_update(s); } @@ -495,13 +493,13 @@ static const MemoryRegionOps acpi_pcihp_io_ops = { }, }; -void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus, +void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, MemoryRegion *io, uint16_t io_base) { s->io_len = ACPI_PCIHP_SIZE; s->io_base = io_base; - s->root = root_bus; + assert(s->root); memory_region_init_io(&s->io, owner, &acpi_pcihp_io_ops, s, "acpi-pci-hotplug", s->io_len); @@ -513,6 +511,425 @@ void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus, OBJ_PROP_FLAG_READ); } +void build_append_pci_dsm_func0_common(Aml *ctx, Aml *retvar) +{ + Aml *UUID, *ifctx1; + uint8_t byte_list[1] = { 0 }; /* nothing supported yet */ + + aml_append(ctx, aml_store(aml_buffer(1, byte_list), retvar)); + /* + * PCI Firmware Specification 3.1 + * 4.6. _DSM Definitions for PCI + */ + UUID = aml_touuid("E5C937D0-3553-4D7A-9117-EA4D19C3434D"); + ifctx1 = aml_if(aml_lnot(aml_equal(aml_arg(0), UUID))); + { + /* call is for unsupported UUID, bail out */ + aml_append(ifctx1, aml_return(retvar)); + } + aml_append(ctx, ifctx1); + + ifctx1 = aml_if(aml_lless(aml_arg(1), aml_int(2))); + { + /* call is for unsupported REV, bail out */ + aml_append(ifctx1, aml_return(retvar)); + } + aml_append(ctx, ifctx1); +} + +static Aml *aml_pci_pdsm(void) +{ + Aml *method, *ifctx, *ifctx1; + Aml *ret = aml_local(0); + Aml *caps = aml_local(1); + Aml *acpi_index = aml_local(2); + Aml *zero = aml_int(0); + Aml *one = aml_int(1); + Aml *not_supp = aml_int(0xFFFFFFFF); + Aml *func = aml_arg(2); + Aml *params = aml_arg(4); + Aml *bnum = aml_derefof(aml_index(params, aml_int(0))); + Aml *sunum = aml_derefof(aml_index(params, aml_int(1))); + + method = aml_method("PDSM", 5, AML_SERIALIZED); + + /* get supported functions */ + ifctx = aml_if(aml_equal(func, zero)); + { + build_append_pci_dsm_func0_common(ifctx, ret); + + aml_append(ifctx, aml_store(zero, caps)); + aml_append(ifctx, + aml_store(aml_call2("AIDX", bnum, sunum), acpi_index)); + /* + * advertise function 7 if device has acpi-index + * acpi_index values: + * 0: not present (default value) + * FFFFFFFF: not supported (old QEMU without PIDX reg) + * other: device's acpi-index + */ + ifctx1 = aml_if(aml_lnot( + aml_or(aml_equal(acpi_index, zero), + aml_equal(acpi_index, not_supp), NULL) + )); + { + /* have supported functions */ + aml_append(ifctx1, aml_or(caps, one, caps)); + /* support for function 7 */ + aml_append(ifctx1, + aml_or(caps, aml_shiftleft(one, aml_int(7)), caps)); + } + aml_append(ifctx, ifctx1); + + aml_append(ifctx, aml_store(caps, aml_index(ret, zero))); + aml_append(ifctx, aml_return(ret)); + } + aml_append(method, ifctx); + + /* handle specific functions requests */ + /* + * PCI Firmware Specification 3.1 + * 4.6.7. _DSM for Naming a PCI or PCI Express Device Under + * Operating Systems + */ + ifctx = aml_if(aml_equal(func, aml_int(7))); + { + Aml *pkg = aml_package(2); + + aml_append(ifctx, aml_store(aml_call2("AIDX", bnum, sunum), acpi_index)); + aml_append(ifctx, aml_store(pkg, ret)); + /* + * Windows calls func=7 without checking if it's available, + * as workaround Microsoft has suggested to return invalid for func7 + * Package, so return 2 elements package but only initialize elements + * when acpi_index is supported and leave them uninitialized, which + * leads elements to being Uninitialized ObjectType and should trip + * Windows into discarding result as an unexpected and prevent setting + * bogus 'PCI Label' on the device. + */ + ifctx1 = aml_if(aml_lnot(aml_lor( + aml_equal(acpi_index, zero), aml_equal(acpi_index, not_supp) + ))); + { + aml_append(ifctx1, aml_store(acpi_index, aml_index(ret, zero))); + /* + * optional, if not impl. should return null string + */ + aml_append(ifctx1, aml_store(aml_string("%s", ""), + aml_index(ret, one))); + } + aml_append(ifctx, ifctx1); + + aml_append(ifctx, aml_return(ret)); + } + + aml_append(method, ifctx); + return method; +} + +void build_acpi_pci_hotplug(Aml *table, AmlRegionSpace rs, uint64_t pcihp_addr) +{ + Aml *scope; + Aml *field; + Aml *method; + + scope = aml_scope("_SB.PCI0"); + + aml_append(scope, + aml_operation_region("PCST", rs, aml_int(pcihp_addr), 0x08)); + field = aml_field("PCST", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); + aml_append(field, aml_named_field("PCIU", 32)); + aml_append(field, aml_named_field("PCID", 32)); + aml_append(scope, field); + + aml_append(scope, + aml_operation_region("SEJ", rs, + aml_int(pcihp_addr + ACPI_PCIHP_SEJ_BASE), 0x04)); + field = aml_field("SEJ", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); + aml_append(field, aml_named_field("B0EJ", 32)); + aml_append(scope, field); + + aml_append(scope, + aml_operation_region("BNMR", rs, + aml_int(pcihp_addr + ACPI_PCIHP_BNMR_BASE), 0x08)); + field = aml_field("BNMR", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); + aml_append(field, aml_named_field("BNUM", 32)); + aml_append(field, aml_named_field("PIDX", 32)); + aml_append(scope, field); + + aml_append(scope, aml_mutex("BLCK", 0)); + + method = aml_method("PCEJ", 2, AML_NOTSERIALIZED); + aml_append(method, aml_acquire(aml_name("BLCK"), 0xFFFF)); + aml_append(method, aml_store(aml_arg(0), aml_name("BNUM"))); + aml_append(method, + aml_store(aml_shiftleft(aml_int(1), aml_arg(1)), aml_name("B0EJ"))); + aml_append(method, aml_release(aml_name("BLCK"))); + aml_append(method, aml_return(aml_int(0))); + aml_append(scope, method); + + method = aml_method("AIDX", 2, AML_NOTSERIALIZED); + aml_append(method, aml_acquire(aml_name("BLCK"), 0xFFFF)); + aml_append(method, aml_store(aml_arg(0), aml_name("BNUM"))); + aml_append(method, + aml_store(aml_shiftleft(aml_int(1), aml_arg(1)), aml_name("PIDX"))); + aml_append(method, aml_store(aml_name("PIDX"), aml_local(0))); + aml_append(method, aml_release(aml_name("BLCK"))); + aml_append(method, aml_return(aml_local(0))); + aml_append(scope, method); + + aml_append(scope, aml_pci_pdsm()); + + aml_append(table, scope); +} + +/* Reserve PCIHP resources */ +void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, + uint64_t io_addr, uint64_t io_len) +{ + Aml *dev, *crs; + + dev = aml_device("PHPR"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06"))); + aml_append(dev, + aml_name_decl("_UID", aml_string("PCI Hotplug resources"))); + /* device present, functioning, decoding, not shown in UI */ + aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); + crs = aml_resource_template(); + aml_append(crs, aml_io(AML_DECODE16, io_addr, io_addr, 1, io_len)); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); +} + +bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus) +{ + Aml *method; + PCIBus *sec; + QObject *bsel; + int nr_notifiers = 0; + GQueue *pcnt_bus_list = g_queue_new(); + + QLIST_FOREACH(sec, &bus->child, sibling) { + Aml *br_scope = aml_scope("S%.02X", sec->parent_dev->devfn); + if (pci_bus_is_root(sec)) { + continue; + } + nr_notifiers = nr_notifiers + + build_append_notification_callback(br_scope, sec); + /* + * add new child scope to parent + * and keep track of bus that have PCNT, + * bus list is used later to call children PCNTs from this level PCNT + */ + if (nr_notifiers) { + g_queue_push_tail(pcnt_bus_list, sec); + aml_append(parent_scope, br_scope); + } + } + + /* + * Append PCNT method to notify about events on local and child buses. + * ps: hostbridge might not have hotplug (bsel) enabled but might have + * child bridges that do have bsel. + */ + method = aml_method("PCNT", 0, AML_NOTSERIALIZED); + + /* If bus supports hotplug select it and notify about local events */ + bsel = object_property_get_qobject(OBJECT(bus), ACPI_PCIHP_PROP_BSEL, NULL); + if (bsel) { + uint64_t bsel_val = qnum_get_uint(qobject_to(QNum, bsel)); + + aml_append(method, aml_store(aml_int(bsel_val), aml_name("BNUM"))); + aml_append(method, aml_call2("DVNT", aml_name("PCIU"), + aml_int(1))); /* Device Check */ + aml_append(method, aml_call2("DVNT", aml_name("PCID"), + aml_int(3))); /* Eject Request */ + nr_notifiers++; + } + + /* Notify about child bus events in any case */ + while ((sec = g_queue_pop_head(pcnt_bus_list))) { + aml_append(method, aml_name("^S%.02X.PCNT", sec->parent_dev->devfn)); + } + + aml_append(parent_scope, method); + qobject_unref(bsel); + g_queue_free(pcnt_bus_list); + return !!nr_notifiers; +} + +static Aml *aml_pci_device_dsm(void) +{ + Aml *method; + + method = aml_method("_DSM", 4, AML_SERIALIZED); + { + Aml *params = aml_local(0); + Aml *pkg = aml_package(2); + aml_append(pkg, aml_int(0)); + aml_append(pkg, aml_int(0)); + aml_append(method, aml_store(pkg, params)); + aml_append(method, + aml_store(aml_name("BSEL"), aml_index(params, aml_int(0)))); + aml_append(method, + aml_store(aml_name("ASUN"), aml_index(params, aml_int(1)))); + aml_append(method, + aml_return(aml_call5("PDSM", aml_arg(0), aml_arg(1), + aml_arg(2), aml_arg(3), params)) + ); + } + return method; +} + +static Aml *aml_pci_static_endpoint_dsm(PCIDevice *pdev) +{ + Aml *method; + + g_assert(pdev->acpi_index != 0); + method = aml_method("_DSM", 4, AML_SERIALIZED); + { + Aml *params = aml_local(0); + Aml *pkg = aml_package(1); + aml_append(pkg, aml_int(pdev->acpi_index)); + aml_append(method, aml_store(pkg, params)); + aml_append(method, + aml_return(aml_call5("EDSM", aml_arg(0), aml_arg(1), + aml_arg(2), aml_arg(3), params)) + ); + } + return method; +} + +static void build_append_pcihp_notify_entry(Aml *method, int slot) +{ + Aml *if_ctx; + int32_t devfn = PCI_DEVFN(slot, 0); + + if_ctx = aml_if(aml_and(aml_arg(0), aml_int(0x1U << slot), NULL)); + aml_append(if_ctx, aml_notify(aml_name("S%.02X", devfn), aml_arg(1))); + aml_append(method, if_ctx); +} + +static bool is_devfn_ignored_generic(const int devfn, const PCIBus *bus) +{ + const PCIDevice *pdev = bus->devices[devfn]; + + if (PCI_FUNC(devfn)) { + if (IS_PCI_BRIDGE(pdev)) { + /* + * Ignore only hotplugged PCI bridges on !0 functions, but + * allow describing cold plugged bridges on all functions + */ + if (DEVICE(pdev)->hotplugged) { + return true; + } + } + } + return false; +} + +static bool is_devfn_ignored_hotplug(const int devfn, const PCIBus *bus) +{ + PCIDevice *pdev = bus->devices[devfn]; + if (pdev) { + return is_devfn_ignored_generic(devfn, bus) || + !DEVICE_GET_CLASS(pdev)->hotpluggable || + /* Cold plugged bridges aren't themselves hot-pluggable */ + (IS_PCI_BRIDGE(pdev) && !DEVICE(pdev)->hotplugged); + } else { /* non populated slots */ + /* + * hotplug is supported only for non-multifunction device + * so generate device description only for function 0 + */ + if (PCI_FUNC(devfn) || + (pci_bus_is_express(bus) && PCI_SLOT(devfn) > 0)) { + return true; + } + } + return false; +} + +void build_append_pcihp_slots(Aml *parent_scope, PCIBus *bus) +{ + int devfn; + Aml *dev, *notify_method = NULL, *method; + QObject *bsel = object_property_get_qobject(OBJECT(bus), + ACPI_PCIHP_PROP_BSEL, NULL); + uint64_t bsel_val = qnum_get_uint(qobject_to(QNum, bsel)); + qobject_unref(bsel); + + aml_append(parent_scope, aml_name_decl("BSEL", aml_int(bsel_val))); + notify_method = aml_method("DVNT", 2, AML_NOTSERIALIZED); + + for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { + int slot = PCI_SLOT(devfn); + int adr = slot << 16 | PCI_FUNC(devfn); + + if (is_devfn_ignored_hotplug(devfn, bus)) { + continue; + } + + if (bus->devices[devfn]) { + dev = aml_scope("S%.02X", devfn); + } else { + dev = aml_device("S%.02X", devfn); + aml_append(dev, aml_name_decl("_ADR", aml_int(adr))); + } + + /* + * Can't declare _SUN here for every device as it changes 'slot' + * enumeration order in linux kernel, so use another variable for it + */ + aml_append(dev, aml_name_decl("ASUN", aml_int(slot))); + aml_append(dev, aml_pci_device_dsm()); + + aml_append(dev, aml_name_decl("_SUN", aml_int(slot))); + /* add _EJ0 to make slot hotpluggable */ + method = aml_method("_EJ0", 1, AML_NOTSERIALIZED); + aml_append(method, + aml_call2("PCEJ", aml_name("BSEL"), aml_name("_SUN")) + ); + aml_append(dev, method); + + build_append_pcihp_notify_entry(notify_method, slot); + + /* device descriptor has been composed, add it into parent context */ + aml_append(parent_scope, dev); + } + aml_append(parent_scope, notify_method); +} + +void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) +{ + int devfn; + Aml *dev; + + for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { + /* ACPI spec: 1.0b: Table 6-2 _ADR Object Bus Types, PCI type */ + int adr = PCI_SLOT(devfn) << 16 | PCI_FUNC(devfn); + PCIDevice *pdev = bus->devices[devfn]; + + if (!pdev || is_devfn_ignored_generic(devfn, bus)) { + continue; + } + + /* start to compose PCI device descriptor */ + dev = aml_device("S%.02X", devfn); + aml_append(dev, aml_name_decl("_ADR", aml_int(adr))); + + call_dev_aml_func(DEVICE(bus->devices[devfn]), dev); + /* add _DSM if device has acpi-index set */ + if (pdev->acpi_index && + !object_property_get_bool(OBJECT(pdev), "hotpluggable", + &error_abort)) { + aml_append(dev, aml_pci_static_endpoint_dsm(pdev)); + } + + /* device descriptor has been composed, add it into parent context */ + aml_append(parent_scope, dev); + } +} + const VMStateDescription vmstate_acpi_pcihp_pci_status = { .name = "acpi_pcihp_pci_status", .version_id = 1, diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c index d98b80d..7a18f18 100644 --- a/hw/acpi/piix4.c +++ b/hw/acpi/piix4.c @@ -567,7 +567,8 @@ static void piix4_acpi_system_hot_add_init(MemoryRegion *parent, if (s->acpi_pci_hotplug.use_acpi_hotplug_bridge || s->acpi_pci_hotplug.use_acpi_root_pci_hotplug) { - acpi_pcihp_init(OBJECT(s), &s->acpi_pci_hotplug, bus, parent, + object_property_set_link(OBJECT(s), "bus", OBJECT(bus), &error_abort); + acpi_pcihp_init(OBJECT(s), &s->acpi_pci_hotplug, parent, ACPI_PCIHP_ADDR_PIIX4); qbus_set_hotplug_handler(BUS(pci_get_bus(PCI_DEVICE(s))), OBJECT(s)); } @@ -611,6 +612,8 @@ static const Property piix4_pm_properties[] = { acpi_pci_hotplug.use_acpi_hotplug_bridge, true), DEFINE_PROP_BOOL(ACPI_PM_PROP_ACPI_PCI_ROOTHP, PIIX4PMState, acpi_pci_hotplug.use_acpi_root_pci_hotplug, true), + DEFINE_PROP_LINK("bus", PIIX4PMState, acpi_pci_hotplug.root, + TYPE_PCI_BUS, PCIBus *), DEFINE_PROP_BOOL("memory-hotplug-support", PIIX4PMState, acpi_memory_hotplug.is_enabled, true), DEFINE_PROP_BOOL("smm-compat", PIIX4PMState, smm_compat, false), diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 1634e26..2aa4b5d 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -34,6 +34,8 @@ config ARM_VIRT select ACPI_HW_REDUCED select ACPI_APEI select ACPI_VIOT + select ACPI_PCIHP + select ACPI_PCI_BRIDGE select VIRTIO_MEM_SUPPORTED select ACPI_CXL select ACPI_HMAT diff --git a/hw/arm/allwinner-r40.c b/hw/arm/allwinner-r40.c index 0bf7008..c8eda39 100644 --- a/hw/arm/allwinner-r40.c +++ b/hw/arm/allwinner-r40.c @@ -20,7 +20,6 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/error-report.h" -#include "qemu/bswap.h" #include "qemu/module.h" #include "qemu/units.h" #include "hw/boards.h" diff --git a/hw/arm/boot.c b/hw/arm/boot.c index becd827..d391cd0 100644 --- a/hw/arm/boot.c +++ b/hw/arm/boot.c @@ -15,6 +15,7 @@ #include "hw/arm/boot.h" #include "hw/arm/linux-boot-if.h" #include "cpu.h" +#include "exec/tswap.h" #include "exec/target_page.h" #include "system/kvm.h" #include "system/tcg.h" @@ -29,6 +30,7 @@ #include "qemu/config-file.h" #include "qemu/option.h" #include "qemu/units.h" +#include "qemu/bswap.h" /* Kernel boot protocol is specified in the kernel docs * Documentation/arm/Booting and Documentation/arm64/booting.txt diff --git a/hw/arm/npcm7xx.c b/hw/arm/npcm7xx.c index 2f30c49..ecfae32 100644 --- a/hw/arm/npcm7xx.c +++ b/hw/arm/npcm7xx.c @@ -24,7 +24,7 @@ #include "hw/qdev-clock.h" #include "hw/qdev-properties.h" #include "qapi/error.h" -#include "qemu/bswap.h" +#include "exec/tswap.h" #include "qemu/units.h" #include "system/system.h" #include "target/arm/cpu-qom.h" diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index 0dfb8ec..b01fc4f 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -34,6 +34,7 @@ #include "hw/core/cpu.h" #include "hw/acpi/acpi-defs.h" #include "hw/acpi/acpi.h" +#include "hw/acpi/pcihp.h" #include "hw/nvram/fw_cfg_acpi.h" #include "hw/acpi/bios-linker-loader.h" #include "hw/acpi/aml-build.h" @@ -144,12 +145,21 @@ static void acpi_dsdt_add_pci(Aml *scope, const MemMapEntry *memmap, int ecam_id = VIRT_ECAM_ID(vms->highmem_ecam); bool cxl_present = false; PCIBus *bus = vms->bus; + bool acpi_pcihp = false; + + if (vms->acpi_dev) { + acpi_pcihp = object_property_get_bool(OBJECT(vms->acpi_dev), + ACPI_PM_PROP_ACPI_PCIHP_BRIDGE, + NULL); + } + struct GPEXConfig cfg = { .mmio32 = memmap[VIRT_PCIE_MMIO], .pio = memmap[VIRT_PCIE_PIO], .ecam = memmap[ecam_id], .irq = irq, .bus = vms->bus, + .pci_native_hotplug = !acpi_pcihp, }; if (vms->highmem_mmio) { @@ -897,6 +907,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) const int *irqmap = vms->irqmap; AcpiTable table = { .sig = "DSDT", .rev = 2, .oem_id = vms->oem_id, .oem_table_id = vms->oem_table_id }; + Aml *pci0_scope; acpi_table_begin(&table, table_data); dsdt = init_aml_allocator(); @@ -950,6 +961,33 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) aml_append(dsdt, scope); + pci0_scope = aml_scope("\\_SB.PCI0"); + + aml_append(pci0_scope, build_pci_bridge_edsm()); + build_append_pci_bus_devices(pci0_scope, vms->bus); + if (object_property_find(OBJECT(vms->bus), ACPI_PCIHP_PROP_BSEL)) { + build_append_pcihp_slots(pci0_scope, vms->bus); + } + + if (vms->acpi_dev) { + bool acpi_pcihp; + + acpi_pcihp = object_property_get_bool(OBJECT(vms->acpi_dev), + ACPI_PM_PROP_ACPI_PCIHP_BRIDGE, + NULL); + + if (acpi_pcihp) { + build_acpi_pci_hotplug(dsdt, AML_SYSTEM_MEMORY, + memmap[VIRT_ACPI_PCIHP].base); + build_append_pcihp_resources(pci0_scope, + memmap[VIRT_ACPI_PCIHP].base, + memmap[VIRT_ACPI_PCIHP].size); + + build_append_notification_callback(pci0_scope, vms->bus); + } + } + aml_append(dsdt, pci0_scope); + /* copy AML table into ACPI tables blob */ g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len); @@ -1023,7 +1061,10 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) } acpi_add_table(table_offsets, tables_blob); - spcr_setup(tables_blob, tables->linker, vms); + + if (ms->acpi_spcr_enabled) { + spcr_setup(tables_blob, tables->linker, vms); + } acpi_add_table(table_offsets, tables_blob); build_dbg2(tables_blob, tables->linker, vms); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index 8070ff7..ef6be36 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -76,6 +76,7 @@ #include "standard-headers/linux/input.h" #include "hw/arm/smmuv3.h" #include "hw/acpi/acpi.h" +#include "hw/acpi/pcihp.h" #include "target/arm/cpu-qom.h" #include "target/arm/internals.h" #include "target/arm/multiprocessing.h" @@ -186,6 +187,7 @@ static const MemMapEntry base_memmap[] = { [VIRT_NVDIMM_ACPI] = { 0x09090000, NVDIMM_ACPI_IO_LEN}, [VIRT_PVTIME] = { 0x090a0000, 0x00010000 }, [VIRT_SECURE_GPIO] = { 0x090b0000, 0x00001000 }, + [VIRT_ACPI_PCIHP] = { 0x090c0000, ACPI_PCIHP_SIZE }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, @@ -686,8 +688,10 @@ static inline DeviceState *create_acpi_ged(VirtMachineState *vms) { DeviceState *dev; MachineState *ms = MACHINE(vms); + SysBusDevice *sbdev; int irq = vms->irqmap[VIRT_ACPI_GED]; uint32_t event = ACPI_GED_PWR_DOWN_EVT; + bool acpi_pcihp; if (ms->ram_slots) { event |= ACPI_GED_MEM_HOTPLUG_EVT; @@ -699,11 +703,26 @@ static inline DeviceState *create_acpi_ged(VirtMachineState *vms) dev = qdev_new(TYPE_ACPI_GED); qdev_prop_set_uint32(dev, "ged-event", event); - sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + object_property_set_link(OBJECT(dev), "bus", OBJECT(vms->bus), &error_abort); + sbdev = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(sbdev, &error_fatal); + + sysbus_mmio_map_name(sbdev, TYPE_ACPI_GED, vms->memmap[VIRT_ACPI_GED].base); + sysbus_mmio_map_name(sbdev, ACPI_MEMHP_REGION_NAME, + vms->memmap[VIRT_PCDIMM_ACPI].base); + + acpi_pcihp = object_property_get_bool(OBJECT(dev), + ACPI_PM_PROP_ACPI_PCIHP_BRIDGE, NULL); + + if (acpi_pcihp) { + int pcihp_region_index; + + pcihp_region_index = sysbus_mmio_map_name(sbdev, ACPI_PCIHP_REGION_NAME, + vms->memmap[VIRT_ACPI_PCIHP].base); + assert(pcihp_region_index >= 0); + } - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, vms->memmap[VIRT_ACPI_GED].base); - sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, vms->memmap[VIRT_PCDIMM_ACPI].base); - sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, qdev_get_gpio_in(vms->gic, irq)); + sysbus_connect_irq(sbdev, 0, qdev_get_gpio_in(vms->gic, irq)); return dev; } diff --git a/hw/arm/xen-pvh.c b/hw/arm/xen-pvh.c index 4b26bcf..1a9eeb0 100644 --- a/hw/arm/xen-pvh.c +++ b/hw/arm/xen-pvh.c @@ -10,7 +10,6 @@ #include "hw/boards.h" #include "system/system.h" #include "hw/xen/xen-pvh-common.h" -#include "hw/xen/arch_hvm.h" #define TYPE_XEN_ARM MACHINE_TYPE_NAME("xenpvh") diff --git a/hw/block/hd-geometry.c b/hw/block/hd-geometry.c index f3939e7..db22190 100644 --- a/hw/block/hd-geometry.c +++ b/hw/block/hd-geometry.c @@ -33,7 +33,6 @@ #include "qemu/osdep.h" #include "system/block-backend.h" #include "qapi/qapi-types-block.h" -#include "qemu/bswap.h" #include "hw/block/block.h" #include "trace.h" diff --git a/hw/block/meson.build b/hw/block/meson.build index 6557044..43ed296 100644 --- a/hw/block/meson.build +++ b/hw/block/meson.build @@ -13,7 +13,9 @@ system_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80_sfdp.c')) system_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c')) system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen-block.c')) -specific_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c', 'virtio-blk-common.c')) -specific_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c', 'virtio-blk-common.c')) +specific_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c')) +system_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk-common.c')) +specific_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk.c')) +system_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('virtio-blk-common.c')) subdir('dataplane') diff --git a/hw/block/vhost-user-blk.c b/hw/block/vhost-user-blk.c index 0eebbcd..c0cc5f6 100644 --- a/hw/block/vhost-user-blk.c +++ b/hw/block/vhost-user-blk.c @@ -210,6 +210,7 @@ static int vhost_user_blk_stop(VirtIODevice *vdev) BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); int ret; + bool force_stop = false; if (!s->started_vu) { return 0; @@ -220,7 +221,11 @@ static int vhost_user_blk_stop(VirtIODevice *vdev) return 0; } - ret = vhost_dev_stop(&s->dev, vdev, true); + force_stop = s->skip_get_vring_base_on_force_shutdown && + qemu_force_shutdown_requested(); + + ret = force_stop ? vhost_dev_force_stop(&s->dev, vdev, true) : + vhost_dev_stop(&s->dev, vdev, true); if (k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false) < 0) { error_report("vhost guest notifier cleanup failed: %d", ret); @@ -584,6 +589,8 @@ static const Property vhost_user_blk_properties[] = { VIRTIO_BLK_F_DISCARD, true), DEFINE_PROP_BIT64("write-zeroes", VHostUserBlk, parent_obj.host_features, VIRTIO_BLK_F_WRITE_ZEROES, true), + DEFINE_PROP_BOOL("skip-get-vring-base-on-force-shutdown", VHostUserBlk, + skip_get_vring_base_on_force_shutdown, false), }; static void vhost_user_blk_class_init(ObjectClass *klass, const void *data) diff --git a/hw/char/riscv_htif.c b/hw/char/riscv_htif.c index c884be5..a78ea9b 100644 --- a/hw/char/riscv_htif.c +++ b/hw/char/riscv_htif.c @@ -29,7 +29,6 @@ #include "qemu/timer.h" #include "qemu/error-report.h" #include "system/address-spaces.h" -#include "exec/tswap.h" #include "system/dma.h" #include "system/runstate.h" #include "trace.h" diff --git a/hw/core/cpu-system.c b/hw/core/cpu-system.c index 3c84176..a975405 100644 --- a/hw/core/cpu-system.c +++ b/hw/core/cpu-system.c @@ -24,7 +24,7 @@ #include "exec/cputlb.h" #include "system/memory.h" #include "exec/tb-flush.h" -#include "exec/tswap.h" +#include "qemu/target-info.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" #include "hw/core/sysemu-cpu-ops.h" diff --git a/hw/core/machine-hmp-cmds.c b/hw/core/machine-hmp-cmds.c index c6325cd..3a612e2 100644 --- a/hw/core/machine-hmp-cmds.c +++ b/hw/core/machine-hmp-cmds.c @@ -18,6 +18,7 @@ #include "monitor/monitor.h" #include "qapi/error.h" #include "qapi/qapi-builtin-visit.h" +#include "qapi/qapi-commands-accelerator.h" #include "qapi/qapi-commands-machine.h" #include "qobject/qdict.h" #include "qapi/string-output-visitor.h" @@ -32,6 +33,7 @@ void hmp_info_cpus(Monitor *mon, const QDict *qdict) cpu_list = qmp_query_cpus_fast(NULL); for (cpu = cpu_list; cpu; cpu = cpu->next) { + g_autofree char *cpu_model = cpu_model_from_type(cpu->value->qom_type); int active = ' '; if (cpu->value->cpu_index == monitor_get_cpu_index(mon)) { @@ -40,7 +42,8 @@ void hmp_info_cpus(Monitor *mon, const QDict *qdict) monitor_printf(mon, "%c CPU #%" PRId64 ":", active, cpu->value->cpu_index); - monitor_printf(mon, " thread_id=%" PRId64 "\n", cpu->value->thread_id); + monitor_printf(mon, " thread_id=%" PRId64 " model=%s\n", + cpu->value->thread_id, cpu_model); } qapi_free_CpuInfoFastList(cpu_list); diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c index d82043e..6aca1a6 100644 --- a/hw/core/machine-qmp-cmds.c +++ b/hw/core/machine-qmp-cmds.c @@ -14,12 +14,13 @@ #include "hw/mem/memory-device.h" #include "qapi/error.h" #include "qapi/qapi-builtin-visit.h" +#include "qapi/qapi-commands-accelerator.h" #include "qapi/qapi-commands-machine.h" #include "qobject/qobject.h" #include "qapi/qobject-input-visitor.h" #include "qapi/type-helpers.h" #include "qemu/uuid.h" -#include "qemu/target-info.h" +#include "qemu/target-info-qapi.h" #include "qom/qom-qobject.h" #include "system/hostmem.h" #include "system/hw_accel.h" @@ -37,8 +38,7 @@ CpuInfoFastList *qmp_query_cpus_fast(Error **errp) MachineState *ms = MACHINE(qdev_get_machine()); MachineClass *mc = MACHINE_GET_CLASS(ms); CpuInfoFastList *head = NULL, **tail = &head; - SysEmuTarget target = qapi_enum_parse(&SysEmuTarget_lookup, target_name(), - -1, &error_abort); + SysEmuTarget target = target_arch(); CPUState *cpu; CPU_FOREACH(cpu) { @@ -47,6 +47,7 @@ CpuInfoFastList *qmp_query_cpus_fast(Error **errp) value->cpu_index = cpu->cpu_index; value->qom_path = object_get_canonical_path(OBJECT(cpu)); value->thread_id = cpu->thread_id; + value->qom_type = g_strdup(object_get_typename(OBJECT(cpu))); if (mc->cpu_index_to_instance_props) { CpuInstanceProperties *props; @@ -139,8 +140,7 @@ QemuTargetInfo *qmp_query_target(Error **errp) { QemuTargetInfo *info = g_malloc0(sizeof(*info)); - info->arch = qapi_enum_parse(&SysEmuTarget_lookup, target_name(), -1, - &error_abort); + info->arch = target_arch(); return info; } diff --git a/hw/core/machine.c b/hw/core/machine.c index e869821..8063271 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -39,6 +39,7 @@ GlobalProperty hw_compat_10_0[] = { { "scsi-hd", "dpofua", "off" }, + { "vfio-pci", "x-migration-load-config-after-iter", "off" }, }; const size_t hw_compat_10_0_len = G_N_ELEMENTS(hw_compat_10_0); @@ -577,6 +578,20 @@ static void machine_set_nvdimm(Object *obj, bool value, Error **errp) ms->nvdimms_state->is_enabled = value; } +static bool machine_get_spcr(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return ms->acpi_spcr_enabled; +} + +static void machine_set_spcr(Object *obj, bool value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->acpi_spcr_enabled = value; +} + static bool machine_get_hmat(Object *obj, Error **errp) { MachineState *ms = MACHINE(obj); @@ -1281,6 +1296,14 @@ static void machine_initfn(Object *obj) "Table (HMAT)"); } + /* SPCR */ + ms->acpi_spcr_enabled = true; + object_property_add_bool(obj, "spcr", machine_get_spcr, machine_set_spcr); + object_property_set_description(obj, "spcr", + "Set on/off to enable/disable " + "ACPI Serial Port Console Redirection " + "Table (spcr)"); + /* default to mc->default_cpus */ ms->smp.cpus = mc->default_cpus; ms->smp.max_cpus = mc->default_cpus; diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c index 24e145d..1f810b7 100644 --- a/hw/core/qdev-properties-system.c +++ b/hw/core/qdev-properties-system.c @@ -1299,3 +1299,47 @@ const PropertyInfo qdev_prop_vmapple_virtio_blk_variant = { .set = qdev_propinfo_set_enum, .set_default_value = qdev_propinfo_set_default_value_enum, }; + +/* --- VirtIOGPUOutputList --- */ + +static void get_virtio_gpu_output_list(Object *obj, Visitor *v, + const char *name, void *opaque, Error **errp) +{ + VirtIOGPUOutputList **prop_ptr = + object_field_prop_ptr(obj, opaque); + + visit_type_VirtIOGPUOutputList(v, name, prop_ptr, errp); +} + +static void set_virtio_gpu_output_list(Object *obj, Visitor *v, + const char *name, void *opaque, Error **errp) +{ + VirtIOGPUOutputList **prop_ptr = + object_field_prop_ptr(obj, opaque); + VirtIOGPUOutputList *list; + + if (!visit_type_VirtIOGPUOutputList(v, name, &list, errp)) { + return; + } + + qapi_free_VirtIOGPUOutputList(*prop_ptr); + *prop_ptr = list; +} + +static void release_virtio_gpu_output_list(Object *obj, + const char *name, void *opaque) +{ + VirtIOGPUOutputList **prop_ptr = + object_field_prop_ptr(obj, opaque); + + qapi_free_VirtIOGPUOutputList(*prop_ptr); + *prop_ptr = NULL; +} + +const PropertyInfo qdev_prop_virtio_gpu_output_list = { + .type = "VirtIOGPUOutputList", + .description = "VirtIO GPU output list [{\"name\":\"<name>\"},...]", + .get = get_virtio_gpu_output_list, + .set = set_virtio_gpu_output_list, + .release = release_virtio_gpu_output_list, +}; diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 147b3ff..b7e8a89 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -2,6 +2,7 @@ #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qapi/qapi-types-misc.h" +#include "qapi/qapi-visit-common.h" #include "qobject/qlist.h" #include "qemu/ctype.h" #include "qemu/error-report.h" @@ -180,7 +181,8 @@ const PropertyInfo qdev_prop_bit = { static uint64_t qdev_get_prop_mask64(const Property *prop) { - assert(prop->info == &qdev_prop_bit64); + assert(prop->info == &qdev_prop_bit64 || + prop->info == &qdev_prop_on_off_auto_bit64); return 0x1ull << prop->bitnr; } @@ -225,6 +227,69 @@ const PropertyInfo qdev_prop_bit64 = { .set_default_value = set_default_value_bool, }; +static void prop_get_on_off_auto_bit64(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit64 *p = object_field_prop_ptr(obj, prop); + OnOffAuto value; + uint64_t mask = qdev_get_prop_mask64(prop); + + if (p->auto_bits & mask) { + value = ON_OFF_AUTO_AUTO; + } else if (p->on_bits & mask) { + value = ON_OFF_AUTO_ON; + } else { + value = ON_OFF_AUTO_OFF; + } + + visit_type_OnOffAuto(v, name, &value, errp); +} + +static void prop_set_on_off_auto_bit64(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit64 *p = object_field_prop_ptr(obj, prop); + OnOffAuto value; + uint64_t mask = qdev_get_prop_mask64(prop); + + if (!visit_type_OnOffAuto(v, name, &value, errp)) { + return; + } + + switch (value) { + case ON_OFF_AUTO_AUTO: + p->on_bits &= ~mask; + p->auto_bits |= mask; + break; + + case ON_OFF_AUTO_ON: + p->on_bits |= mask; + p->auto_bits &= ~mask; + break; + + case ON_OFF_AUTO_OFF: + p->on_bits &= ~mask; + p->auto_bits &= ~mask; + break; + + case ON_OFF_AUTO__MAX: + g_assert_not_reached(); + } +} + +const PropertyInfo qdev_prop_on_off_auto_bit64 = { + .type = "OnOffAuto", + .description = "on/off/auto", + .enum_table = &OnOffAuto_lookup, + .get = prop_get_on_off_auto_bit64, + .set = prop_set_on_off_auto_bit64, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; + /* --- bool --- */ static void get_bool(Object *obj, Visitor *v, const char *name, void *opaque, diff --git a/hw/core/sysbus.c b/hw/core/sysbus.c index e71367a..ec69e87 100644 --- a/hw/core/sysbus.c +++ b/hw/core/sysbus.c @@ -151,6 +151,17 @@ void sysbus_mmio_map(SysBusDevice *dev, int n, hwaddr addr) sysbus_mmio_map_common(dev, n, addr, false, 0); } +int sysbus_mmio_map_name(SysBusDevice *dev, const char *name, hwaddr addr) +{ + for (int i = 0; i < dev->num_mmio; i++) { + if (!strcmp(dev->mmio[i].memory->name, name)) { + sysbus_mmio_map(dev, i, addr); + return i; + } + } + return -1; +} + void sysbus_mmio_map_overlap(SysBusDevice *dev, int n, hwaddr addr, int priority) { diff --git a/hw/cxl/cxl-events.c b/hw/cxl/cxl-events.c index 12dee2e..7583dd9 100644 --- a/hw/cxl/cxl-events.c +++ b/hw/cxl/cxl-events.c @@ -8,8 +8,6 @@ */ #include "qemu/osdep.h" - -#include "qemu/bswap.h" #include "qemu/error-report.h" #include "hw/pci/msi.h" #include "hw/pci/msix.h" @@ -260,3 +258,41 @@ void cxl_event_irq_assert(CXLType3Dev *ct3d) } } } + +void cxl_create_dc_event_records_for_extents(CXLType3Dev *ct3d, + CXLDCEventType type, + CXLDCExtentRaw extents[], + uint32_t ext_count) +{ + CXLEventDynamicCapacity event_rec = {}; + int i; + + cxl_assign_event_header(&event_rec.hdr, + &dynamic_capacity_uuid, + (1 << CXL_EVENT_TYPE_INFO), + sizeof(event_rec), + cxl_device_get_timestamp(&ct3d->cxl_dstate)); + event_rec.type = type; + event_rec.validity_flags = 1; + event_rec.host_id = 0; + event_rec.updated_region_id = 0; + event_rec.extents_avail = CXL_NUM_EXTENTS_SUPPORTED - + ct3d->dc.total_extent_count; + + for (i = 0; i < ext_count; i++) { + memcpy(&event_rec.dynamic_capacity_extent, + &extents[i], + sizeof(CXLDCExtentRaw)); + event_rec.flags = 0; + if (i < ext_count - 1) { + /* Set "More" flag */ + event_rec.flags |= BIT(0); + } + + if (cxl_event_insert(&ct3d->cxl_dstate, + CXL_EVENT_TYPE_DYNAMIC_CAP, + (CXLEventRecordRaw *)&event_rec)) { + cxl_event_irq_assert(ct3d); + } + } +} diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c index 299f232..68c7cc9 100644 --- a/hw/cxl/cxl-mailbox-utils.c +++ b/hw/cxl/cxl-mailbox-utils.c @@ -18,15 +18,16 @@ #include "hw/pci/pci.h" #include "hw/pci-bridge/cxl_upstream_port.h" #include "qemu/cutils.h" +#include "qemu/host-utils.h" #include "qemu/log.h" #include "qemu/units.h" #include "qemu/uuid.h" #include "system/hostmem.h" #include "qemu/range.h" +#include "qapi/qapi-types-cxl.h" #define CXL_CAPACITY_MULTIPLIER (256 * MiB) #define CXL_DC_EVENT_LOG_SIZE 8 -#define CXL_NUM_EXTENTS_SUPPORTED 512 #define CXL_NUM_TAGS_SUPPORTED 0 #define CXL_ALERTS_LIFE_USED_WARN_THRESH (1 << 0) #define CXL_ALERTS_OVER_TEMP_WARN_THRESH (1 << 1) @@ -117,6 +118,13 @@ enum { #define GET_PHYSICAL_PORT_STATE 0x1 TUNNEL = 0x53, #define MANAGEMENT_COMMAND 0x0 + FMAPI_DCD_MGMT = 0x56, + #define GET_DCD_INFO 0x0 + #define GET_HOST_DC_REGION_CONFIG 0x1 + #define SET_DC_REGION_CONFIG 0x2 + #define GET_DC_REGION_EXTENT_LIST 0x3 + #define INITIATE_DC_ADD 0x4 + #define INITIATE_DC_RELEASE 0x5 }; /* CCI Message Format CXL r3.1 Figure 7-19 */ @@ -2750,7 +2758,7 @@ static CXLRetCode cmd_dcd_get_dyn_cap_ext_list(const struct cxl_cmd *cmd, uint16_t out_pl_len, size; CXLDCExtent *ent; - if (start_extent_id > ct3d->dc.total_extent_count) { + if (start_extent_id > ct3d->dc.nr_extents_accepted) { return CXL_MBOX_INVALID_INPUT; } @@ -2761,7 +2769,7 @@ static CXLRetCode cmd_dcd_get_dyn_cap_ext_list(const struct cxl_cmd *cmd, out_pl_len = sizeof(*out) + record_count * sizeof(out->records[0]); stl_le_p(&out->count, record_count); - stl_le_p(&out->total_extents, ct3d->dc.total_extent_count); + stl_le_p(&out->total_extents, ct3d->dc.nr_extents_accepted); stl_le_p(&out->generation_num, ct3d->dc.ext_list_gen_seq); if (record_count > 0) { @@ -2883,16 +2891,20 @@ void cxl_extent_group_list_insert_tail(CXLDCExtentGroupList *list, QTAILQ_INSERT_TAIL(list, group, node); } -void cxl_extent_group_list_delete_front(CXLDCExtentGroupList *list) +uint32_t cxl_extent_group_list_delete_front(CXLDCExtentGroupList *list) { CXLDCExtent *ent, *ent_next; CXLDCExtentGroup *group = QTAILQ_FIRST(list); + uint32_t extents_deleted = 0; QTAILQ_REMOVE(list, group, node); QTAILQ_FOREACH_SAFE(ent, &group->list, node, ent_next) { cxl_remove_extent_from_extent_list(&group->list, ent); + extents_deleted++; } g_free(group); + + return extents_deleted; } /* @@ -3011,7 +3023,7 @@ static CXLRetCode cmd_dcd_add_dyn_cap_rsp(const struct cxl_cmd *cmd, CXLUpdateDCExtentListInPl *in = (void *)payload_in; CXLType3Dev *ct3d = CXL_TYPE3(cci->d); CXLDCExtentList *extent_list = &ct3d->dc.extents; - uint32_t i; + uint32_t i, num; uint64_t dpa, len; CXLRetCode ret; @@ -3020,7 +3032,8 @@ static CXLRetCode cmd_dcd_add_dyn_cap_rsp(const struct cxl_cmd *cmd, } if (in->num_entries_updated == 0) { - cxl_extent_group_list_delete_front(&ct3d->dc.extents_pending); + num = cxl_extent_group_list_delete_front(&ct3d->dc.extents_pending); + ct3d->dc.total_extent_count -= num; return CXL_MBOX_SUCCESS; } @@ -3051,10 +3064,12 @@ static CXLRetCode cmd_dcd_add_dyn_cap_rsp(const struct cxl_cmd *cmd, cxl_insert_extent_to_extent_list(extent_list, dpa, len, NULL, 0); ct3d->dc.total_extent_count += 1; + ct3d->dc.nr_extents_accepted += 1; ct3_set_region_block_backed(ct3d, dpa, len); } /* Remove the first extent group in the pending list */ - cxl_extent_group_list_delete_front(&ct3d->dc.extents_pending); + num = cxl_extent_group_list_delete_front(&ct3d->dc.extents_pending); + ct3d->dc.total_extent_count -= num; return CXL_MBOX_SUCCESS; } @@ -3160,7 +3175,7 @@ free_and_exit: } *updated_list_size = 0; } else { - *updated_list_size = ct3d->dc.total_extent_count + cnt_delta; + *updated_list_size = ct3d->dc.nr_extents_accepted + cnt_delta; } return ret; @@ -3222,11 +3237,495 @@ static CXLRetCode cmd_dcd_release_dyn_cap(const struct cxl_cmd *cmd, ct3_set_region_block_backed(ct3d, ent->start_dpa, ent->len); cxl_remove_extent_from_extent_list(&updated_list, ent); } - ct3d->dc.total_extent_count = updated_list_size; + ct3d->dc.total_extent_count += (updated_list_size - + ct3d->dc.nr_extents_accepted); + + ct3d->dc.nr_extents_accepted = updated_list_size; + + return CXL_MBOX_SUCCESS; +} + +/* CXL r3.2 section 7.6.7.6.1: Get DCD Info (Opcode 5600h) */ +static CXLRetCode cmd_fm_get_dcd_info(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint8_t num_hosts; + uint8_t num_regions_supported; + uint8_t rsvd1[2]; + uint16_t supported_add_sel_policy_bitmask; + uint8_t rsvd2[2]; + uint16_t supported_removal_policy_bitmask; + uint8_t sanitize_on_release_bitmask; + uint8_t rsvd3; + uint64_t total_dynamic_capacity; + uint64_t region_blk_size_bitmasks[8]; + } QEMU_PACKED *out = (void *)payload_out; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + CXLDCRegion *region; + int i; + + out->num_hosts = 1; + out->num_regions_supported = ct3d->dc.num_regions; + stw_le_p(&out->supported_add_sel_policy_bitmask, + BIT(CXL_EXTENT_SELECTION_POLICY_PRESCRIPTIVE)); + stw_le_p(&out->supported_removal_policy_bitmask, + BIT(CXL_EXTENT_REMOVAL_POLICY_PRESCRIPTIVE)); + out->sanitize_on_release_bitmask = 0; + + stq_le_p(&out->total_dynamic_capacity, + ct3d->dc.total_capacity / CXL_CAPACITY_MULTIPLIER); + + for (i = 0; i < ct3d->dc.num_regions; i++) { + region = &ct3d->dc.regions[i]; + memcpy(&out->region_blk_size_bitmasks[i], + ®ion->supported_blk_size_bitmask, + sizeof(out->region_blk_size_bitmasks[i])); + } + + *len_out = sizeof(*out); + return CXL_MBOX_SUCCESS; +} + +static void build_dsmas_flags(uint8_t *flags, CXLDCRegion *region) +{ + *flags = 0; + + if (region->nonvolatile) { + *flags |= BIT(CXL_DSMAS_FLAGS_NONVOLATILE); + } + if (region->sharable) { + *flags |= BIT(CXL_DSMAS_FLAGS_SHARABLE); + } + if (region->hw_managed_coherency) { + *flags |= BIT(CXL_DSMAS_FLAGS_HW_MANAGED_COHERENCY); + } + if (region->ic_specific_dc_management) { + *flags |= BIT(CXL_DSMAS_FLAGS_IC_SPECIFIC_DC_MANAGEMENT); + } + if (region->rdonly) { + *flags |= BIT(CXL_DSMAS_FLAGS_RDONLY); + } +} + +/* + * CXL r3.2 section 7.6.7.6.2: + * Get Host DC Region Configuration (Opcode 5601h) + */ +static CXLRetCode cmd_fm_get_host_dc_region_config(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint16_t host_id; + uint8_t region_cnt; + uint8_t start_rid; + } QEMU_PACKED *in = (void *)payload_in; + struct { + uint16_t host_id; + uint8_t num_regions; + uint8_t regions_returned; + struct { + uint64_t base; + uint64_t decode_len; + uint64_t region_len; + uint64_t block_size; + uint8_t flags; + uint8_t rsvd1[3]; + uint8_t sanitize; + uint8_t rsvd2[3]; + } QEMU_PACKED records[]; + } QEMU_PACKED *out = (void *)payload_out; + struct { + uint32_t num_extents_supported; + uint32_t num_extents_available; + uint32_t num_tags_supported; + uint32_t num_tags_available; + } QEMU_PACKED *extra_out; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + uint16_t record_count, out_pl_len, i; + + if (in->start_rid >= ct3d->dc.num_regions) { + return CXL_MBOX_INVALID_INPUT; + } + record_count = MIN(ct3d->dc.num_regions - in->start_rid, in->region_cnt); + + out_pl_len = sizeof(*out) + record_count * sizeof(out->records[0]); + extra_out = (void *)out + out_pl_len; + out_pl_len += sizeof(*extra_out); + + assert(out_pl_len <= CXL_MAILBOX_MAX_PAYLOAD_SIZE); + + stw_le_p(&out->host_id, 0); + out->num_regions = ct3d->dc.num_regions; + out->regions_returned = record_count; + + for (i = 0; i < record_count; i++) { + stq_le_p(&out->records[i].base, + ct3d->dc.regions[in->start_rid + i].base); + stq_le_p(&out->records[i].decode_len, + ct3d->dc.regions[in->start_rid + i].decode_len / + CXL_CAPACITY_MULTIPLIER); + stq_le_p(&out->records[i].region_len, + ct3d->dc.regions[in->start_rid + i].len); + stq_le_p(&out->records[i].block_size, + ct3d->dc.regions[in->start_rid + i].block_size); + build_dsmas_flags(&out->records[i].flags, + &ct3d->dc.regions[in->start_rid + i]); + /* Sanitize is bit 0 of flags. */ + out->records[i].sanitize = + ct3d->dc.regions[in->start_rid + i].flags & BIT(0); + } + + stl_le_p(&extra_out->num_extents_supported, CXL_NUM_EXTENTS_SUPPORTED); + stl_le_p(&extra_out->num_extents_available, CXL_NUM_EXTENTS_SUPPORTED - + ct3d->dc.total_extent_count); + stl_le_p(&extra_out->num_tags_supported, CXL_NUM_TAGS_SUPPORTED); + stl_le_p(&extra_out->num_tags_available, CXL_NUM_TAGS_SUPPORTED); + + *len_out = out_pl_len; + return CXL_MBOX_SUCCESS; +} + +/* CXL r3.2 section 7.6.7.6.3: Set Host DC Region Configuration (Opcode 5602) */ +static CXLRetCode cmd_fm_set_dc_region_config(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint8_t reg_id; + uint8_t rsvd[3]; + uint64_t block_sz; + uint8_t flags; + uint8_t rsvd2[3]; + } QEMU_PACKED *in = (void *)payload_in; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + CXLEventDynamicCapacity dcEvent = {}; + CXLDCRegion *region = &ct3d->dc.regions[in->reg_id]; + + /* + * CXL r3.2 7.6.7.6.3: Set DC Region Configuration + * This command shall fail with Unsupported when the Sanitize on Release + * field does not match the region’s configuration... and the device + * does not support reconfiguration of the Sanitize on Release setting. + * + * Currently not reconfigurable, so always fail if sanitize bit (bit 0) + * doesn't match. + */ + if ((in->flags & 0x1) != (region->flags & 0x1)) { + return CXL_MBOX_UNSUPPORTED; + } + + if (in->reg_id >= DCD_MAX_NUM_REGION) { + return CXL_MBOX_UNSUPPORTED; + } + + /* Check that no extents are in the region being reconfigured */ + if (!bitmap_empty(region->blk_bitmap, region->len / region->block_size)) { + return CXL_MBOX_UNSUPPORTED; + } + + /* Check that new block size is supported */ + if (!is_power_of_2(in->block_sz) || + !(in->block_sz & region->supported_blk_size_bitmask)) { + return CXL_MBOX_INVALID_INPUT; + } + + /* Return success if new block size == current block size */ + if (in->block_sz == region->block_size) { + return CXL_MBOX_SUCCESS; + } + + /* Free bitmap and create new one for new block size. */ + qemu_mutex_lock(®ion->bitmap_lock); + g_free(region->blk_bitmap); + region->blk_bitmap = bitmap_new(region->len / in->block_sz); + qemu_mutex_unlock(®ion->bitmap_lock); + region->block_size = in->block_sz; + + /* Create event record and insert into event log */ + cxl_assign_event_header(&dcEvent.hdr, + &dynamic_capacity_uuid, + (1 << CXL_EVENT_TYPE_INFO), + sizeof(dcEvent), + cxl_device_get_timestamp(&ct3d->cxl_dstate)); + dcEvent.type = DC_EVENT_REGION_CONFIG_UPDATED; + dcEvent.validity_flags = 1; + dcEvent.host_id = 0; + dcEvent.updated_region_id = in->reg_id; + + if (cxl_event_insert(&ct3d->cxl_dstate, + CXL_EVENT_TYPE_DYNAMIC_CAP, + (CXLEventRecordRaw *)&dcEvent)) { + cxl_event_irq_assert(ct3d); + } + return CXL_MBOX_SUCCESS; +} + +/* CXL r3.2 section 7.6.7.6.4: Get DC Region Extent Lists (Opcode 5603h) */ +static CXLRetCode cmd_fm_get_dc_region_extent_list(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint16_t host_id; + uint8_t rsvd[2]; + uint32_t extent_cnt; + uint32_t start_extent_id; + } QEMU_PACKED *in = (void *)payload_in; + struct { + uint16_t host_id; + uint8_t rsvd[2]; + uint32_t start_extent_id; + uint32_t extents_returned; + uint32_t total_extents; + uint32_t list_generation_num; + uint8_t rsvd2[4]; + CXLDCExtentRaw records[]; + } QEMU_PACKED *out = (void *)payload_out; + QEMU_BUILD_BUG_ON(sizeof(*in) != 0xc); + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + CXLDCExtent *ent; + CXLDCExtentRaw *out_rec; + uint16_t record_count = 0, record_done = 0, i = 0; + uint16_t out_pl_len, max_size; + + if (in->host_id != 0) { + return CXL_MBOX_INVALID_INPUT; + } + + if (in->start_extent_id > ct3d->dc.nr_extents_accepted) { + return CXL_MBOX_INVALID_INPUT; + } + record_count = MIN(in->extent_cnt, + ct3d->dc.nr_extents_accepted - in->start_extent_id); + max_size = CXL_MAILBOX_MAX_PAYLOAD_SIZE - sizeof(*out); + record_count = MIN(record_count, max_size / sizeof(out->records[0])); + out_pl_len = sizeof(*out) + record_count * sizeof(out->records[0]); + + stw_le_p(&out->host_id, in->host_id); + stl_le_p(&out->start_extent_id, in->start_extent_id); + stl_le_p(&out->extents_returned, record_count); + stl_le_p(&out->total_extents, ct3d->dc.nr_extents_accepted); + stl_le_p(&out->list_generation_num, ct3d->dc.ext_list_gen_seq); + + if (record_count > 0) { + QTAILQ_FOREACH(ent, &ct3d->dc.extents, node) { + if (i++ < in->start_extent_id) { + continue; + } + out_rec = &out->records[record_done]; + stq_le_p(&out_rec->start_dpa, ent->start_dpa); + stq_le_p(&out_rec->len, ent->len); + memcpy(&out_rec->tag, ent->tag, 0x10); + stw_le_p(&out_rec->shared_seq, ent->shared_seq); + + record_done++; + if (record_done == record_count) { + break; + } + } + } + + *len_out = out_pl_len; return CXL_MBOX_SUCCESS; } +/* + * Helper function to convert CXLDCExtentRaw to CXLUpdateDCExtentListInPl + * in order to reuse cxl_detect_malformed_extent_list() function which accepts + * CXLUpdateDCExtentListInPl as a parameter. + */ +static void convert_raw_extents(CXLDCExtentRaw raw_extents[], + CXLUpdateDCExtentListInPl *extent_list, + int count) +{ + int i; + + extent_list->num_entries_updated = count; + + for (i = 0; i < count; i++) { + extent_list->updated_entries[i].start_dpa = raw_extents[i].start_dpa; + extent_list->updated_entries[i].len = raw_extents[i].len; + } +} + +/* CXL r3.2 Section 7.6.7.6.5: Initiate Dynamic Capacity Add (Opcode 5604h) */ +static CXLRetCode cmd_fm_initiate_dc_add(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint16_t host_id; + uint8_t selection_policy; + uint8_t reg_num; + uint64_t length; + uint8_t tag[0x10]; + uint32_t ext_count; + CXLDCExtentRaw extents[]; + } QEMU_PACKED *in = (void *)payload_in; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + int i, rc; + + switch (in->selection_policy) { + case CXL_EXTENT_SELECTION_POLICY_PRESCRIPTIVE: { + /* Adding extents exceeds device's extent tracking ability. */ + if (in->ext_count + ct3d->dc.total_extent_count > + CXL_NUM_EXTENTS_SUPPORTED) { + return CXL_MBOX_RESOURCES_EXHAUSTED; + } + + g_autofree CXLUpdateDCExtentListInPl *list = + g_malloc0(sizeof(*list) + + in->ext_count * sizeof(*list->updated_entries)); + + convert_raw_extents(in->extents, list, in->ext_count); + rc = cxl_detect_malformed_extent_list(ct3d, list); + + for (i = 0; i < in->ext_count; i++) { + CXLDCExtentRaw *ext = &in->extents[i]; + + /* Check requested extents do not overlap with pending ones. */ + if (cxl_extent_groups_overlaps_dpa_range(&ct3d->dc.extents_pending, + ext->start_dpa, + ext->len)) { + return CXL_MBOX_INVALID_EXTENT_LIST; + } + /* Check requested extents do not overlap with existing ones. */ + if (cxl_extents_overlaps_dpa_range(&ct3d->dc.extents, + ext->start_dpa, + ext->len)) { + return CXL_MBOX_INVALID_EXTENT_LIST; + } + } + + if (rc) { + return rc; + } + + CXLDCExtentGroup *group = NULL; + for (i = 0; i < in->ext_count; i++) { + CXLDCExtentRaw *ext = &in->extents[i]; + + group = cxl_insert_extent_to_extent_group(group, ext->start_dpa, + ext->len, ext->tag, + ext->shared_seq); + } + + cxl_extent_group_list_insert_tail(&ct3d->dc.extents_pending, group); + ct3d->dc.total_extent_count += in->ext_count; + cxl_create_dc_event_records_for_extents(ct3d, + DC_EVENT_ADD_CAPACITY, + in->extents, + in->ext_count); + + return CXL_MBOX_SUCCESS; + } + default: { + qemu_log_mask(LOG_UNIMP, + "CXL extent selection policy not supported.\n"); + return CXL_MBOX_INVALID_INPUT; + } + } +} + +#define CXL_EXTENT_REMOVAL_POLICY_MASK 0x0F +#define CXL_FORCED_REMOVAL_MASK (1 << 4) +/* + * CXL r3.2 Section 7.6.7.6.6: + * Initiate Dynamic Capacity Release (Opcode 5605h) + */ +static CXLRetCode cmd_fm_initiate_dc_release(const struct cxl_cmd *cmd, + uint8_t *payload_in, + size_t len_in, + uint8_t *payload_out, + size_t *len_out, + CXLCCI *cci) +{ + struct { + uint16_t host_id; + uint8_t flags; + uint8_t reg_num; + uint64_t length; + uint8_t tag[0x10]; + uint32_t ext_count; + CXLDCExtentRaw extents[]; + } QEMU_PACKED *in = (void *)payload_in; + CXLType3Dev *ct3d = CXL_TYPE3(cci->d); + int i, rc; + + switch (in->flags & CXL_EXTENT_REMOVAL_POLICY_MASK) { + case CXL_EXTENT_REMOVAL_POLICY_PRESCRIPTIVE: { + CXLDCExtentList updated_list; + uint32_t updated_list_size; + g_autofree CXLUpdateDCExtentListInPl *list = + g_malloc0(sizeof(*list) + + in->ext_count * sizeof(*list->updated_entries)); + + convert_raw_extents(in->extents, list, in->ext_count); + rc = cxl_detect_malformed_extent_list(ct3d, list); + if (rc) { + return rc; + } + + /* + * Fail with Invalid PA if an extent is pending and Forced Removal + * flag not set. + */ + if (!(in->flags & CXL_FORCED_REMOVAL_MASK)) { + for (i = 0; i < in->ext_count; i++) { + CXLDCExtentRaw ext = in->extents[i]; + /* + * Check requested extents don't overlap with pending + * extents. + */ + if (cxl_extent_groups_overlaps_dpa_range( + &ct3d->dc.extents_pending, + ext.start_dpa, + ext.len)) { + return CXL_MBOX_INVALID_PA; + } + } + } + + rc = cxl_dc_extent_release_dry_run(ct3d, + list, + &updated_list, + &updated_list_size); + if (rc) { + return rc; + } + cxl_create_dc_event_records_for_extents(ct3d, + DC_EVENT_RELEASE_CAPACITY, + in->extents, + in->ext_count); + return CXL_MBOX_SUCCESS; + } + default: { + qemu_log_mask(LOG_UNIMP, + "CXL extent removal policy not supported.\n"); + return CXL_MBOX_INVALID_INPUT; + } + } +} + static const struct cxl_cmd cxl_cmd_set[256][256] = { [INFOSTAT][BACKGROUND_OPERATION_ABORT] = { "BACKGROUND_OPERATION_ABORT", cmd_infostat_bg_op_abort, 0, 0 }, @@ -3340,6 +3839,36 @@ static const struct cxl_cmd cxl_cmd_set_sw[256][256] = { cmd_tunnel_management_cmd, ~0, 0 }, }; +static const struct cxl_cmd cxl_cmd_set_fm_dcd[256][256] = { + [FMAPI_DCD_MGMT][GET_DCD_INFO] = { "GET_DCD_INFO", + cmd_fm_get_dcd_info, 0, 0 }, + [FMAPI_DCD_MGMT][GET_HOST_DC_REGION_CONFIG] = { "GET_HOST_DC_REGION_CONFIG", + cmd_fm_get_host_dc_region_config, 4, 0 }, + [FMAPI_DCD_MGMT][SET_DC_REGION_CONFIG] = { "SET_DC_REGION_CONFIG", + cmd_fm_set_dc_region_config, 16, + (CXL_MBOX_CONFIG_CHANGE_COLD_RESET | + CXL_MBOX_CONFIG_CHANGE_CONV_RESET | + CXL_MBOX_CONFIG_CHANGE_CXL_RESET | + CXL_MBOX_IMMEDIATE_CONFIG_CHANGE | + CXL_MBOX_IMMEDIATE_DATA_CHANGE) }, + [FMAPI_DCD_MGMT][GET_DC_REGION_EXTENT_LIST] = { "GET_DC_REGION_EXTENT_LIST", + cmd_fm_get_dc_region_extent_list, 12, 0 }, + [FMAPI_DCD_MGMT][INITIATE_DC_ADD] = { "INIT_DC_ADD", + cmd_fm_initiate_dc_add, ~0, + (CXL_MBOX_CONFIG_CHANGE_COLD_RESET | + CXL_MBOX_CONFIG_CHANGE_CONV_RESET | + CXL_MBOX_CONFIG_CHANGE_CXL_RESET | + CXL_MBOX_IMMEDIATE_CONFIG_CHANGE | + CXL_MBOX_IMMEDIATE_DATA_CHANGE) }, + [FMAPI_DCD_MGMT][INITIATE_DC_RELEASE] = { "INIT_DC_RELEASE", + cmd_fm_initiate_dc_release, ~0, + (CXL_MBOX_CONFIG_CHANGE_COLD_RESET | + CXL_MBOX_CONFIG_CHANGE_CONV_RESET | + CXL_MBOX_CONFIG_CHANGE_CXL_RESET | + CXL_MBOX_IMMEDIATE_CONFIG_CHANGE | + CXL_MBOX_IMMEDIATE_DATA_CHANGE) }, +}; + /* * While the command is executing in the background, the device should * update the percentage complete in the Background Command Status Register @@ -3614,7 +4143,12 @@ void cxl_initialize_t3_fm_owned_ld_mctpcci(CXLCCI *cci, DeviceState *d, DeviceState *intf, size_t payload_max) { + CXLType3Dev *ct3d = CXL_TYPE3(d); + cxl_copy_cci_commands(cci, cxl_cmd_set_t3_fm_owned_ld_mctp); + if (ct3d->dc.num_regions) { + cxl_copy_cci_commands(cci, cxl_cmd_set_fm_dcd); + } cci->d = d; cci->intf = intf; cxl_init_cci(cci, payload_max); diff --git a/hw/display/artist.c b/hw/display/artist.c index 3fafc8a..3c884c9 100644 --- a/hw/display/artist.c +++ b/hw/display/artist.c @@ -12,6 +12,7 @@ #include "qemu/log.h" #include "qemu/module.h" #include "qemu/units.h" +#include "qemu/bswap.h" #include "qapi/error.h" #include "hw/sysbus.h" #include "hw/loader.h" diff --git a/hw/display/ati.c b/hw/display/ati.c index 7de2773..f7c0006 100644 --- a/hw/display/ati.c +++ b/hw/display/ati.c @@ -22,6 +22,7 @@ #include "vga-access.h" #include "hw/qdev-properties.h" #include "vga_regs.h" +#include "qemu/bswap.h" #include "qemu/log.h" #include "qemu/module.h" #include "qemu/error-report.h" diff --git a/hw/display/vga.c b/hw/display/vga.c index 20475eb..90b89cf 100644 --- a/hw/display/vga.c +++ b/hw/display/vga.c @@ -26,7 +26,7 @@ #include "qemu/units.h" #include "system/reset.h" #include "qapi/error.h" -#include "exec/tswap.h" +#include "qemu/target-info.h" #include "hw/display/vga.h" #include "hw/i386/x86.h" #include "hw/pci/pci.h" diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c index 9eb806b..7269477 100644 --- a/hw/display/virtio-gpu-base.c +++ b/hw/display/virtio-gpu-base.c @@ -19,6 +19,7 @@ #include "qemu/error-report.h" #include "hw/display/edid.h" #include "trace.h" +#include "qapi/qapi-types-virtio.h" void virtio_gpu_base_reset(VirtIOGPUBase *g) @@ -56,6 +57,8 @@ void virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int scanout, struct virtio_gpu_resp_edid *edid) { + size_t output_idx; + VirtIOGPUOutputList *node; qemu_edid_info info = { .width_mm = g->req_state[scanout].width_mm, .height_mm = g->req_state[scanout].height_mm, @@ -64,6 +67,14 @@ virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int scanout, .refresh_rate = g->req_state[scanout].refresh_rate, }; + for (output_idx = 0, node = g->conf.outputs; + output_idx <= scanout && node; output_idx++, node = node->next) { + if (output_idx == scanout && node->value && node->value->name) { + info.name = node->value->name; + break; + } + } + edid->size = cpu_to_le32(sizeof(edid->edid)); qemu_edid_generate(edid->edid, sizeof(edid->edid), &info); } @@ -172,6 +183,8 @@ virtio_gpu_base_device_realize(DeviceState *qdev, VirtIOHandleOutput cursor_cb, Error **errp) { + size_t output_idx; + VirtIOGPUOutputList *node; VirtIODevice *vdev = VIRTIO_DEVICE(qdev); VirtIOGPUBase *g = VIRTIO_GPU_BASE(qdev); int i; @@ -181,6 +194,20 @@ virtio_gpu_base_device_realize(DeviceState *qdev, return false; } + for (output_idx = 0, node = g->conf.outputs; + node; output_idx++, node = node->next) { + if (output_idx == g->conf.max_outputs) { + error_setg(errp, "invalid outputs > %d", g->conf.max_outputs); + return false; + } + if (node->value && node->value->name && + strlen(node->value->name) > EDID_NAME_MAX_LENGTH) { + error_setg(errp, "invalid output name '%s' > %d", + node->value->name, EDID_NAME_MAX_LENGTH); + return false; + } + } + if (virtio_gpu_virgl_enabled(g->conf)) { error_setg(&g->migration_blocker, "virgl is not yet migratable"); if (migrate_add_blocker(&g->migration_blocker, errp) < 0) { diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c index 61851cc..423c495 100644 --- a/hw/i386/acpi-build.c +++ b/hw/i386/acpi-build.c @@ -338,405 +338,6 @@ build_facs(GArray *table_data) g_array_append_vals(table_data, reserved, 40); /* Reserved */ } -Aml *aml_pci_device_dsm(void) -{ - Aml *method; - - method = aml_method("_DSM", 4, AML_SERIALIZED); - { - Aml *params = aml_local(0); - Aml *pkg = aml_package(2); - aml_append(pkg, aml_int(0)); - aml_append(pkg, aml_int(0)); - aml_append(method, aml_store(pkg, params)); - aml_append(method, - aml_store(aml_name("BSEL"), aml_index(params, aml_int(0)))); - aml_append(method, - aml_store(aml_name("ASUN"), aml_index(params, aml_int(1)))); - aml_append(method, - aml_return(aml_call5("PDSM", aml_arg(0), aml_arg(1), - aml_arg(2), aml_arg(3), params)) - ); - } - return method; -} - -static void build_append_pci_dsm_func0_common(Aml *ctx, Aml *retvar) -{ - Aml *UUID, *ifctx1; - uint8_t byte_list[1] = { 0 }; /* nothing supported yet */ - - aml_append(ctx, aml_store(aml_buffer(1, byte_list), retvar)); - /* - * PCI Firmware Specification 3.1 - * 4.6. _DSM Definitions for PCI - */ - UUID = aml_touuid("E5C937D0-3553-4D7A-9117-EA4D19C3434D"); - ifctx1 = aml_if(aml_lnot(aml_equal(aml_arg(0), UUID))); - { - /* call is for unsupported UUID, bail out */ - aml_append(ifctx1, aml_return(retvar)); - } - aml_append(ctx, ifctx1); - - ifctx1 = aml_if(aml_lless(aml_arg(1), aml_int(2))); - { - /* call is for unsupported REV, bail out */ - aml_append(ifctx1, aml_return(retvar)); - } - aml_append(ctx, ifctx1); -} - -static Aml *aml_pci_edsm(void) -{ - Aml *method, *ifctx; - Aml *zero = aml_int(0); - Aml *func = aml_arg(2); - Aml *ret = aml_local(0); - Aml *aidx = aml_local(1); - Aml *params = aml_arg(4); - - method = aml_method("EDSM", 5, AML_SERIALIZED); - - /* get supported functions */ - ifctx = aml_if(aml_equal(func, zero)); - { - /* 1: have supported functions */ - /* 7: support for function 7 */ - const uint8_t caps = 1 | BIT(7); - build_append_pci_dsm_func0_common(ifctx, ret); - aml_append(ifctx, aml_store(aml_int(caps), aml_index(ret, zero))); - aml_append(ifctx, aml_return(ret)); - } - aml_append(method, ifctx); - - /* handle specific functions requests */ - /* - * PCI Firmware Specification 3.1 - * 4.6.7. _DSM for Naming a PCI or PCI Express Device Under - * Operating Systems - */ - ifctx = aml_if(aml_equal(func, aml_int(7))); - { - Aml *pkg = aml_package(2); - aml_append(pkg, zero); - /* optional, if not impl. should return null string */ - aml_append(pkg, aml_string("%s", "")); - aml_append(ifctx, aml_store(pkg, ret)); - - /* - * IASL is fine when initializing Package with computational data, - * however it makes guest unhappy /it fails to process such AML/. - * So use runtime assignment to set acpi-index after initializer - * to make OSPM happy. - */ - aml_append(ifctx, - aml_store(aml_derefof(aml_index(params, aml_int(0))), aidx)); - aml_append(ifctx, aml_store(aidx, aml_index(ret, zero))); - aml_append(ifctx, aml_return(ret)); - } - aml_append(method, ifctx); - - return method; -} - -static Aml *aml_pci_static_endpoint_dsm(PCIDevice *pdev) -{ - Aml *method; - - g_assert(pdev->acpi_index != 0); - method = aml_method("_DSM", 4, AML_SERIALIZED); - { - Aml *params = aml_local(0); - Aml *pkg = aml_package(1); - aml_append(pkg, aml_int(pdev->acpi_index)); - aml_append(method, aml_store(pkg, params)); - aml_append(method, - aml_return(aml_call5("EDSM", aml_arg(0), aml_arg(1), - aml_arg(2), aml_arg(3), params)) - ); - } - return method; -} - -static void build_append_pcihp_notify_entry(Aml *method, int slot) -{ - Aml *if_ctx; - int32_t devfn = PCI_DEVFN(slot, 0); - - if_ctx = aml_if(aml_and(aml_arg(0), aml_int(0x1U << slot), NULL)); - aml_append(if_ctx, aml_notify(aml_name("S%.02X", devfn), aml_arg(1))); - aml_append(method, if_ctx); -} - -static bool is_devfn_ignored_generic(const int devfn, const PCIBus *bus) -{ - const PCIDevice *pdev = bus->devices[devfn]; - - if (PCI_FUNC(devfn)) { - if (IS_PCI_BRIDGE(pdev)) { - /* - * Ignore only hotplugged PCI bridges on !0 functions, but - * allow describing cold plugged bridges on all functions - */ - if (DEVICE(pdev)->hotplugged) { - return true; - } - } - } - return false; -} - -static bool is_devfn_ignored_hotplug(const int devfn, const PCIBus *bus) -{ - PCIDevice *pdev = bus->devices[devfn]; - if (pdev) { - return is_devfn_ignored_generic(devfn, bus) || - !DEVICE_GET_CLASS(pdev)->hotpluggable || - /* Cold plugged bridges aren't themselves hot-pluggable */ - (IS_PCI_BRIDGE(pdev) && !DEVICE(pdev)->hotplugged); - } else { /* non populated slots */ - /* - * hotplug is supported only for non-multifunction device - * so generate device description only for function 0 - */ - if (PCI_FUNC(devfn) || - (pci_bus_is_express(bus) && PCI_SLOT(devfn) > 0)) { - return true; - } - } - return false; -} - -void build_append_pcihp_slots(Aml *parent_scope, PCIBus *bus) -{ - int devfn; - Aml *dev, *notify_method = NULL, *method; - QObject *bsel = object_property_get_qobject(OBJECT(bus), - ACPI_PCIHP_PROP_BSEL, NULL); - uint64_t bsel_val = qnum_get_uint(qobject_to(QNum, bsel)); - qobject_unref(bsel); - - aml_append(parent_scope, aml_name_decl("BSEL", aml_int(bsel_val))); - notify_method = aml_method("DVNT", 2, AML_NOTSERIALIZED); - - for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { - int slot = PCI_SLOT(devfn); - int adr = slot << 16 | PCI_FUNC(devfn); - - if (is_devfn_ignored_hotplug(devfn, bus)) { - continue; - } - - if (bus->devices[devfn]) { - dev = aml_scope("S%.02X", devfn); - } else { - dev = aml_device("S%.02X", devfn); - aml_append(dev, aml_name_decl("_ADR", aml_int(adr))); - } - - /* - * Can't declare _SUN here for every device as it changes 'slot' - * enumeration order in linux kernel, so use another variable for it - */ - aml_append(dev, aml_name_decl("ASUN", aml_int(slot))); - aml_append(dev, aml_pci_device_dsm()); - - aml_append(dev, aml_name_decl("_SUN", aml_int(slot))); - /* add _EJ0 to make slot hotpluggable */ - method = aml_method("_EJ0", 1, AML_NOTSERIALIZED); - aml_append(method, - aml_call2("PCEJ", aml_name("BSEL"), aml_name("_SUN")) - ); - aml_append(dev, method); - - build_append_pcihp_notify_entry(notify_method, slot); - - /* device descriptor has been composed, add it into parent context */ - aml_append(parent_scope, dev); - } - aml_append(parent_scope, notify_method); -} - -void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) -{ - int devfn; - Aml *dev; - - for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { - /* ACPI spec: 1.0b: Table 6-2 _ADR Object Bus Types, PCI type */ - int adr = PCI_SLOT(devfn) << 16 | PCI_FUNC(devfn); - PCIDevice *pdev = bus->devices[devfn]; - - if (!pdev || is_devfn_ignored_generic(devfn, bus)) { - continue; - } - - /* start to compose PCI device descriptor */ - dev = aml_device("S%.02X", devfn); - aml_append(dev, aml_name_decl("_ADR", aml_int(adr))); - - call_dev_aml_func(DEVICE(bus->devices[devfn]), dev); - /* add _DSM if device has acpi-index set */ - if (pdev->acpi_index && - !object_property_get_bool(OBJECT(pdev), "hotpluggable", - &error_abort)) { - aml_append(dev, aml_pci_static_endpoint_dsm(pdev)); - } - - /* device descriptor has been composed, add it into parent context */ - aml_append(parent_scope, dev); - } -} - -static bool build_append_notification_callback(Aml *parent_scope, - const PCIBus *bus) -{ - Aml *method; - PCIBus *sec; - QObject *bsel; - int nr_notifiers = 0; - GQueue *pcnt_bus_list = g_queue_new(); - - QLIST_FOREACH(sec, &bus->child, sibling) { - Aml *br_scope = aml_scope("S%.02X", sec->parent_dev->devfn); - if (pci_bus_is_root(sec)) { - continue; - } - nr_notifiers = nr_notifiers + - build_append_notification_callback(br_scope, sec); - /* - * add new child scope to parent - * and keep track of bus that have PCNT, - * bus list is used later to call children PCNTs from this level PCNT - */ - if (nr_notifiers) { - g_queue_push_tail(pcnt_bus_list, sec); - aml_append(parent_scope, br_scope); - } - } - - /* - * Append PCNT method to notify about events on local and child buses. - * ps: hostbridge might not have hotplug (bsel) enabled but might have - * child bridges that do have bsel. - */ - method = aml_method("PCNT", 0, AML_NOTSERIALIZED); - - /* If bus supports hotplug select it and notify about local events */ - bsel = object_property_get_qobject(OBJECT(bus), ACPI_PCIHP_PROP_BSEL, NULL); - if (bsel) { - uint64_t bsel_val = qnum_get_uint(qobject_to(QNum, bsel)); - - aml_append(method, aml_store(aml_int(bsel_val), aml_name("BNUM"))); - aml_append(method, aml_call2("DVNT", aml_name("PCIU"), - aml_int(1))); /* Device Check */ - aml_append(method, aml_call2("DVNT", aml_name("PCID"), - aml_int(3))); /* Eject Request */ - nr_notifiers++; - } - - /* Notify about child bus events in any case */ - while ((sec = g_queue_pop_head(pcnt_bus_list))) { - aml_append(method, aml_name("^S%.02X.PCNT", sec->parent_dev->devfn)); - } - - aml_append(parent_scope, method); - qobject_unref(bsel); - g_queue_free(pcnt_bus_list); - return !!nr_notifiers; -} - -static Aml *aml_pci_pdsm(void) -{ - Aml *method, *ifctx, *ifctx1; - Aml *ret = aml_local(0); - Aml *caps = aml_local(1); - Aml *acpi_index = aml_local(2); - Aml *zero = aml_int(0); - Aml *one = aml_int(1); - Aml *not_supp = aml_int(0xFFFFFFFF); - Aml *func = aml_arg(2); - Aml *params = aml_arg(4); - Aml *bnum = aml_derefof(aml_index(params, aml_int(0))); - Aml *sunum = aml_derefof(aml_index(params, aml_int(1))); - - method = aml_method("PDSM", 5, AML_SERIALIZED); - - /* get supported functions */ - ifctx = aml_if(aml_equal(func, zero)); - { - build_append_pci_dsm_func0_common(ifctx, ret); - - aml_append(ifctx, aml_store(zero, caps)); - aml_append(ifctx, - aml_store(aml_call2("AIDX", bnum, sunum), acpi_index)); - /* - * advertise function 7 if device has acpi-index - * acpi_index values: - * 0: not present (default value) - * FFFFFFFF: not supported (old QEMU without PIDX reg) - * other: device's acpi-index - */ - ifctx1 = aml_if(aml_lnot( - aml_or(aml_equal(acpi_index, zero), - aml_equal(acpi_index, not_supp), NULL) - )); - { - /* have supported functions */ - aml_append(ifctx1, aml_or(caps, one, caps)); - /* support for function 7 */ - aml_append(ifctx1, - aml_or(caps, aml_shiftleft(one, aml_int(7)), caps)); - } - aml_append(ifctx, ifctx1); - - aml_append(ifctx, aml_store(caps, aml_index(ret, zero))); - aml_append(ifctx, aml_return(ret)); - } - aml_append(method, ifctx); - - /* handle specific functions requests */ - /* - * PCI Firmware Specification 3.1 - * 4.6.7. _DSM for Naming a PCI or PCI Express Device Under - * Operating Systems - */ - ifctx = aml_if(aml_equal(func, aml_int(7))); - { - Aml *pkg = aml_package(2); - - aml_append(ifctx, aml_store(aml_call2("AIDX", bnum, sunum), acpi_index)); - aml_append(ifctx, aml_store(pkg, ret)); - /* - * Windows calls func=7 without checking if it's available, - * as workaround Microsoft has suggested to return invalid for func7 - * Package, so return 2 elements package but only initialize elements - * when acpi_index is supported and leave them uninitialized, which - * leads elements to being Uninitialized ObjectType and should trip - * Windows into discarding result as an unexpected and prevent setting - * bogus 'PCI Label' on the device. - */ - ifctx1 = aml_if(aml_lnot(aml_lor( - aml_equal(acpi_index, zero), aml_equal(acpi_index, not_supp) - ))); - { - aml_append(ifctx1, aml_store(acpi_index, aml_index(ret, zero))); - /* - * optional, if not impl. should return null string - */ - aml_append(ifctx1, aml_store(aml_string("%s", ""), - aml_index(ret, one))); - } - aml_append(ifctx, ifctx1); - - aml_append(ifctx, aml_return(ret)); - } - - aml_append(method, ifctx); - return method; -} - /* * build_prt - Define interrupt routing rules * @@ -1227,112 +828,6 @@ static Aml *build_q35_dram_controller(const AcpiMcfgInfo *mcfg) return dev; } -static void build_x86_acpi_pci_hotplug(Aml *table, uint64_t pcihp_addr) -{ - Aml *scope; - Aml *field; - Aml *method; - - scope = aml_scope("_SB.PCI0"); - - aml_append(scope, - aml_operation_region("PCST", AML_SYSTEM_IO, aml_int(pcihp_addr), 0x08)); - field = aml_field("PCST", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); - aml_append(field, aml_named_field("PCIU", 32)); - aml_append(field, aml_named_field("PCID", 32)); - aml_append(scope, field); - - aml_append(scope, - aml_operation_region("SEJ", AML_SYSTEM_IO, - aml_int(pcihp_addr + ACPI_PCIHP_SEJ_BASE), 0x04)); - field = aml_field("SEJ", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); - aml_append(field, aml_named_field("B0EJ", 32)); - aml_append(scope, field); - - aml_append(scope, - aml_operation_region("BNMR", AML_SYSTEM_IO, - aml_int(pcihp_addr + ACPI_PCIHP_BNMR_BASE), 0x08)); - field = aml_field("BNMR", AML_DWORD_ACC, AML_NOLOCK, AML_WRITE_AS_ZEROS); - aml_append(field, aml_named_field("BNUM", 32)); - aml_append(field, aml_named_field("PIDX", 32)); - aml_append(scope, field); - - aml_append(scope, aml_mutex("BLCK", 0)); - - method = aml_method("PCEJ", 2, AML_NOTSERIALIZED); - aml_append(method, aml_acquire(aml_name("BLCK"), 0xFFFF)); - aml_append(method, aml_store(aml_arg(0), aml_name("BNUM"))); - aml_append(method, - aml_store(aml_shiftleft(aml_int(1), aml_arg(1)), aml_name("B0EJ"))); - aml_append(method, aml_release(aml_name("BLCK"))); - aml_append(method, aml_return(aml_int(0))); - aml_append(scope, method); - - method = aml_method("AIDX", 2, AML_NOTSERIALIZED); - aml_append(method, aml_acquire(aml_name("BLCK"), 0xFFFF)); - aml_append(method, aml_store(aml_arg(0), aml_name("BNUM"))); - aml_append(method, - aml_store(aml_shiftleft(aml_int(1), aml_arg(1)), aml_name("PIDX"))); - aml_append(method, aml_store(aml_name("PIDX"), aml_local(0))); - aml_append(method, aml_release(aml_name("BLCK"))); - aml_append(method, aml_return(aml_local(0))); - aml_append(scope, method); - - aml_append(scope, aml_pci_pdsm()); - - aml_append(table, scope); -} - -static Aml *build_q35_osc_method(bool enable_native_pcie_hotplug) -{ - Aml *if_ctx; - Aml *if_ctx2; - Aml *else_ctx; - Aml *method; - Aml *a_cwd1 = aml_name("CDW1"); - Aml *a_ctrl = aml_local(0); - - method = aml_method("_OSC", 4, AML_NOTSERIALIZED); - aml_append(method, aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1")); - - if_ctx = aml_if(aml_equal( - aml_arg(0), aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766"))); - aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2")); - aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3")); - - aml_append(if_ctx, aml_store(aml_name("CDW3"), a_ctrl)); - - /* - * Always allow native PME, AER (no dependencies) - * Allow SHPC (PCI bridges can have SHPC controller) - * Disable PCIe Native Hot-plug if ACPI PCI Hot-plug is enabled. - */ - aml_append(if_ctx, aml_and(a_ctrl, - aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl)); - - if_ctx2 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(1)))); - /* Unknown revision */ - aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x08), a_cwd1)); - aml_append(if_ctx, if_ctx2); - - if_ctx2 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), a_ctrl))); - /* Capabilities bits were masked */ - aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x10), a_cwd1)); - aml_append(if_ctx, if_ctx2); - - /* Update DWORD3 in the buffer */ - aml_append(if_ctx, aml_store(a_ctrl, aml_name("CDW3"))); - aml_append(method, if_ctx); - - else_ctx = aml_else(); - /* Unrecognized UUID */ - aml_append(else_ctx, aml_or(a_cwd1, aml_int(4), a_cwd1)); - aml_append(method, else_ctx); - - aml_append(method, aml_return(aml_arg(3))); - return method; -} - static void build_acpi0017(Aml *table) { Aml *dev, *scope, *method; @@ -1389,12 +884,12 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, dev = aml_device("PCI0"); aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A03"))); aml_append(dev, aml_name_decl("_UID", aml_int(pcmc->pci_root_uid))); - aml_append(dev, aml_pci_edsm()); + aml_append(dev, build_pci_bridge_edsm()); aml_append(sb_scope, dev); aml_append(dsdt, sb_scope); if (pm->pcihp_bridge_en || pm->pcihp_root_en) { - build_x86_acpi_pci_hotplug(dsdt, pm->pcihp_io_base); + build_acpi_pci_hotplug(dsdt, AML_SYSTEM_IO, pm->pcihp_io_base); } build_piix4_pci0_int(dsdt); } else if (q35) { @@ -1403,8 +898,8 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A08"))); aml_append(dev, aml_name_decl("_CID", aml_eisaid("PNP0A03"))); aml_append(dev, aml_name_decl("_UID", aml_int(pcmc->pci_root_uid))); - aml_append(dev, build_q35_osc_method(!pm->pcihp_bridge_en)); - aml_append(dev, aml_pci_edsm()); + aml_append(dev, build_pci_host_bridge_osc_method(!pm->pcihp_bridge_en)); + aml_append(dev, build_pci_bridge_edsm()); aml_append(sb_scope, dev); if (mcfg_valid) { aml_append(sb_scope, build_q35_dram_controller(&mcfg)); @@ -1438,7 +933,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, aml_append(dsdt, sb_scope); if (pm->pcihp_bridge_en) { - build_x86_acpi_pci_hotplug(dsdt, pm->pcihp_io_base); + build_acpi_pci_hotplug(dsdt, AML_SYSTEM_IO, pm->pcihp_io_base); } build_q35_pci0_int(dsdt); } @@ -1525,7 +1020,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, aml_append(dev, aml_name_decl("_CID", aml_eisaid("PNP0A03"))); /* Expander bridges do not have ACPI PCI Hot-plug enabled */ - aml_append(dev, build_q35_osc_method(true)); + aml_append(dev, build_pci_host_bridge_osc_method(true)); } else { aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A03"))); } @@ -1654,19 +1149,8 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, /* reserve PCIHP resources */ if (pm->pcihp_io_len && (pm->pcihp_bridge_en || pm->pcihp_root_en)) { - dev = aml_device("PHPR"); - aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06"))); - aml_append(dev, - aml_name_decl("_UID", aml_string("PCI Hotplug resources"))); - /* device present, functioning, decoding, not shown in UI */ - aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); - crs = aml_resource_template(); - aml_append(crs, - aml_io(AML_DECODE16, pm->pcihp_io_base, pm->pcihp_io_base, 1, - pm->pcihp_io_len) - ); - aml_append(dev, aml_name_decl("_CRS", crs)); - aml_append(scope, dev); + build_append_pcihp_resources(scope, + pm->pcihp_io_base, pm->pcihp_io_len); } aml_append(dsdt, scope); diff --git a/hw/i386/acpi-build.h b/hw/i386/acpi-build.h index 275ec05..8ba3c33 100644 --- a/hw/i386/acpi-build.h +++ b/hw/i386/acpi-build.h @@ -5,10 +5,6 @@ extern const struct AcpiGenericAddress x86_nvdimm_acpi_dsmio; -/* PCI Hot-plug registers' base. See docs/specs/acpi_pci_hotplug.rst */ -#define ACPI_PCIHP_SEJ_BASE 0x8 -#define ACPI_PCIHP_BNMR_BASE 0x10 - void acpi_setup(void); Object *acpi_get_i386_pci_host(void); diff --git a/hw/i386/amd_iommu.c b/hw/i386/amd_iommu.c index 963aa24..5a24c17 100644 --- a/hw/i386/amd_iommu.c +++ b/hw/i386/amd_iommu.c @@ -140,7 +140,7 @@ static void amdvi_writeq(AMDVIState *s, hwaddr addr, uint64_t val) { uint64_t romask = ldq_le_p(&s->romask[addr]); uint64_t w1cmask = ldq_le_p(&s->w1cmask[addr]); - uint32_t oldval = ldq_le_p(&s->mmior[addr]); + uint64_t oldval = ldq_le_p(&s->mmior[addr]); stq_le_p(&s->mmior[addr], ((oldval & romask) | (val & ~romask)) & ~(val & w1cmask)); } @@ -508,7 +508,7 @@ static void amdvi_inval_inttable(AMDVIState *s, uint64_t *cmd) static void iommu_inval_iotlb(AMDVIState *s, uint64_t *cmd) { - uint16_t devid = extract64(cmd[0], 0, 16); + uint16_t devid = cpu_to_le16(extract64(cmd[0], 0, 16)); if (extract64(cmd[1], 1, 1) || extract64(cmd[1], 3, 1) || extract64(cmd[1], 6, 6)) { amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4), @@ -521,7 +521,7 @@ static void iommu_inval_iotlb(AMDVIState *s, uint64_t *cmd) &devid); } else { amdvi_iotlb_remove_page(s, cpu_to_le64(extract64(cmd[1], 12, 52)) << 12, - cpu_to_le16(extract64(cmd[1], 0, 16))); + devid); } trace_amdvi_iotlb_inval(); } @@ -665,8 +665,8 @@ static inline void amdvi_handle_devtab_write(AMDVIState *s) uint64_t val = amdvi_readq(s, AMDVI_MMIO_DEVICE_TABLE); s->devtab = (val & AMDVI_MMIO_DEVTAB_BASE_MASK); - /* set device table length */ - s->devtab_len = ((val & AMDVI_MMIO_DEVTAB_SIZE_MASK) + 1 * + /* set device table length (i.e. number of entries table can hold) */ + s->devtab_len = (((val & AMDVI_MMIO_DEVTAB_SIZE_MASK) + 1) * (AMDVI_MMIO_DEVTAB_SIZE_UNIT / AMDVI_MMIO_DEVTAB_ENTRY_SIZE)); } @@ -848,9 +848,10 @@ static inline uint64_t amdvi_get_perms(uint64_t entry) static bool amdvi_validate_dte(AMDVIState *s, uint16_t devid, uint64_t *dte) { - if ((dte[0] & AMDVI_DTE_LOWER_QUAD_RESERVED) - || (dte[1] & AMDVI_DTE_MIDDLE_QUAD_RESERVED) - || (dte[2] & AMDVI_DTE_UPPER_QUAD_RESERVED) || dte[3]) { + if ((dte[0] & AMDVI_DTE_QUAD0_RESERVED) || + (dte[1] & AMDVI_DTE_QUAD1_RESERVED) || + (dte[2] & AMDVI_DTE_QUAD2_RESERVED) || + (dte[3] & AMDVI_DTE_QUAD3_RESERVED)) { amdvi_log_illegaldevtab_error(s, devid, s->devtab + devid * AMDVI_DEVTAB_ENTRY_SIZE, 0); diff --git a/hw/i386/amd_iommu.h b/hw/i386/amd_iommu.h index 5672bde..8b42913 100644 --- a/hw/i386/amd_iommu.h +++ b/hw/i386/amd_iommu.h @@ -25,6 +25,8 @@ #include "hw/i386/x86-iommu.h" #include "qom/object.h" +#define GENMASK64(h, l) (((~0ULL) >> (63 - (h) + (l))) << (l)) + /* Capability registers */ #define AMDVI_CAPAB_BAR_LOW 0x04 #define AMDVI_CAPAB_BAR_HIGH 0x08 @@ -66,34 +68,34 @@ #define AMDVI_MMIO_SIZE 0x4000 -#define AMDVI_MMIO_DEVTAB_SIZE_MASK ((1ULL << 12) - 1) -#define AMDVI_MMIO_DEVTAB_BASE_MASK (((1ULL << 52) - 1) & ~ \ - AMDVI_MMIO_DEVTAB_SIZE_MASK) +#define AMDVI_MMIO_DEVTAB_SIZE_MASK GENMASK64(8, 0) +#define AMDVI_MMIO_DEVTAB_BASE_MASK GENMASK64(51, 12) + #define AMDVI_MMIO_DEVTAB_ENTRY_SIZE 32 #define AMDVI_MMIO_DEVTAB_SIZE_UNIT 4096 /* some of this are similar but just for readability */ #define AMDVI_MMIO_CMDBUF_SIZE_BYTE (AMDVI_MMIO_COMMAND_BASE + 7) #define AMDVI_MMIO_CMDBUF_SIZE_MASK 0x0f -#define AMDVI_MMIO_CMDBUF_BASE_MASK AMDVI_MMIO_DEVTAB_BASE_MASK -#define AMDVI_MMIO_CMDBUF_HEAD_MASK (((1ULL << 19) - 1) & ~0x0f) -#define AMDVI_MMIO_CMDBUF_TAIL_MASK AMDVI_MMIO_EVTLOG_HEAD_MASK +#define AMDVI_MMIO_CMDBUF_BASE_MASK GENMASK64(51, 12) +#define AMDVI_MMIO_CMDBUF_HEAD_MASK GENMASK64(18, 4) +#define AMDVI_MMIO_CMDBUF_TAIL_MASK GENMASK64(18, 4) #define AMDVI_MMIO_EVTLOG_SIZE_BYTE (AMDVI_MMIO_EVENT_BASE + 7) -#define AMDVI_MMIO_EVTLOG_SIZE_MASK AMDVI_MMIO_CMDBUF_SIZE_MASK -#define AMDVI_MMIO_EVTLOG_BASE_MASK AMDVI_MMIO_CMDBUF_BASE_MASK -#define AMDVI_MMIO_EVTLOG_HEAD_MASK (((1ULL << 19) - 1) & ~0x0f) -#define AMDVI_MMIO_EVTLOG_TAIL_MASK AMDVI_MMIO_EVTLOG_HEAD_MASK +#define AMDVI_MMIO_EVTLOG_SIZE_MASK 0x0f +#define AMDVI_MMIO_EVTLOG_BASE_MASK GENMASK64(51, 12) +#define AMDVI_MMIO_EVTLOG_HEAD_MASK GENMASK64(18, 4) +#define AMDVI_MMIO_EVTLOG_TAIL_MASK GENMASK64(18, 4) -#define AMDVI_MMIO_PPRLOG_SIZE_BYTE (AMDVI_MMIO_EVENT_BASE + 7) -#define AMDVI_MMIO_PPRLOG_HEAD_MASK AMDVI_MMIO_EVTLOG_HEAD_MASK -#define AMDVI_MMIO_PPRLOG_TAIL_MASK AMDVI_MMIO_EVTLOG_HEAD_MASK -#define AMDVI_MMIO_PPRLOG_BASE_MASK AMDVI_MMIO_EVTLOG_BASE_MASK -#define AMDVI_MMIO_PPRLOG_SIZE_MASK AMDVI_MMIO_EVTLOG_SIZE_MASK +#define AMDVI_MMIO_PPRLOG_SIZE_BYTE (AMDVI_MMIO_PPR_BASE + 7) +#define AMDVI_MMIO_PPRLOG_SIZE_MASK 0x0f +#define AMDVI_MMIO_PPRLOG_BASE_MASK GENMASK64(51, 12) +#define AMDVI_MMIO_PPRLOG_HEAD_MASK GENMASK64(18, 4) +#define AMDVI_MMIO_PPRLOG_TAIL_MASK GENMASK64(18, 4) #define AMDVI_MMIO_EXCL_ENABLED_MASK (1ULL << 0) #define AMDVI_MMIO_EXCL_ALLOW_MASK (1ULL << 1) -#define AMDVI_MMIO_EXCL_LIMIT_MASK AMDVI_MMIO_DEVTAB_BASE_MASK +#define AMDVI_MMIO_EXCL_LIMIT_MASK GENMASK64(51, 12) #define AMDVI_MMIO_EXCL_LIMIT_LOW 0xfff /* mmio control register flags */ @@ -130,14 +132,14 @@ #define AMDVI_DEV_TRANSLATION_VALID (1ULL << 1) #define AMDVI_DEV_MODE_MASK 0x7 #define AMDVI_DEV_MODE_RSHIFT 9 -#define AMDVI_DEV_PT_ROOT_MASK 0xffffffffff000 +#define AMDVI_DEV_PT_ROOT_MASK GENMASK64(51, 12) #define AMDVI_DEV_PT_ROOT_RSHIFT 12 #define AMDVI_DEV_PERM_SHIFT 61 #define AMDVI_DEV_PERM_READ (1ULL << 61) #define AMDVI_DEV_PERM_WRITE (1ULL << 62) /* Device table entry bits 64:127 */ -#define AMDVI_DEV_DOMID_ID_MASK ((1ULL << 16) - 1) +#define AMDVI_DEV_DOMID_ID_MASK GENMASK64(15, 0) /* Event codes and flags, as stored in the info field */ #define AMDVI_EVENT_ILLEGAL_DEVTAB_ENTRY (0x1U << 12) @@ -162,9 +164,10 @@ #define AMDVI_FEATURE_PC (1ULL << 9) /* Perf counters */ /* reserved DTE bits */ -#define AMDVI_DTE_LOWER_QUAD_RESERVED 0x80300000000000fc -#define AMDVI_DTE_MIDDLE_QUAD_RESERVED 0x0000000000000100 -#define AMDVI_DTE_UPPER_QUAD_RESERVED 0x08f0000000000000 +#define AMDVI_DTE_QUAD0_RESERVED (GENMASK64(6, 2) | GENMASK64(63, 63)) +#define AMDVI_DTE_QUAD1_RESERVED 0 +#define AMDVI_DTE_QUAD2_RESERVED GENMASK64(53, 52) +#define AMDVI_DTE_QUAD3_RESERVED (GENMASK64(14, 0) | GENMASK64(53, 48)) /* AMDVI paging mode */ #define AMDVI_GATS_MODE (2ULL << 12) @@ -194,16 +197,12 @@ #define AMDVI_PAGE_SIZE (1ULL << AMDVI_PAGE_SHIFT) #define AMDVI_PAGE_SHIFT_4K 12 -#define AMDVI_PAGE_MASK_4K (~((1ULL << AMDVI_PAGE_SHIFT_4K) - 1)) - -#define AMDVI_MAX_VA_ADDR (48UL << 5) -#define AMDVI_MAX_PH_ADDR (40UL << 8) -#define AMDVI_MAX_GVA_ADDR (48UL << 15) +#define AMDVI_PAGE_MASK_4K GENMASK64(63, 12) -/* Completion Wait data size */ -#define AMDVI_COMPLETION_DATA_SIZE 8 +#define AMDVI_MAX_GVA_ADDR (2UL << 5) +#define AMDVI_MAX_PH_ADDR (40UL << 8) +#define AMDVI_MAX_VA_ADDR (48UL << 15) -#define AMDVI_COMMAND_SIZE 16 /* Completion Wait data size */ #define AMDVI_COMPLETION_DATA_SIZE 8 @@ -228,7 +227,7 @@ #define AMDVI_IR_INTCTL_PASS 1 #define AMDVI_IR_INTCTL_REMAP 2 -#define AMDVI_IR_PHYS_ADDR_MASK (((1ULL << 45) - 1) << 6) +#define AMDVI_IR_PHYS_ADDR_MASK GENMASK64(51, 6) /* MSI data 10:0 bits (section 2.2.5.1 Fig 14) */ #define AMDVI_IRTE_OFFSET 0x7ff diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c index 69d72ad..fe9a5f2 100644 --- a/hw/i386/intel_iommu.c +++ b/hw/i386/intel_iommu.c @@ -1987,9 +1987,9 @@ static int vtd_iova_to_flpte(IntelIOMMUState *s, VTDContextEntry *ce, uint32_t pasid) { dma_addr_t addr = vtd_get_iova_pgtbl_base(s, ce, pasid); - uint32_t level = vtd_get_iova_level(s, ce, pasid); uint32_t offset; uint64_t flpte, flag_ad = VTD_FL_A; + *flpte_level = vtd_get_iova_level(s, ce, pasid); if (!vtd_iova_fl_check_canonical(s, iova, ce, pasid)) { error_report_once("%s: detected non canonical IOVA (iova=0x%" PRIx64 "," @@ -1998,11 +1998,11 @@ static int vtd_iova_to_flpte(IntelIOMMUState *s, VTDContextEntry *ce, } while (true) { - offset = vtd_iova_level_offset(iova, level); + offset = vtd_iova_level_offset(iova, *flpte_level); flpte = vtd_get_pte(addr, offset); if (flpte == (uint64_t)-1) { - if (level == vtd_get_iova_level(s, ce, pasid)) { + if (*flpte_level == vtd_get_iova_level(s, ce, pasid)) { /* Invalid programming of pasid-entry */ return -VTD_FR_PASID_ENTRY_FSPTPTR_INV; } else { @@ -2028,15 +2028,15 @@ static int vtd_iova_to_flpte(IntelIOMMUState *s, VTDContextEntry *ce, if (is_write && !(flpte & VTD_FL_RW)) { return -VTD_FR_SM_WRITE; } - if (vtd_flpte_nonzero_rsvd(flpte, level)) { + if (vtd_flpte_nonzero_rsvd(flpte, *flpte_level)) { error_report_once("%s: detected flpte reserved non-zero " "iova=0x%" PRIx64 ", level=0x%" PRIx32 "flpte=0x%" PRIx64 ", pasid=0x%" PRIX32 ")", - __func__, iova, level, flpte, pasid); + __func__, iova, *flpte_level, flpte, pasid); return -VTD_FR_FS_PAGING_ENTRY_RSVD; } - if (vtd_is_last_pte(flpte, level) && is_write) { + if (vtd_is_last_pte(flpte, *flpte_level) && is_write) { flag_ad |= VTD_FL_D; } @@ -2044,14 +2044,13 @@ static int vtd_iova_to_flpte(IntelIOMMUState *s, VTDContextEntry *ce, return -VTD_FR_FS_BIT_UPDATE_FAILED; } - if (vtd_is_last_pte(flpte, level)) { + if (vtd_is_last_pte(flpte, *flpte_level)) { *flptep = flpte; - *flpte_level = level; return 0; } addr = vtd_get_pte_addr(flpte, aw_bits); - level--; + (*flpte_level)--; } } @@ -2092,7 +2091,8 @@ static bool vtd_do_iommu_translate(VTDAddressSpace *vtd_as, PCIBus *bus, uint8_t bus_num = pci_bus_num(bus); VTDContextCacheEntry *cc_entry; uint64_t pte, page_mask; - uint32_t level, pasid = vtd_as->pasid; + uint32_t level = UINT32_MAX; + uint32_t pasid = vtd_as->pasid; uint16_t source_id = PCI_BUILD_BDF(bus_num, devfn); int ret_fr; bool is_fpd_set = false; @@ -2251,14 +2251,19 @@ out: entry->iova = addr & page_mask; entry->translated_addr = vtd_get_pte_addr(pte, s->aw_bits) & page_mask; entry->addr_mask = ~page_mask; - entry->perm = access_flags; + entry->perm = (is_write ? access_flags : (access_flags & (~IOMMU_WO))); return true; error: vtd_iommu_unlock(s); entry->iova = 0; entry->translated_addr = 0; - entry->addr_mask = 0; + /* + * Set the mask for ATS (the range must be present even when the + * translation fails : PCIe rev 5 10.2.3.5) + */ + entry->addr_mask = (level != UINT32_MAX) ? + (~vtd_pt_level_page_mask(level)) : (~VTD_PAGE_MASK_4K); entry->perm = IOMMU_NONE; return false; } @@ -2503,6 +2508,7 @@ static void vtd_iotlb_page_invalidate_notify(IntelIOMMUState *s, .translated_addr = 0, .addr_mask = size - 1, .perm = IOMMU_NONE, + .pasid = vtd_as->pasid, }, }; memory_region_notify_iommu(&vtd_as->iommu, 0, event); @@ -3090,6 +3096,7 @@ static void do_invalidate_device_tlb(VTDAddressSpace *vtd_dev_as, event.entry.iova = addr; event.entry.perm = IOMMU_NONE; event.entry.translated_addr = 0; + event.entry.pasid = vtd_dev_as->pasid; memory_region_notify_iommu(&vtd_dev_as->iommu, 0, event); } @@ -3672,6 +3679,7 @@ static IOMMUTLBEntry vtd_iommu_translate(IOMMUMemoryRegion *iommu, hwaddr addr, IOMMUTLBEntry iotlb = { /* We'll fill in the rest later. */ .target_as = &address_space_memory, + .pasid = vtd_as->pasid, }; bool success; @@ -4587,7 +4595,7 @@ static void vtd_cap_init(IntelIOMMUState *s) } if (s->pasid) { - s->ecap |= VTD_ECAP_PASID; + s->ecap |= VTD_ECAP_PASID | VTD_ECAP_PSS; } } @@ -4730,10 +4738,118 @@ static AddressSpace *vtd_host_dma_iommu(PCIBus *bus, void *opaque, int devfn) return &vtd_as->as; } +static IOMMUTLBEntry vtd_iommu_ats_do_translate(IOMMUMemoryRegion *iommu, + hwaddr addr, + IOMMUAccessFlags flags) +{ + IOMMUTLBEntry entry; + VTDAddressSpace *vtd_as = container_of(iommu, VTDAddressSpace, iommu); + + if (vtd_is_interrupt_addr(addr)) { + vtd_report_ir_illegal_access(vtd_as, addr, flags & IOMMU_WO); + entry.target_as = &address_space_memory; + entry.iova = 0; + entry.translated_addr = 0; + entry.addr_mask = ~VTD_PAGE_MASK_4K; + entry.perm = IOMMU_NONE; + entry.pasid = PCI_NO_PASID; + } else { + entry = vtd_iommu_translate(iommu, addr, flags, 0); + } + + return entry; +} + +static ssize_t vtd_ats_request_translation(PCIBus *bus, void *opaque, + int devfn, uint32_t pasid, + bool priv_req, bool exec_req, + hwaddr addr, size_t length, + bool no_write, IOMMUTLBEntry *result, + size_t result_length, + uint32_t *err_count) +{ + IntelIOMMUState *s = opaque; + VTDAddressSpace *vtd_as; + IOMMUAccessFlags flags = IOMMU_ACCESS_FLAG_FULL(true, !no_write, exec_req, + priv_req, false, false); + ssize_t res_index = 0; + hwaddr target_address = addr + length; + IOMMUTLBEntry entry; + + vtd_as = vtd_find_add_as(s, bus, devfn, pasid); + *err_count = 0; + + while ((addr < target_address) && (res_index < result_length)) { + entry = vtd_iommu_ats_do_translate(&vtd_as->iommu, addr, flags); + entry.perm &= ~IOMMU_GLOBAL; /* Spec 4.1.2: Global Mapping never set */ + + if ((entry.perm & flags) != flags) { + *err_count += 1; /* Less than expected */ + } + + result[res_index] = entry; + res_index += 1; + addr = (addr & (~entry.addr_mask)) + (entry.addr_mask + 1); + } + + /* Buffer too small */ + if (addr < target_address) { + return -ENOMEM; + } + + return res_index; +} + +static void vtd_init_iotlb_notifier(PCIBus *bus, void *opaque, int devfn, + IOMMUNotifier *n, IOMMUNotify fn, + void *user_opaque) +{ + n->opaque = user_opaque; + iommu_notifier_init(n, fn, IOMMU_NOTIFIER_DEVIOTLB_EVENTS, 0, + HWADDR_MAX, 0); +} + +static void vtd_get_iotlb_info(void *opaque, uint8_t *addr_width, + uint32_t *min_page_size) +{ + IntelIOMMUState *s = opaque; + + *addr_width = s->aw_bits; + *min_page_size = VTD_PAGE_SIZE; +} + +static void vtd_register_iotlb_notifier(PCIBus *bus, void *opaque, + int devfn, uint32_t pasid, + IOMMUNotifier *n) +{ + IntelIOMMUState *s = opaque; + VTDAddressSpace *vtd_as; + + vtd_as = vtd_find_add_as(s, bus, devfn, pasid); + memory_region_register_iommu_notifier(MEMORY_REGION(&vtd_as->iommu), n, + &error_fatal); +} + +static void vtd_unregister_iotlb_notifier(PCIBus *bus, void *opaque, + int devfn, uint32_t pasid, + IOMMUNotifier *n) +{ + IntelIOMMUState *s = opaque; + VTDAddressSpace *vtd_as; + + vtd_as = vtd_find_add_as(s, bus, devfn, pasid); + memory_region_unregister_iommu_notifier(MEMORY_REGION(&vtd_as->iommu), n); +} + static PCIIOMMUOps vtd_iommu_ops = { .get_address_space = vtd_host_dma_iommu, .set_iommu_device = vtd_dev_set_iommu_device, .unset_iommu_device = vtd_dev_unset_iommu_device, + .get_iotlb_info = vtd_get_iotlb_info, + .init_iotlb_notifier = vtd_init_iotlb_notifier, + .register_iotlb_notifier = vtd_register_iotlb_notifier, + .unregister_iotlb_notifier = vtd_unregister_iotlb_notifier, + .ats_request_translation = vtd_ats_request_translation, }; static bool vtd_decide_config(IntelIOMMUState *s, Error **errp) diff --git a/hw/i386/intel_iommu_internal.h b/hw/i386/intel_iommu_internal.h index e8b211e..360e937 100644 --- a/hw/i386/intel_iommu_internal.h +++ b/hw/i386/intel_iommu_internal.h @@ -192,6 +192,7 @@ #define VTD_ECAP_SC (1ULL << 7) #define VTD_ECAP_MHMV (15ULL << 20) #define VTD_ECAP_SRS (1ULL << 31) +#define VTD_ECAP_PSS (7ULL << 35) /* limit: MemTxAttrs::pid */ #define VTD_ECAP_PASID (1ULL << 40) #define VTD_ECAP_SMTS (1ULL << 43) #define VTD_ECAP_SLTS (1ULL << 46) diff --git a/hw/i386/pc.c b/hw/i386/pc.c index 860346d..2f58e73 100644 --- a/hw/i386/pc.c +++ b/hw/i386/pc.c @@ -81,7 +81,10 @@ { "qemu64-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, },\ { "athlon-" TYPE_X86_CPU, "model-id", "QEMU Virtual CPU version " v, }, -GlobalProperty pc_compat_10_0[] = {}; +GlobalProperty pc_compat_10_0[] = { + { TYPE_X86_CPU, "x-consistent-cache", "false" }, + { TYPE_X86_CPU, "x-vendor-cpuid-only-v2", "false" }, +}; const size_t pc_compat_10_0_len = G_N_ELEMENTS(pc_compat_10_0); GlobalProperty pc_compat_9_2[] = {}; @@ -1827,6 +1830,18 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) object_class_property_add_bool(oc, "fd-bootchk", pc_machine_get_fd_bootchk, pc_machine_set_fd_bootchk); + +#if defined(CONFIG_IGVM) + object_class_property_add_link(oc, "igvm-cfg", + TYPE_IGVM_CFG, + offsetof(X86MachineState, igvm), + object_property_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_class_property_set_description(oc, "igvm-cfg", + "Set IGVM configuration"); +#endif + + } static const TypeInfo pc_machine_info = { diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c index ea7572e..a3285fb 100644 --- a/hw/i386/pc_piix.c +++ b/hw/i386/pc_piix.c @@ -366,6 +366,16 @@ static void pc_init1(MachineState *machine, const char *pci_type) x86_nvdimm_acpi_dsmio, x86ms->fw_cfg, OBJECT(pcms)); } + +#if defined(CONFIG_IGVM) + /* Apply guest state from IGVM if supplied */ + if (x86ms->igvm) { + if (IGVM_CFG_GET_CLASS(x86ms->igvm) + ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) { + g_assert_not_reached(); + } + } +#endif } typedef enum PCSouthBridgeOption { diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 33211b1..cf871cf 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -325,6 +325,16 @@ static void pc_q35_init(MachineState *machine) x86_nvdimm_acpi_dsmio, x86ms->fw_cfg, OBJECT(pcms)); } + +#if defined(CONFIG_IGVM) + /* Apply guest state from IGVM if supplied */ + if (x86ms->igvm) { + if (IGVM_CFG_GET_CLASS(x86ms->igvm) + ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) { + g_assert_not_reached(); + } + } +#endif } #define DEFINE_Q35_MACHINE(major, minor) \ diff --git a/hw/i386/pc_sysfw.c b/hw/i386/pc_sysfw.c index 821396c..1a12b63 100644 --- a/hw/i386/pc_sysfw.c +++ b/hw/i386/pc_sysfw.c @@ -220,7 +220,13 @@ void pc_system_firmware_init(PCMachineState *pcms, BlockBackend *pflash_blk[ARRAY_SIZE(pcms->flash)]; if (!pcmc->pci_enabled) { - x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, true); + /* + * If an IGVM file is specified then the firmware must be provided + * in the IGVM file. + */ + if (!X86_MACHINE(pcms)->igvm) { + x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, true); + } return; } @@ -240,8 +246,13 @@ void pc_system_firmware_init(PCMachineState *pcms, } if (!pflash_blk[0]) { - /* Machine property pflash0 not set, use ROM mode */ - x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, false); + /* + * Machine property pflash0 not set, use ROM mode unless using IGVM, + * in which case the firmware must be provided by the IGVM file. + */ + if (!X86_MACHINE(pcms)->igvm) { + x86_bios_rom_init(X86_MACHINE(pcms), "bios.bin", rom_memory, false); + } } else { if (kvm_enabled() && !kvm_readonly_mem_enabled()) { /* @@ -257,6 +268,20 @@ void pc_system_firmware_init(PCMachineState *pcms, } pc_system_flash_cleanup_unused(pcms); + + /* + * The user should not have specified any pflash devices when using IGVM + * to configure the guest. + */ + if (X86_MACHINE(pcms)->igvm) { + for (i = 0; i < ARRAY_SIZE(pcms->flash); i++) { + if (pcms->flash[i]) { + error_report("pflash devices cannot be configured when " + "using IGVM"); + exit(1); + } + } + } } void x86_firmware_configure(hwaddr gpa, void *ptr, int size) diff --git a/hw/intc/loongarch_extioi_kvm.c b/hw/intc/loongarch_extioi_kvm.c index 0133540..aa2e8c7 100644 --- a/hw/intc/loongarch_extioi_kvm.c +++ b/hw/intc/loongarch_extioi_kvm.c @@ -6,7 +6,6 @@ */ #include "qemu/osdep.h" -#include "qemu/typedefs.h" #include "hw/intc/loongarch_extioi.h" #include "linux/kvm.h" #include "qapi/error.h" diff --git a/hw/loongarch/virt-acpi-build.c b/hw/loongarch/virt-acpi-build.c index 2cd2d9d..8c2228a 100644 --- a/hw/loongarch/virt-acpi-build.c +++ b/hw/loongarch/virt-acpi-build.c @@ -557,7 +557,9 @@ static void acpi_build(AcpiBuildTables *tables, MachineState *machine) acpi_add_table(table_offsets, tables_blob); build_srat(tables_blob, tables->linker, machine); acpi_add_table(table_offsets, tables_blob); - spcr_setup(tables_blob, tables->linker, machine); + + if (machine->acpi_spcr_enabled) + spcr_setup(tables_blob, tables->linker, machine); if (machine->numa_state->num_nodes) { if (machine->numa_state->have_numa_distance) { diff --git a/hw/mem/cxl_type3.c b/hw/mem/cxl_type3.c index 94e7274..be609ff 100644 --- a/hw/mem/cxl_type3.c +++ b/hw/mem/cxl_type3.c @@ -8,6 +8,7 @@ * * SPDX-License-Identifier: GPL-v2-only */ +#include <math.h> #include "qemu/osdep.h" #include "qemu/units.h" @@ -225,10 +226,16 @@ static int ct3_build_cdat_table(CDATSubHeader ***cdat_table, void *priv) * future. */ for (i = 0; i < ct3d->dc.num_regions; i++) { + ct3d->dc.regions[i].nonvolatile = false; + ct3d->dc.regions[i].sharable = false; + ct3d->dc.regions[i].hw_managed_coherency = false; + ct3d->dc.regions[i].ic_specific_dc_management = false; + ct3d->dc.regions[i].rdonly = false; ct3_build_cdat_entries_for_mr(&(table[cur_ent]), dsmad_handle++, ct3d->dc.regions[i].len, - false, true, region_base); + ct3d->dc.regions[i].nonvolatile, + true, region_base); ct3d->dc.regions[i].dsmadhandle = dsmad_handle - 1; cur_ent += CT3_CDAT_NUM_ENTRIES; @@ -634,6 +641,8 @@ static bool cxl_create_dc_regions(CXLType3Dev *ct3d, Error **errp) uint64_t region_len; uint64_t decode_len; uint64_t blk_size = 2 * MiB; + /* Only 1 block size is supported for now. */ + uint64_t supported_blk_size_bitmask = blk_size; CXLDCRegion *region; MemoryRegion *mr; uint64_t dc_size; @@ -679,9 +688,11 @@ static bool cxl_create_dc_regions(CXLType3Dev *ct3d, Error **errp) .block_size = blk_size, /* dsmad_handle set when creating CDAT table entries */ .flags = 0, + .supported_blk_size_bitmask = supported_blk_size_bitmask, }; ct3d->dc.total_capacity += region->len; region->blk_bitmap = bitmap_new(region->len / region->block_size); + qemu_mutex_init(®ion->bitmap_lock); } QTAILQ_INIT(&ct3d->dc.extents); QTAILQ_INIT(&ct3d->dc.extents_pending); @@ -1010,6 +1021,7 @@ void ct3_set_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, return; } + QEMU_LOCK_GUARD(®ion->bitmap_lock); bitmap_set(region->blk_bitmap, (dpa - region->base) / region->block_size, len / region->block_size); } @@ -1036,6 +1048,7 @@ bool ct3_test_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, * if bits between [dpa, dpa + len) are all 1s, meaning the DPA range is * backed with DC extents, return true; else return false. */ + QEMU_LOCK_GUARD(®ion->bitmap_lock); return find_next_zero_bit(region->blk_bitmap, nr + nbits, nr) == nr + nbits; } @@ -1057,6 +1070,7 @@ void ct3_clear_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, nr = (dpa - region->base) / region->block_size; nbits = len / region->block_size; + QEMU_LOCK_GUARD(®ion->bitmap_lock); bitmap_clear(region->blk_bitmap, nr, nbits); } @@ -1576,9 +1590,9 @@ void qmp_cxl_inject_correctable_error(const char *path, CxlCorErrorType type, pcie_aer_inject_error(PCI_DEVICE(obj), &err); } -static void cxl_assign_event_header(CXLEventRecordHdr *hdr, - const QemuUUID *uuid, uint32_t flags, - uint8_t length, uint64_t timestamp) +void cxl_assign_event_header(CXLEventRecordHdr *hdr, + const QemuUUID *uuid, uint32_t flags, + uint8_t length, uint64_t timestamp) { st24_le_p(&hdr->flags, flags); hdr->length = length; @@ -1866,28 +1880,13 @@ void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log, } } -/* CXL r3.1 Table 8-50: Dynamic Capacity Event Record */ -static const QemuUUID dynamic_capacity_uuid = { - .data = UUID(0xca95afa7, 0xf183, 0x4018, 0x8c, 0x2f, - 0x95, 0x26, 0x8e, 0x10, 0x1a, 0x2a), -}; - -typedef enum CXLDCEventType { - DC_EVENT_ADD_CAPACITY = 0x0, - DC_EVENT_RELEASE_CAPACITY = 0x1, - DC_EVENT_FORCED_RELEASE_CAPACITY = 0x2, - DC_EVENT_REGION_CONFIG_UPDATED = 0x3, - DC_EVENT_ADD_CAPACITY_RSP = 0x4, - DC_EVENT_CAPACITY_RELEASED = 0x5, -} CXLDCEventType; - /* * Check whether the range [dpa, dpa + len - 1] has overlaps with extents in * the list. * Return value: return true if has overlaps; otherwise, return false */ -static bool cxl_extents_overlaps_dpa_range(CXLDCExtentList *list, - uint64_t dpa, uint64_t len) +bool cxl_extents_overlaps_dpa_range(CXLDCExtentList *list, + uint64_t dpa, uint64_t len) { CXLDCExtent *ent; Range range1, range2; @@ -1932,8 +1931,8 @@ bool cxl_extents_contains_dpa_range(CXLDCExtentList *list, return false; } -static bool cxl_extent_groups_overlaps_dpa_range(CXLDCExtentGroupList *list, - uint64_t dpa, uint64_t len) +bool cxl_extent_groups_overlaps_dpa_range(CXLDCExtentGroupList *list, + uint64_t dpa, uint64_t len) { CXLDCExtentGroup *group; @@ -1958,15 +1957,11 @@ static void qmp_cxl_process_dynamic_capacity_prescriptive(const char *path, CxlDynamicCapacityExtentList *records, Error **errp) { Object *obj; - CXLEventDynamicCapacity dCap = {}; - CXLEventRecordHdr *hdr = &dCap.hdr; CXLType3Dev *dcd; - uint8_t flags = 1 << CXL_EVENT_TYPE_INFO; uint32_t num_extents = 0; CxlDynamicCapacityExtentList *list; CXLDCExtentGroup *group = NULL; g_autofree CXLDCExtentRaw *extents = NULL; - uint8_t enc_log = CXL_EVENT_TYPE_DYNAMIC_CAP; uint64_t dpa, offset, len, block_size; g_autofree unsigned long *blk_bitmap = NULL; int i; @@ -2076,40 +2071,10 @@ static void qmp_cxl_process_dynamic_capacity_prescriptive(const char *path, } if (group) { cxl_extent_group_list_insert_tail(&dcd->dc.extents_pending, group); + dcd->dc.total_extent_count += num_extents; } - /* - * CXL r3.1 section 8.2.9.2.1.6: Dynamic Capacity Event Record - * - * All Dynamic Capacity event records shall set the Event Record Severity - * field in the Common Event Record Format to Informational Event. All - * Dynamic Capacity related events shall be logged in the Dynamic Capacity - * Event Log. - */ - cxl_assign_event_header(hdr, &dynamic_capacity_uuid, flags, sizeof(dCap), - cxl_device_get_timestamp(&dcd->cxl_dstate)); - - dCap.type = type; - /* FIXME: for now, validity flag is cleared */ - dCap.validity_flags = 0; - stw_le_p(&dCap.host_id, hid); - /* only valid for DC_REGION_CONFIG_UPDATED event */ - dCap.updated_region_id = 0; - for (i = 0; i < num_extents; i++) { - memcpy(&dCap.dynamic_capacity_extent, &extents[i], - sizeof(CXLDCExtentRaw)); - - dCap.flags = 0; - if (i < num_extents - 1) { - /* Set "More" flag */ - dCap.flags |= BIT(0); - } - - if (cxl_event_insert(&dcd->cxl_dstate, enc_log, - (CXLEventRecordRaw *)&dCap)) { - cxl_event_irq_assert(dcd); - } - } + cxl_create_dc_event_records_for_extents(dcd, type, extents, num_extents); } void qmp_cxl_add_dynamic_capacity(const char *path, uint16_t host_id, diff --git a/hw/microblaze/Kconfig b/hw/microblaze/Kconfig index b0214b2..72d8072 100644 --- a/hw/microblaze/Kconfig +++ b/hw/microblaze/Kconfig @@ -1,7 +1,7 @@ config PETALOGIX_S3ADSP1800 bool default y - depends on MICROBLAZE + depends on MICROBLAZE && FDT select PFLASH_CFI01 select XILINX select XILINX_AXI @@ -11,7 +11,7 @@ config PETALOGIX_S3ADSP1800 config PETALOGIX_ML605 bool default y - depends on MICROBLAZE + depends on MICROBLAZE && FDT select PFLASH_CFI01 select SERIAL_MM select SSI_M25P80 diff --git a/hw/mips/Kconfig b/hw/mips/Kconfig index b09c89a..f84fffc 100644 --- a/hw/mips/Kconfig +++ b/hw/mips/Kconfig @@ -76,7 +76,7 @@ config LOONGSON3V config MIPS_CPS bool - select MIPS_ITU + select MIPS_ITU if TCG config MIPS_BOSTON bool diff --git a/hw/mips/cps.c b/hw/mips/cps.c index 2a3ba3f..e47695e 100644 --- a/hw/mips/cps.c +++ b/hw/mips/cps.c @@ -24,7 +24,7 @@ #include "hw/mips/mips.h" #include "hw/qdev-clock.h" #include "hw/qdev-properties.h" -#include "system/kvm.h" +#include "system/tcg.h" #include "system/reset.h" qemu_irq get_cps_irq(MIPSCPSState *s, int pin_number) @@ -59,7 +59,7 @@ static bool cpu_mips_itu_supported(CPUMIPSState *env) { bool is_mt = (env->CP0_Config5 & (1 << CP0C5_VP)) || ase_mt_available(env); - return is_mt && !kvm_enabled(); + return is_mt && tcg_enabled(); } static void mips_cps_realize(DeviceState *dev, Error **errp) diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index c27285b..4e35657 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -119,6 +119,7 @@ config STM32L4X5_RCC config MIPS_ITU bool + depends on TCG config MPS2_FPGAIO bool diff --git a/hw/net/can/ctucan_core.c b/hw/net/can/ctucan_core.c index 17131a4..6bd99c4 100644 --- a/hw/net/can/ctucan_core.c +++ b/hw/net/can/ctucan_core.c @@ -28,7 +28,6 @@ #include "qemu/osdep.h" #include "qemu/log.h" -#include "qemu/bswap.h" #include "qemu/bitops.h" #include "hw/irq.h" #include "migration/vmstate.h" diff --git a/hw/net/lan9118.c b/hw/net/lan9118.c index 6dda1e5..3017e12 100644 --- a/hw/net/lan9118.c +++ b/hw/net/lan9118.c @@ -21,6 +21,7 @@ #include "hw/ptimer.h" #include "hw/qdev-properties.h" #include "qapi/error.h" +#include "qemu/bswap.h" #include "qemu/log.h" #include "qemu/module.h" #include <zlib.h> /* for crc32 */ diff --git a/hw/net/rtl8139.c b/hw/net/rtl8139.c index 654a087..324fb93 100644 --- a/hw/net/rtl8139.c +++ b/hw/net/rtl8139.c @@ -57,6 +57,7 @@ #include "system/dma.h" #include "qemu/module.h" #include "qemu/timer.h" +#include "qemu/bswap.h" #include "net/net.h" #include "net/eth.h" #include "system/system.h" diff --git a/hw/net/vhost_net-stub.c b/hw/net/vhost_net-stub.c index 72df6d7..7d49f82 100644 --- a/hw/net/vhost_net-stub.c +++ b/hw/net/vhost_net-stub.c @@ -13,7 +13,6 @@ #include "qemu/osdep.h" #include "net/net.h" #include "net/tap.h" -#include "net/vhost-user.h" #include "hw/virtio/virtio-net.h" #include "net/vhost_net.h" @@ -101,7 +100,7 @@ VHostNetState *get_vhost_net(NetClientState *nc) return 0; } -int vhost_set_vring_enable(NetClientState *nc, int enable) +int vhost_net_set_vring_enable(NetClientState *nc, int enable) { return 0; } diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c index 891f235..540492b 100644 --- a/hw/net/vhost_net.c +++ b/hw/net/vhost_net.c @@ -16,7 +16,6 @@ #include "qemu/osdep.h" #include "net/net.h" #include "net/tap.h" -#include "net/vhost-user.h" #include "net/vhost-vdpa.h" #include "standard-headers/linux/vhost_types.h" @@ -36,94 +35,9 @@ #include "hw/virtio/virtio-bus.h" #include "linux-headers/linux/vhost.h" - -/* Features supported by host kernel. */ -static const int kernel_feature_bits[] = { - VIRTIO_F_NOTIFY_ON_EMPTY, - VIRTIO_RING_F_INDIRECT_DESC, - VIRTIO_RING_F_EVENT_IDX, - VIRTIO_NET_F_MRG_RXBUF, - VIRTIO_F_VERSION_1, - VIRTIO_NET_F_MTU, - VIRTIO_F_IOMMU_PLATFORM, - VIRTIO_F_RING_PACKED, - VIRTIO_F_RING_RESET, - VIRTIO_F_IN_ORDER, - VIRTIO_F_NOTIFICATION_DATA, - VIRTIO_NET_F_RSC_EXT, - VIRTIO_NET_F_HASH_REPORT, - VHOST_INVALID_FEATURE_BIT -}; - -/* Features supported by others. */ -static const int user_feature_bits[] = { - VIRTIO_F_NOTIFY_ON_EMPTY, - VIRTIO_F_NOTIFICATION_DATA, - VIRTIO_RING_F_INDIRECT_DESC, - VIRTIO_RING_F_EVENT_IDX, - - VIRTIO_F_ANY_LAYOUT, - VIRTIO_F_VERSION_1, - VIRTIO_NET_F_CSUM, - VIRTIO_NET_F_GUEST_CSUM, - VIRTIO_NET_F_GSO, - VIRTIO_NET_F_GUEST_TSO4, - VIRTIO_NET_F_GUEST_TSO6, - VIRTIO_NET_F_GUEST_ECN, - VIRTIO_NET_F_GUEST_UFO, - VIRTIO_NET_F_HOST_TSO4, - VIRTIO_NET_F_HOST_TSO6, - VIRTIO_NET_F_HOST_ECN, - VIRTIO_NET_F_HOST_UFO, - VIRTIO_NET_F_MRG_RXBUF, - VIRTIO_NET_F_MTU, - VIRTIO_F_IOMMU_PLATFORM, - VIRTIO_F_RING_PACKED, - VIRTIO_F_RING_RESET, - VIRTIO_F_IN_ORDER, - VIRTIO_NET_F_RSS, - VIRTIO_NET_F_RSC_EXT, - VIRTIO_NET_F_HASH_REPORT, - VIRTIO_NET_F_GUEST_USO4, - VIRTIO_NET_F_GUEST_USO6, - VIRTIO_NET_F_HOST_USO, - - /* This bit implies RARP isn't sent by QEMU out of band */ - VIRTIO_NET_F_GUEST_ANNOUNCE, - - VIRTIO_NET_F_MQ, - - VHOST_INVALID_FEATURE_BIT -}; - -static const int *vhost_net_get_feature_bits(struct vhost_net *net) -{ - const int *feature_bits = 0; - - switch (net->nc->info->type) { - case NET_CLIENT_DRIVER_TAP: - feature_bits = kernel_feature_bits; - break; - case NET_CLIENT_DRIVER_VHOST_USER: - feature_bits = user_feature_bits; - break; -#ifdef CONFIG_VHOST_NET_VDPA - case NET_CLIENT_DRIVER_VHOST_VDPA: - feature_bits = vdpa_feature_bits; - break; -#endif - default: - error_report("Feature bits not defined for this type: %d", - net->nc->info->type); - break; - } - - return feature_bits; -} - uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features) { - return vhost_get_features(&net->dev, vhost_net_get_feature_bits(net), + return vhost_get_features(&net->dev, net->feature_bits, features); } int vhost_net_get_config(struct vhost_net *net, uint8_t *config, @@ -140,7 +54,7 @@ int vhost_net_set_config(struct vhost_net *net, const uint8_t *data, void vhost_net_ack_features(struct vhost_net *net, uint64_t features) { net->dev.acked_features = net->dev.backend_features; - vhost_ack_features(&net->dev, vhost_net_get_feature_bits(net), features); + vhost_ack_features(&net->dev, net->feature_bits, features); } uint64_t vhost_net_get_max_queues(VHostNetState *net) @@ -155,11 +69,11 @@ uint64_t vhost_net_get_acked_features(VHostNetState *net) void vhost_net_save_acked_features(NetClientState *nc) { -#ifdef CONFIG_VHOST_NET_USER - if (nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) { - vhost_user_save_acked_features(nc); + struct vhost_net *net = get_vhost_net(nc); + + if (net && net->save_acked_features) { + net->save_acked_features(nc); } -#endif } static void vhost_net_disable_notifiers_nvhosts(VirtIODevice *dev, @@ -329,6 +243,10 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options) } net->nc = options->net_backend; net->dev.nvqs = options->nvqs; + net->feature_bits = options->feature_bits; + net->save_acked_features = options->save_acked_features; + net->max_tx_queue_size = options->max_tx_queue_size; + net->is_vhost_user = options->is_vhost_user; net->dev.max_queues = 1; net->dev.vqs = net->vqs; @@ -372,9 +290,8 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options) } /* Set sane init value. Override when guest acks. */ -#ifdef CONFIG_VHOST_NET_USER - if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) { - features = vhost_user_get_acked_features(net->nc); + if (options->get_acked_features) { + features = options->get_acked_features(net->nc); if (~net->dev.features & features) { fprintf(stderr, "vhost lacks feature mask 0x%" PRIx64 " for backend\n", @@ -382,7 +299,6 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options) goto fail; } } -#endif vhost_net_ack_features(net, features); @@ -525,7 +441,7 @@ int vhost_net_start(VirtIODevice *dev, NetClientState *ncs, * because vhost user doesn't interrupt masking/unmasking * properly. */ - if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) { + if (net->is_vhost_user) { dev->use_guest_notifier_mask = false; } } @@ -551,7 +467,7 @@ int vhost_net_start(VirtIODevice *dev, NetClientState *ncs, if (peer->vring_enable) { /* restore vring enable state */ - r = vhost_set_vring_enable(peer, peer->vring_enable); + r = vhost_net_set_vring_enable(peer, peer->vring_enable); if (r < 0) { goto err_guest_notifiers; @@ -649,44 +565,21 @@ void vhost_net_config_mask(VHostNetState *net, VirtIODevice *dev, bool mask) { vhost_config_mask(&net->dev, dev, mask); } + VHostNetState *get_vhost_net(NetClientState *nc) { - VHostNetState *vhost_net = 0; - if (!nc) { return 0; } - switch (nc->info->type) { - case NET_CLIENT_DRIVER_TAP: - vhost_net = tap_get_vhost_net(nc); - /* - * tap_get_vhost_net() can return NULL if a tap net-device backend is - * created with 'vhost=off' option, 'vhostforce=off' or no vhost or - * vhostforce or vhostfd options at all. Please see net_init_tap_one(). - * Hence, we omit the assertion here. - */ - break; -#ifdef CONFIG_VHOST_NET_USER - case NET_CLIENT_DRIVER_VHOST_USER: - vhost_net = vhost_user_get_vhost_net(nc); - assert(vhost_net); - break; -#endif -#ifdef CONFIG_VHOST_NET_VDPA - case NET_CLIENT_DRIVER_VHOST_VDPA: - vhost_net = vhost_vdpa_get_vhost_net(nc); - assert(vhost_net); - break; -#endif - default: - break; + if (nc->info->get_vhost_net) { + return nc->info->get_vhost_net(nc); } - return vhost_net; + return NULL; } -int vhost_set_vring_enable(NetClientState *nc, int enable) +int vhost_net_set_vring_enable(NetClientState *nc, int enable) { VHostNetState *net = get_vhost_net(nc); const VhostOps *vhost_ops = net->dev.vhost_ops; diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index eb93607..c4c49b0 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -158,7 +158,7 @@ static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config) virtio_host_has_feature(vdev, VIRTIO_NET_F_RSS) ? VIRTIO_NET_RSS_MAX_TABLE_LEN : 1); virtio_stl_p(vdev, &netcfg.supported_hash_types, - VIRTIO_NET_RSS_SUPPORTED_HASHES); + n->rss_data.supported_hash_types); memcpy(config, &netcfg, n->config_size); /* @@ -670,34 +670,36 @@ static void virtio_net_set_mrg_rx_bufs(VirtIONet *n, int mergeable_rx_bufs, static int virtio_net_max_tx_queue_size(VirtIONet *n) { NetClientState *peer = n->nic_conf.peers.ncs[0]; + struct vhost_net *net; - /* - * Backends other than vhost-user or vhost-vdpa don't support max queue - * size. - */ if (!peer) { - return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE; + goto default_value; } - switch(peer->info->type) { - case NET_CLIENT_DRIVER_VHOST_USER: - case NET_CLIENT_DRIVER_VHOST_VDPA: - return VIRTQUEUE_MAX_SIZE; - default: - return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE; - }; + net = get_vhost_net(peer); + + if (!net || !net->max_tx_queue_size) { + goto default_value; + } + + return net->max_tx_queue_size; + +default_value: + return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE; } static int peer_attach(VirtIONet *n, int index) { NetClientState *nc = qemu_get_subqueue(n->nic, index); + struct vhost_net *net; if (!nc->peer) { return 0; } - if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) { - vhost_set_vring_enable(nc->peer, 1); + net = get_vhost_net(nc->peer); + if (net && net->is_vhost_user) { + vhost_net_set_vring_enable(nc->peer, 1); } if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) { @@ -714,13 +716,15 @@ static int peer_attach(VirtIONet *n, int index) static int peer_detach(VirtIONet *n, int index) { NetClientState *nc = qemu_get_subqueue(n->nic, index); + struct vhost_net *net; if (!nc->peer) { return 0; } - if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) { - vhost_set_vring_enable(nc->peer, 0); + net = get_vhost_net(nc->peer); + if (net && net->is_vhost_user) { + vhost_net_set_vring_enable(nc->peer, 0); } if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) { @@ -752,79 +756,6 @@ static void virtio_net_set_queue_pairs(VirtIONet *n) static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue); -static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, - Error **errp) -{ - VirtIONet *n = VIRTIO_NET(vdev); - NetClientState *nc = qemu_get_queue(n->nic); - - /* Firstly sync all virtio-net possible supported features */ - features |= n->host_features; - - virtio_add_feature(&features, VIRTIO_NET_F_MAC); - - if (!peer_has_vnet_hdr(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - - virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); - } - - if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); - } - - if (!peer_has_uso(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - } - - if (!get_vhost_net(nc->peer)) { - return features; - } - - if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); - } - features = vhost_net_get_features(get_vhost_net(nc->peer), features); - vdev->backend_features = features; - - if (n->mtu_bypass_backend && - (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { - features |= (1ULL << VIRTIO_NET_F_MTU); - } - - /* - * Since GUEST_ANNOUNCE is emulated the feature bit could be set without - * enabled. This happens in the vDPA case. - * - * Make sure the feature set is not incoherent, as the driver could refuse - * to start. - * - * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, - * helping guest to notify the new location with vDPA devices that does not - * support it. - */ - if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); - } - - return features; -} - static uint64_t virtio_net_bad_features(VirtIODevice *vdev) { uint64_t features = 0; @@ -1251,7 +1182,7 @@ static void rss_data_to_rss_config(struct VirtioNetRssData *data, { config->redirect = data->redirect; config->populate_hash = data->populate_hash; - config->hash_types = data->hash_types; + config->hash_types = data->runtime_hash_types; config->indirections_len = data->indirections_len; config->default_queue = data->default_queue; } @@ -1286,6 +1217,10 @@ static void virtio_net_detach_ebpf_rss(VirtIONet *n) static void virtio_net_commit_rss_config(VirtIONet *n) { + if (n->rss_data.peer_hash_available) { + return; + } + if (n->rss_data.enabled) { n->rss_data.enabled_software_rss = n->rss_data.populate_hash; if (n->rss_data.populate_hash) { @@ -1300,7 +1235,7 @@ static void virtio_net_commit_rss_config(VirtIONet *n) } trace_virtio_net_rss_enable(n, - n->rss_data.hash_types, + n->rss_data.runtime_hash_types, n->rss_data.indirections_len, sizeof(n->rss_data.key)); } else { @@ -1411,7 +1346,7 @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = (uint32_t)s; goto error; } - n->rss_data.hash_types = virtio_ldl_p(vdev, &cfg.hash_types); + n->rss_data.runtime_hash_types = virtio_ldl_p(vdev, &cfg.hash_types); n->rss_data.indirections_len = virtio_lduw_p(vdev, &cfg.indirection_table_mask); if (!do_rss) { @@ -1474,12 +1409,12 @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = temp.b; goto error; } - if (!temp.b && n->rss_data.hash_types) { + if (!temp.b && n->rss_data.runtime_hash_types) { err_msg = "No key provided"; err_value = 0; goto error; } - if (!temp.b && !n->rss_data.hash_types) { + if (!temp.b && !n->rss_data.runtime_hash_types) { virtio_net_disable_rss(n); return queue_pairs; } @@ -1881,7 +1816,7 @@ static int virtio_net_process_rss(NetClientState *nc, const uint8_t *buf, net_rx_pkt_set_protocols(pkt, &iov, 1, n->host_hdr_len); net_rx_pkt_get_protocols(pkt, &hasip4, &hasip6, &l4hdr_proto); net_hash_type = virtio_net_get_hash_type(hasip4, hasip6, l4hdr_proto, - n->rss_data.hash_types); + n->rss_data.runtime_hash_types); if (net_hash_type > NetPktRssIpV6UdpEx) { if (n->rss_data.populate_hash) { hdr->hash_value = VIRTIO_NET_HASH_REPORT_NONE; @@ -3022,11 +2957,10 @@ static void virtio_net_del_queue(VirtIONet *n, int index) virtio_del_queue(vdev, index * 2 + 1); } -static void virtio_net_change_num_queue_pairs(VirtIONet *n, int new_max_queue_pairs) +static void virtio_net_change_num_queues(VirtIONet *n, int new_num_queues) { VirtIODevice *vdev = VIRTIO_DEVICE(n); int old_num_queues = virtio_get_num_queues(vdev); - int new_num_queues = new_max_queue_pairs * 2 + 1; int i; assert(old_num_queues >= 3); @@ -3062,20 +2996,115 @@ static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue) int max = multiqueue ? n->max_queue_pairs : 1; n->multiqueue = multiqueue; - virtio_net_change_num_queue_pairs(n, max); + virtio_net_change_num_queues(n, max * 2 + 1); virtio_net_set_queue_pairs(n); } -static int virtio_net_pre_load_queues(VirtIODevice *vdev) +static int virtio_net_pre_load_queues(VirtIODevice *vdev, uint32_t n) { - virtio_net_set_multiqueue(VIRTIO_NET(vdev), - virtio_has_feature(vdev->guest_features, VIRTIO_NET_F_RSS) || - virtio_has_feature(vdev->guest_features, VIRTIO_NET_F_MQ)); + virtio_net_change_num_queues(VIRTIO_NET(vdev), n); return 0; } +static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, + Error **errp) +{ + VirtIONet *n = VIRTIO_NET(vdev); + NetClientState *nc = qemu_get_queue(n->nic); + uint32_t supported_hash_types = n->rss_data.supported_hash_types; + uint32_t peer_hash_types = n->rss_data.peer_hash_types; + bool use_own_hash = + (supported_hash_types & VIRTIO_NET_RSS_SUPPORTED_HASHES) == + supported_hash_types; + bool use_peer_hash = + n->rss_data.peer_hash_available && + (supported_hash_types & peer_hash_types) == supported_hash_types; + + /* Firstly sync all virtio-net possible supported features */ + features |= n->host_features; + + virtio_add_feature(&features, VIRTIO_NET_F_MAC); + + if (!peer_has_vnet_hdr(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + } + + if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); + } + + if (!peer_has_uso(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + } + + if (!get_vhost_net(nc->peer)) { + if (!use_own_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + virtio_net_load_ebpf(n, errp); + } + + return features; + } + + if (!use_peer_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + + if (!use_own_hash || !virtio_net_attach_ebpf_to_backend(n->nic, -1)) { + if (!virtio_net_load_ebpf(n, errp)) { + return features; + } + + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } + } + + features = vhost_net_get_features(get_vhost_net(nc->peer), features); + vdev->backend_features = features; + + if (n->mtu_bypass_backend && + (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { + features |= (1ULL << VIRTIO_NET_F_MTU); + } + + /* + * Since GUEST_ANNOUNCE is emulated the feature bit could be set without + * enabled. This happens in the vDPA case. + * + * Make sure the feature set is not incoherent, as the driver could refuse + * to start. + * + * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, + * helping guest to notify the new location with vDPA devices that does not + * support it. + */ + if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); + } + + return features; +} + static int virtio_net_post_load_device(void *opaque, int version_id) { VirtIONet *n = opaque; @@ -3314,6 +3343,17 @@ static const VMStateDescription vmstate_virtio_net_has_vnet = { }, }; +static int virtio_net_rss_post_load(void *opaque, int version_id) +{ + VirtIONet *n = VIRTIO_NET(opaque); + + if (version_id == 1) { + n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; + } + + return 0; +} + static bool virtio_net_rss_needed(void *opaque) { return VIRTIO_NET(opaque)->rss_data.enabled; @@ -3321,14 +3361,16 @@ static bool virtio_net_rss_needed(void *opaque) static const VMStateDescription vmstate_virtio_net_rss = { .name = "virtio-net-device/rss", - .version_id = 1, + .version_id = 2, .minimum_version_id = 1, + .post_load = virtio_net_rss_post_load, .needed = virtio_net_rss_needed, .fields = (const VMStateField[]) { VMSTATE_BOOL(rss_data.enabled, VirtIONet), VMSTATE_BOOL(rss_data.redirect, VirtIONet), VMSTATE_BOOL(rss_data.populate_hash, VirtIONet), - VMSTATE_UINT32(rss_data.hash_types, VirtIONet), + VMSTATE_UINT32(rss_data.runtime_hash_types, VirtIONet), + VMSTATE_UINT32_V(rss_data.supported_hash_types, VirtIONet, 2), VMSTATE_UINT16(rss_data.indirections_len, VirtIONet), VMSTATE_UINT16(rss_data.default_queue, VirtIONet), VMSTATE_UINT8_ARRAY(rss_data.key, VirtIONet, @@ -3915,8 +3957,17 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) net_rx_pkt_init(&n->rx_pkt); - if (virtio_has_feature(n->host_features, VIRTIO_NET_F_RSS)) { - virtio_net_load_ebpf(n, errp); + if (qemu_get_vnet_hash_supported_types(qemu_get_queue(n->nic)->peer, + &n->rss_data.peer_hash_types)) { + n->rss_data.peer_hash_available = true; + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + (n->rss_data.specified_hash_types.auto_bits & + n->rss_data.peer_hash_types); + } else { + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + n->rss_data.specified_hash_types.auto_bits; } } @@ -4133,6 +4184,42 @@ static const Property virtio_net_properties[] = { VIRTIO_NET_F_GUEST_USO6, true), DEFINE_PROP_BIT64("host_uso", VirtIONet, host_features, VIRTIO_NET_F_HOST_USO, true), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6_EX - 1, + ON_OFF_AUTO_AUTO), }; static void virtio_net_class_init(ObjectClass *klass, const void *data) diff --git a/hw/net/vmxnet3.c b/hw/net/vmxnet3.c index 7c0ca56..af73aa8 100644 --- a/hw/net/vmxnet3.c +++ b/hw/net/vmxnet3.c @@ -22,7 +22,6 @@ #include "net/tap.h" #include "net/checksum.h" #include "system/system.h" -#include "qemu/bswap.h" #include "qemu/log.h" #include "qemu/module.h" #include "hw/pci/msix.h" diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig index 35c0415..9824fa1 100644 --- a/hw/pci-host/Kconfig +++ b/hw/pci-host/Kconfig @@ -54,6 +54,7 @@ config PCI_EXPRESS_Q35 config PCI_EXPRESS_GENERIC_BRIDGE bool select PCI_EXPRESS + imply ACPI_PCI config PCI_EXPRESS_XILINX bool diff --git a/hw/pci-host/gpex-acpi.c b/hw/pci-host/gpex-acpi.c index 0aba47c..952a0ac 100644 --- a/hw/pci-host/gpex-acpi.c +++ b/hw/pci-host/gpex-acpi.c @@ -1,5 +1,6 @@ #include "qemu/osdep.h" #include "hw/acpi/aml-build.h" +#include "hw/acpi/pci.h" #include "hw/pci-host/gpex.h" #include "hw/arm/virt.h" #include "hw/pci/pci_bus.h" @@ -50,61 +51,10 @@ static void acpi_dsdt_add_pci_route_table(Aml *dev, uint32_t irq, } } -static void acpi_dsdt_add_pci_osc(Aml *dev) +static Aml *build_pci_host_bridge_dsm_method(void) { - Aml *method, *UUID, *ifctx, *ifctx1, *elsectx, *buf; - - /* Declare an _OSC (OS Control Handoff) method */ - aml_append(dev, aml_name_decl("SUPP", aml_int(0))); - aml_append(dev, aml_name_decl("CTRL", aml_int(0))); - method = aml_method("_OSC", 4, AML_NOTSERIALIZED); - aml_append(method, - aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1")); - - /* PCI Firmware Specification 3.0 - * 4.5.1. _OSC Interface for PCI Host Bridge Devices - * The _OSC interface for a PCI/PCI-X/PCI Express hierarchy is - * identified by the Universal Unique IDentifier (UUID) - * 33DB4D5B-1FF7-401C-9657-7441C03DD766 - */ - UUID = aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766"); - ifctx = aml_if(aml_equal(aml_arg(0), UUID)); - aml_append(ifctx, - aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2")); - aml_append(ifctx, - aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3")); - aml_append(ifctx, aml_store(aml_name("CDW2"), aml_name("SUPP"))); - aml_append(ifctx, aml_store(aml_name("CDW3"), aml_name("CTRL"))); - - /* - * Allow OS control for all 5 features: - * PCIeHotplug SHPCHotplug PME AER PCIeCapability. - */ - aml_append(ifctx, aml_and(aml_name("CTRL"), aml_int(0x1F), - aml_name("CTRL"))); - - ifctx1 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(0x1)))); - aml_append(ifctx1, aml_or(aml_name("CDW1"), aml_int(0x08), - aml_name("CDW1"))); - aml_append(ifctx, ifctx1); - - ifctx1 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), aml_name("CTRL")))); - aml_append(ifctx1, aml_or(aml_name("CDW1"), aml_int(0x10), - aml_name("CDW1"))); - aml_append(ifctx, ifctx1); - - aml_append(ifctx, aml_store(aml_name("CTRL"), aml_name("CDW3"))); - aml_append(ifctx, aml_return(aml_arg(3))); - aml_append(method, ifctx); - - elsectx = aml_else(); - aml_append(elsectx, aml_or(aml_name("CDW1"), aml_int(4), - aml_name("CDW1"))); - aml_append(elsectx, aml_return(aml_arg(3))); - aml_append(method, elsectx); - aml_append(dev, method); - - method = aml_method("_DSM", 4, AML_NOTSERIALIZED); + Aml *method = aml_method("_DSM", 4, AML_NOTSERIALIZED); + Aml *UUID, *ifctx, *ifctx1, *buf; /* PCI Firmware Specification 3.0 * 4.6.1. _DSM for PCI Express Slot Information @@ -123,7 +73,16 @@ static void acpi_dsdt_add_pci_osc(Aml *dev) byte_list[0] = 0; buf = aml_buffer(1, byte_list); aml_append(method, aml_return(buf)); - aml_append(dev, method); + return method; +} + +static void acpi_dsdt_add_host_bridge_methods(Aml *dev, + bool enable_native_pcie_hotplug) +{ + /* Declare an _OSC (OS Control Handoff) method */ + aml_append(dev, + build_pci_host_bridge_osc_method(enable_native_pcie_hotplug)); + aml_append(dev, build_pci_host_bridge_dsm_method()); } void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg) @@ -192,7 +151,8 @@ void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg) if (is_cxl) { build_cxl_osc_method(dev); } else { - acpi_dsdt_add_pci_osc(dev); + /* pxb bridges do not have ACPI PCI Hot-plug enabled */ + acpi_dsdt_add_host_bridge_methods(dev, true); } aml_append(scope, dev); @@ -267,7 +227,7 @@ void acpi_dsdt_add_gpex(Aml *scope, struct GPEXConfig *cfg) } aml_append(dev, aml_name_decl("_CRS", rbuf)); - acpi_dsdt_add_pci_osc(dev); + acpi_dsdt_add_host_bridge_methods(dev, cfg->pci_native_hotplug); Aml *dev_res0 = aml_device("%s", "RES0"); aml_append(dev_res0, aml_name_decl("_HID", aml_string("PNP0C02"))); diff --git a/hw/pci-host/gt64120.c b/hw/pci-host/gt64120.c index b12a256..b1d96f6 100644 --- a/hw/pci-host/gt64120.c +++ b/hw/pci-host/gt64120.c @@ -28,6 +28,7 @@ #include "qapi/error.h" #include "qemu/units.h" #include "qemu/log.h" +#include "qemu/bswap.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" #include "hw/pci/pci_device.h" diff --git a/hw/pci-host/pnv_phb3.c b/hw/pci-host/pnv_phb3.c index a4335f4..5d8383f 100644 --- a/hw/pci-host/pnv_phb3.c +++ b/hw/pci-host/pnv_phb3.c @@ -8,6 +8,7 @@ */ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qemu/bswap.h" #include "qapi/visitor.h" #include "qapi/error.h" #include "hw/pci-host/pnv_phb3_regs.h" diff --git a/hw/pci-host/pnv_phb4.c b/hw/pci-host/pnv_phb4.c index 77ea352..1899205 100644 --- a/hw/pci-host/pnv_phb4.c +++ b/hw/pci-host/pnv_phb4.c @@ -8,6 +8,7 @@ */ #include "qemu/osdep.h" #include "qemu/log.h" +#include "qemu/bswap.h" #include "qapi/visitor.h" #include "qapi/error.h" #include "target/ppc/cpu.h" diff --git a/hw/pci-host/ppce500.c b/hw/pci-host/ppce500.c index 52269b0..975d191 100644 --- a/hw/pci-host/ppce500.c +++ b/hw/pci-host/ppce500.c @@ -20,7 +20,6 @@ #include "migration/vmstate.h" #include "hw/pci/pci_device.h" #include "hw/pci/pci_host.h" -#include "qemu/bswap.h" #include "hw/pci-host/ppce500.h" #include "qom/object.h" diff --git a/hw/pci-host/sh_pci.c b/hw/pci-host/sh_pci.c index de8f6a8..62fb945 100644 --- a/hw/pci-host/sh_pci.c +++ b/hw/pci-host/sh_pci.c @@ -28,7 +28,6 @@ #include "hw/irq.h" #include "hw/pci/pci_device.h" #include "hw/pci/pci_host.h" -#include "qemu/bswap.h" #include "qemu/module.h" #include "qom/object.h" diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c index 8b5683d..ee1416d 100644 --- a/hw/riscv/virt-acpi-build.c +++ b/hw/riscv/virt-acpi-build.c @@ -894,7 +894,10 @@ static void virt_acpi_build(RISCVVirtState *s, AcpiBuildTables *tables) } acpi_add_table(table_offsets, tables_blob); - spcr_setup(tables_blob, tables->linker, s); + + if (ms->acpi_spcr_enabled) { + spcr_setup(tables_blob, tables->linker, s); + } acpi_add_table(table_offsets, tables_blob); { diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c index b5dddb2..a3bb5aa 100644 --- a/hw/s390x/s390-pci-inst.c +++ b/hw/s390x/s390-pci-inst.c @@ -16,6 +16,7 @@ #include "exec/target_page.h" #include "system/memory.h" #include "qemu/error-report.h" +#include "qemu/bswap.h" #include "system/hw_accel.h" #include "hw/boards.h" #include "hw/pci/pci_device.h" diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c index f24991f..1d264c4 100644 --- a/hw/scsi/esp.c +++ b/hw/scsi/esp.c @@ -275,6 +275,7 @@ static int esp_select(ESPState *s) if (!s->current_dev) { /* No such drive */ s->rregs[ESP_RSTAT] = 0; + s->asc_mode = ESP_ASC_MODE_DIS; s->rregs[ESP_RINTR] = INTR_DC; esp_raise_irq(s); return -1; @@ -284,6 +285,7 @@ static int esp_select(ESPState *s) * Note that we deliberately don't raise the IRQ here: this will be done * either in esp_transfer_data() or esp_command_complete() */ + s->asc_mode = ESP_ASC_MODE_INI; return 0; } @@ -308,6 +310,7 @@ static void do_command_phase(ESPState *s) if (!current_lun) { /* No such drive */ s->rregs[ESP_RSTAT] = 0; + s->asc_mode = ESP_ASC_MODE_DIS; s->rregs[ESP_RINTR] = INTR_DC; s->rregs[ESP_RSEQ] = SEQ_0; esp_raise_irq(s); @@ -487,8 +490,10 @@ static void esp_do_dma(ESPState *s) case STAT_MO: if (s->dma_memory_read) { len = MIN(len, fifo8_num_free(&s->cmdfifo)); - s->dma_memory_read(s->dma_opaque, buf, len); - esp_set_tc(s, esp_get_tc(s) - len); + if (len) { + s->dma_memory_read(s->dma_opaque, buf, len); + esp_set_tc(s, esp_get_tc(s) - len); + } } else { len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo)); len = MIN(fifo8_num_free(&s->cmdfifo), len); @@ -541,9 +546,11 @@ static void esp_do_dma(ESPState *s) trace_esp_do_dma(cmdlen, len); if (s->dma_memory_read) { len = MIN(len, fifo8_num_free(&s->cmdfifo)); - s->dma_memory_read(s->dma_opaque, buf, len); - fifo8_push_all(&s->cmdfifo, buf, len); - esp_set_tc(s, esp_get_tc(s) - len); + if (len) { + s->dma_memory_read(s->dma_opaque, buf, len); + fifo8_push_all(&s->cmdfifo, buf, len); + esp_set_tc(s, esp_get_tc(s) - len); + } } else { len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo)); len = MIN(fifo8_num_free(&s->cmdfifo), len); @@ -572,8 +579,10 @@ static void esp_do_dma(ESPState *s) switch (s->rregs[ESP_CMD]) { case CMD_TI | CMD_DMA: if (s->dma_memory_read) { - s->dma_memory_read(s->dma_opaque, s->async_buf, len); - esp_set_tc(s, esp_get_tc(s) - len); + if (len) { + s->dma_memory_read(s->dma_opaque, s->async_buf, len); + esp_set_tc(s, esp_get_tc(s) - len); + } } else { /* Copy FIFO data to device */ len = MIN(s->async_len, ESP_FIFO_SZ); @@ -625,7 +634,9 @@ static void esp_do_dma(ESPState *s) switch (s->rregs[ESP_CMD]) { case CMD_TI | CMD_DMA: if (s->dma_memory_write) { - s->dma_memory_write(s->dma_opaque, s->async_buf, len); + if (len) { + s->dma_memory_write(s->dma_opaque, s->async_buf, len); + } } else { /* Copy device data to FIFO */ len = MIN(len, fifo8_num_free(&s->fifo)); @@ -675,6 +686,7 @@ static void esp_do_dma(ESPState *s) buf[0] = s->status; if (s->dma_memory_write) { + /* Length already non-zero */ s->dma_memory_write(s->dma_opaque, buf, len); } else { esp_fifo_push_buf(s, buf, len); @@ -709,6 +721,7 @@ static void esp_do_dma(ESPState *s) buf[0] = 0; if (s->dma_memory_write) { + /* Length already non-zero */ s->dma_memory_write(s->dma_opaque, buf, len); } else { esp_fifo_push_buf(s, buf, len); @@ -1012,6 +1025,7 @@ void esp_transfer_data(SCSIRequest *req, uint32_t len) */ s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC; s->rregs[ESP_RSEQ] = SEQ_CD; + esp_raise_irq(s); break; case CMD_SELATNS | CMD_DMA: @@ -1022,20 +1036,21 @@ void esp_transfer_data(SCSIRequest *req, uint32_t len) */ s->rregs[ESP_RINTR] |= INTR_BS; s->rregs[ESP_RSEQ] = SEQ_MO; + esp_raise_irq(s); break; case CMD_TI | CMD_DMA: case CMD_TI: /* - * Bus service interrupt raised because of initial change to - * DATA phase + * If the final COMMAND phase data was transferred using a TI + * command, clear ESP_CMD to terminate the TI command and raise + * the completion interrupt */ s->rregs[ESP_CMD] = 0; s->rregs[ESP_RINTR] |= INTR_BS; + esp_raise_irq(s); break; } - - esp_raise_irq(s); } /* @@ -1090,6 +1105,7 @@ void esp_hard_reset(ESPState *s) fifo8_reset(&s->cmdfifo); s->dma = 0; s->dma_cb = NULL; + s->asc_mode = ESP_ASC_MODE_DIS; s->rregs[ESP_CFG1] = 7; } @@ -1113,6 +1129,38 @@ static void parent_esp_reset(ESPState *s, int irq, int level) } } +static bool esp_cmd_is_valid(ESPState *s, uint8_t cmd) +{ + uint8_t cmd_group = (cmd & CMD_GRP_MASK) >> 4; + + /* Always allow misc commands */ + if (cmd_group == CMD_GRP_MISC) { + return true; + } + + switch (s->asc_mode) { + case ESP_ASC_MODE_DIS: + /* Disconnected mode: only allow disconnected commands */ + if (cmd_group == CMD_GRP_DISC) { + return true; + } + break; + + case ESP_ASC_MODE_INI: + /* Initiator mode: allow initiator commands */ + if (cmd_group == CMD_GRP_INIT) { + return true; + } + break; + + default: + g_assert_not_reached(); + } + + trace_esp_invalid_cmd(cmd, s->asc_mode); + return false; +} + static void esp_run_cmd(ESPState *s) { uint8_t cmd = s->rregs[ESP_CMD]; @@ -1158,6 +1206,7 @@ static void esp_run_cmd(ESPState *s) break; case CMD_MSGACC: trace_esp_mem_writeb_cmd_msgacc(cmd); + s->asc_mode = ESP_ASC_MODE_DIS; s->rregs[ESP_RINTR] |= INTR_DC; s->rregs[ESP_RSEQ] = 0; s->rregs[ESP_RFLAGS] = 0; @@ -1268,6 +1317,11 @@ void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val) break; case ESP_CMD: s->rregs[saddr] = val; + if (!esp_cmd_is_valid(s, s->rregs[saddr])) { + s->rregs[ESP_RSTAT] |= INTR_IL; + esp_raise_irq(s); + break; + } esp_run_cmd(s); break; case ESP_WBUSID ... ESP_WSYNO: @@ -1325,6 +1379,14 @@ static bool esp_is_between_version_5_and_6(void *opaque, int version_id) return version_id >= 5 && version_id <= 6; } +static bool esp_is_version_8(void *opaque, int version_id) +{ + ESPState *s = ESP(opaque); + + version_id = MIN(version_id, s->mig_version_id); + return version_id >= 8; +} + int esp_pre_save(void *opaque) { ESPState *s = ESP(object_resolve_path_component( @@ -1356,13 +1418,18 @@ static int esp_post_load(void *opaque, int version_id) } } + if (version_id < 8) { + /* Assume initiator mode to allow all commands to continue */ + s->asc_mode = ESP_ASC_MODE_INI; + } + s->mig_version_id = vmstate_esp.version_id; return 0; } const VMStateDescription vmstate_esp = { .name = "esp", - .version_id = 7, + .version_id = 8, .minimum_version_id = 3, .post_load = esp_post_load, .fields = (const VMStateField[]) { @@ -1394,6 +1461,7 @@ const VMStateDescription vmstate_esp = { esp_is_between_version_5_and_6), VMSTATE_UINT8_TEST(lun, ESPState, esp_is_version_6), VMSTATE_BOOL(drq_state, ESPState), + VMSTATE_UINT8_TEST(asc_mode, ESPState, esp_is_version_8), VMSTATE_END_OF_LIST() }, }; diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events index f0f2a98..6c2788e 100644 --- a/hw/scsi/trace-events +++ b/hw/scsi/trace-events @@ -198,6 +198,7 @@ esp_mem_writeb_cmd_ensel(uint32_t val) "Enable selection (0x%2.2x)" esp_mem_writeb_cmd_dissel(uint32_t val) "Disable selection (0x%2.2x)" esp_mem_writeb_cmd_ti(uint32_t val) "Transfer Information (0x%2.2x)" esp_set_phase(const char *phase) "setting bus phase to %s" +esp_invalid_cmd(uint8_t cmd, uint8_t asc_mode) "command 0x%x asc_mode 0x%x" # esp-pci.c esp_pci_error_invalid_dma_direction(void) "invalid DMA transfer direction" diff --git a/hw/sensor/lsm303dlhc_mag.c b/hw/sensor/lsm303dlhc_mag.c index f9e501d..cd5773a 100644 --- a/hw/sensor/lsm303dlhc_mag.c +++ b/hw/sensor/lsm303dlhc_mag.c @@ -28,7 +28,6 @@ #include "qapi/visitor.h" #include "qemu/module.h" #include "qemu/log.h" -#include "qemu/bswap.h" enum LSM303DLHCMagReg { LSM303DLHC_MAG_REG_CRA = 0x00, diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c index ad4cd67..1ac063c 100644 --- a/hw/smbios/smbios.c +++ b/hw/smbios/smbios.c @@ -17,6 +17,7 @@ #include "qemu/osdep.h" #include "qemu/units.h" +#include "qemu/bswap.h" #include "qapi/error.h" #include "qemu/config-file.h" #include "qemu/module.h" diff --git a/hw/uefi/trace.h b/hw/uefi/trace.h new file mode 100644 index 0000000..6aa1c93 --- /dev/null +++ b/hw/uefi/trace.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "trace/trace-hw_uefi.h" diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c index 4836a0c..feec5a5 100644 --- a/hw/uefi/var-service-core.c +++ b/hw/uefi/var-service-core.c @@ -12,7 +12,7 @@ #include "hw/uefi/var-service-api.h" #include "hw/uefi/var-service-edk2.h" -#include "trace/trace-hw_uefi.h" +#include "trace.h" static int uefi_vars_pre_load(void *opaque) { diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c index 3b1155f..58da4ad 100644 --- a/hw/uefi/var-service-policy.c +++ b/hw/uefi/var-service-policy.c @@ -14,7 +14,7 @@ #include "hw/uefi/var-service-api.h" #include "hw/uefi/var-service-edk2.h" -#include "trace/trace-hw_uefi.h" +#include "trace.h" static void calc_policy(uefi_var_policy *pol); diff --git a/hw/uefi/var-service-utils.c b/hw/uefi/var-service-utils.c index c9ef465..258013f 100644 --- a/hw/uefi/var-service-utils.c +++ b/hw/uefi/var-service-utils.c @@ -8,7 +8,7 @@ #include "hw/uefi/var-service.h" -#include "trace/trace-hw_uefi.h" +#include "trace.h" /* ------------------------------------------------------------------ */ diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c index 7f98d77..37d05b7 100644 --- a/hw/uefi/var-service-vars.c +++ b/hw/uefi/var-service-vars.c @@ -12,7 +12,7 @@ #include "hw/uefi/var-service-api.h" #include "hw/uefi/var-service-edk2.h" -#include "trace/trace-hw_uefi.h" +#include "trace.h" #define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \ (EFI_VARIABLE_NON_VOLATILE | \ diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c index 54d064e..96623aa 100644 --- a/hw/usb/dev-hid.c +++ b/hw/usb/dev-hid.c @@ -491,14 +491,14 @@ static const uint8_t qemu_tablet_hid_report_descriptor[] = { 0xa1, 0x00, /* Collection (Physical) */ 0x05, 0x09, /* Usage Page (Button) */ 0x19, 0x01, /* Usage Minimum (1) */ - 0x29, 0x03, /* Usage Maximum (3) */ + 0x29, 0x05, /* Usage Maximum (5) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ - 0x95, 0x03, /* Report Count (3) */ + 0x95, 0x05, /* Report Count (5) */ 0x75, 0x01, /* Report Size (1) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ - 0x75, 0x05, /* Report Size (5) */ + 0x75, 0x03, /* Report Size (3) */ 0x81, 0x01, /* Input (Constant) */ 0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x30, /* Usage (X) */ diff --git a/hw/vfio-user/container.c b/hw/vfio-user/container.c index d318e6a..d589dd9 100644 --- a/hw/vfio-user/container.c +++ b/hw/vfio-user/container.c @@ -64,8 +64,6 @@ static int vfio_user_dma_unmap(const VFIOContainerBase *bcontainer, 0, &local_err)) { error_report_err(local_err); ret = -EFAULT; - } else { - ret = 0; } } else { if (!vfio_user_send_wait(container->proxy, &msgp->hdr, NULL, @@ -92,7 +90,7 @@ static int vfio_user_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova, bcontainer); int fd = memory_region_get_fd(mrp); Error *local_err = NULL; - int ret; + int ret = 0; VFIOUserFDs *fds = NULL; VFIOUserDMAMap *msgp = g_malloc0(sizeof(*msgp)); @@ -135,8 +133,6 @@ static int vfio_user_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova, 0, &local_err)) { error_report_err(local_err); ret = -EFAULT; - } else { - ret = 0; } } else { VFIOUserFDs local_fds = { 1, 0, &fd }; diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c index c418954..2275d3f 100644 --- a/hw/vfio-user/proxy.c +++ b/hw/vfio-user/proxy.c @@ -32,7 +32,6 @@ static void vfio_user_recycle(VFIOUserProxy *proxy, VFIOUserMsg *msg); static void vfio_user_recv(void *opaque); static void vfio_user_send(void *opaque); -static void vfio_user_cb(void *opaque); static void vfio_user_request(void *opaque); @@ -492,7 +491,7 @@ static void vfio_user_send(void *opaque) } } -static void vfio_user_cb(void *opaque) +static void vfio_user_close_cb(void *opaque) { VFIOUserProxy *proxy = opaque; @@ -984,8 +983,11 @@ void vfio_user_disconnect(VFIOUserProxy *proxy) * handler to run after the proxy fd handlers were * deleted above. */ - aio_bh_schedule_oneshot(proxy->ctx, vfio_user_cb, proxy); - qemu_cond_wait(&proxy->close_cv, &proxy->lock); + aio_bh_schedule_oneshot(proxy->ctx, vfio_user_close_cb, proxy); + + while (proxy->state != VFIO_PROXY_CLOSED) { + qemu_cond_wait(&proxy->close_cv, &proxy->lock); + } /* we now hold the only ref to proxy */ qemu_mutex_unlock(&proxy->lock); diff --git a/hw/vfio/device.c b/hw/vfio/device.c index 96cf214..52a1996 100644 --- a/hw/vfio/device.c +++ b/hw/vfio/device.c @@ -463,6 +463,8 @@ void vfio_device_detach(VFIODevice *vbasedev) void vfio_device_prepare(VFIODevice *vbasedev, VFIOContainerBase *bcontainer, struct vfio_device_info *info) { + int i; + vbasedev->num_irqs = info->num_irqs; vbasedev->num_regions = info->num_regions; vbasedev->flags = info->flags; @@ -477,6 +479,9 @@ void vfio_device_prepare(VFIODevice *vbasedev, VFIOContainerBase *bcontainer, vbasedev->num_regions); if (vbasedev->use_region_fds) { vbasedev->region_fds = g_new0(int, vbasedev->num_regions); + for (i = 0; i < vbasedev->num_regions; i++) { + vbasedev->region_fds[i] = -1; + } } } @@ -489,7 +494,6 @@ void vfio_device_unprepare(VFIODevice *vbasedev) if (vbasedev->region_fds != NULL && vbasedev->region_fds[i] != -1) { close(vbasedev->region_fds[i]); } - } g_clear_pointer(&vbasedev->reginfo, g_free); diff --git a/hw/vfio/helpers.c b/hw/vfio/helpers.c index 9a5f621..23d13e5 100644 --- a/hw/vfio/helpers.c +++ b/hw/vfio/helpers.c @@ -209,3 +209,20 @@ retry: return info; } + +bool vfio_arch_wants_loading_config_after_iter(void) +{ + /* + * Starting the config load only after all iterables were loaded (during + * non-iterables loading phase) is required for ARM64 due to this platform + * VFIO dependency on interrupt controller being loaded first. + * + * See commit d329f5032e17 ("vfio: Move the saving of the config space to + * the right place in VFIO migration"). + */ +#if defined(TARGET_ARM) + return true; +#else + return false; +#endif +} diff --git a/hw/vfio/migration-multifd.c b/hw/vfio/migration-multifd.c index 5563548..e478503 100644 --- a/hw/vfio/migration-multifd.c +++ b/hw/vfio/migration-multifd.c @@ -13,7 +13,6 @@ #include "hw/vfio/vfio-device.h" #include "migration/misc.h" #include "qapi/error.h" -#include "qemu/bswap.h" #include "qemu/error-report.h" #include "qemu/lockable.h" #include "qemu/main-loop.h" @@ -23,6 +22,7 @@ #include "migration-multifd.h" #include "vfio-migration-internal.h" #include "trace.h" +#include "vfio-helpers.h" #define VFIO_DEVICE_STATE_CONFIG_STATE (1) @@ -35,6 +35,18 @@ typedef struct VFIODeviceStatePacket { uint8_t data[0]; } QEMU_PACKED VFIODeviceStatePacket; +bool vfio_load_config_after_iter(VFIODevice *vbasedev) +{ + if (vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_ON) { + return true; + } else if (vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_OFF) { + return false; + } + + assert(vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_AUTO); + return vfio_arch_wants_loading_config_after_iter(); +} + /* type safety */ typedef struct VFIOStateBuffers { GArray *array; @@ -50,12 +62,16 @@ typedef struct VFIOMultifd { bool load_bufs_thread_running; bool load_bufs_thread_want_exit; + bool load_bufs_iter_done; + QemuCond load_bufs_iter_done_cond; + VFIOStateBuffers load_bufs; QemuCond load_bufs_buffer_ready_cond; QemuCond load_bufs_thread_finished_cond; QemuMutex load_bufs_mutex; /* Lock order: this lock -> BQL */ uint32_t load_buf_idx; uint32_t load_buf_idx_last; + size_t load_buf_queued_pending_buffers_size; } VFIOMultifd; static void vfio_state_buffer_clear(gpointer data) @@ -112,6 +128,7 @@ static bool vfio_load_state_buffer_insert(VFIODevice *vbasedev, VFIOMigration *migration = vbasedev->migration; VFIOMultifd *multifd = migration->multifd; VFIOStateBuffer *lb; + size_t data_size = packet_total_size - sizeof(*packet); vfio_state_buffers_assert_init(&multifd->load_bufs); if (packet->idx >= vfio_state_buffers_size_get(&multifd->load_bufs)) { @@ -127,8 +144,19 @@ static bool vfio_load_state_buffer_insert(VFIODevice *vbasedev, assert(packet->idx >= multifd->load_buf_idx); - lb->data = g_memdup2(&packet->data, packet_total_size - sizeof(*packet)); - lb->len = packet_total_size - sizeof(*packet); + multifd->load_buf_queued_pending_buffers_size += data_size; + if (multifd->load_buf_queued_pending_buffers_size > + vbasedev->migration_max_queued_buffers_size) { + error_setg(errp, + "%s: queuing state buffer %" PRIu32 + " would exceed the size max of %" PRIu64, + vbasedev->name, packet->idx, + vbasedev->migration_max_queued_buffers_size); + return false; + } + + lb->data = g_memdup2(&packet->data, data_size); + lb->len = data_size; lb->is_present = true; return true; @@ -312,6 +340,9 @@ static bool vfio_load_state_buffer_write(VFIODevice *vbasedev, assert(wr_ret <= buf_len); buf_len -= wr_ret; buf_cur += wr_ret; + + assert(multifd->load_buf_queued_pending_buffers_size >= wr_ret); + multifd->load_buf_queued_pending_buffers_size -= wr_ret; } trace_vfio_load_state_device_buffer_load_end(vbasedev->name, @@ -394,6 +425,22 @@ static bool vfio_load_bufs_thread(void *opaque, bool *should_quit, Error **errp) multifd->load_buf_idx++; } + if (vfio_load_config_after_iter(vbasedev)) { + while (!multifd->load_bufs_iter_done) { + qemu_cond_wait(&multifd->load_bufs_iter_done_cond, + &multifd->load_bufs_mutex); + + /* + * Need to re-check cancellation immediately after wait in case + * cond was signalled by vfio_load_cleanup_load_bufs_thread(). + */ + if (vfio_load_bufs_thread_want_exit(multifd, should_quit)) { + error_setg(errp, "operation cancelled"); + goto thread_exit; + } + } + } + if (!vfio_load_bufs_thread_load_config(vbasedev, errp)) { goto thread_exit; } @@ -413,6 +460,48 @@ thread_exit: return ret; } +int vfio_load_state_config_load_ready(VFIODevice *vbasedev) +{ + VFIOMigration *migration = vbasedev->migration; + VFIOMultifd *multifd = migration->multifd; + int ret = 0; + + if (!vfio_multifd_transfer_enabled(vbasedev)) { + error_report("%s: got DEV_CONFIG_LOAD_READY outside multifd transfer", + vbasedev->name); + return -EINVAL; + } + + if (!vfio_load_config_after_iter(vbasedev)) { + error_report("%s: got DEV_CONFIG_LOAD_READY but was disabled", + vbasedev->name); + return -EINVAL; + } + + assert(multifd); + + /* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */ + bql_unlock(); + WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) { + if (multifd->load_bufs_iter_done) { + /* Can't print error here as we're outside BQL */ + ret = -EINVAL; + break; + } + + multifd->load_bufs_iter_done = true; + qemu_cond_signal(&multifd->load_bufs_iter_done_cond); + } + bql_lock(); + + if (ret) { + error_report("%s: duplicate DEV_CONFIG_LOAD_READY", + vbasedev->name); + } + + return ret; +} + static VFIOMultifd *vfio_multifd_new(void) { VFIOMultifd *multifd = g_new(VFIOMultifd, 1); @@ -423,8 +512,12 @@ static VFIOMultifd *vfio_multifd_new(void) multifd->load_buf_idx = 0; multifd->load_buf_idx_last = UINT32_MAX; + multifd->load_buf_queued_pending_buffers_size = 0; qemu_cond_init(&multifd->load_bufs_buffer_ready_cond); + multifd->load_bufs_iter_done = false; + qemu_cond_init(&multifd->load_bufs_iter_done_cond); + multifd->load_bufs_thread_running = false; multifd->load_bufs_thread_want_exit = false; qemu_cond_init(&multifd->load_bufs_thread_finished_cond); @@ -448,6 +541,7 @@ static void vfio_load_cleanup_load_bufs_thread(VFIOMultifd *multifd) multifd->load_bufs_thread_want_exit = true; qemu_cond_signal(&multifd->load_bufs_buffer_ready_cond); + qemu_cond_signal(&multifd->load_bufs_iter_done_cond); qemu_cond_wait(&multifd->load_bufs_thread_finished_cond, &multifd->load_bufs_mutex); } @@ -460,6 +554,7 @@ static void vfio_multifd_free(VFIOMultifd *multifd) vfio_load_cleanup_load_bufs_thread(multifd); qemu_cond_destroy(&multifd->load_bufs_thread_finished_cond); + qemu_cond_destroy(&multifd->load_bufs_iter_done_cond); vfio_state_buffers_destroy(&multifd->load_bufs); qemu_cond_destroy(&multifd->load_bufs_buffer_ready_cond); qemu_mutex_destroy(&multifd->load_bufs_mutex); diff --git a/hw/vfio/migration-multifd.h b/hw/vfio/migration-multifd.h index ebf22a7..82d2d3a 100644 --- a/hw/vfio/migration-multifd.h +++ b/hw/vfio/migration-multifd.h @@ -20,9 +20,12 @@ void vfio_multifd_cleanup(VFIODevice *vbasedev); bool vfio_multifd_transfer_supported(void); bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev); +bool vfio_load_config_after_iter(VFIODevice *vbasedev); bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size, Error **errp); +int vfio_load_state_config_load_ready(VFIODevice *vbasedev); + void vfio_multifd_emit_dummy_eos(VFIODevice *vbasedev, QEMUFile *f); bool diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index c329578..4c06e3d 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -675,7 +675,11 @@ static void vfio_save_state(QEMUFile *f, void *opaque) int ret; if (vfio_multifd_transfer_enabled(vbasedev)) { - vfio_multifd_emit_dummy_eos(vbasedev, f); + if (vfio_load_config_after_iter(vbasedev)) { + qemu_put_be64(f, VFIO_MIG_FLAG_DEV_CONFIG_LOAD_READY); + } else { + vfio_multifd_emit_dummy_eos(vbasedev, f); + } return; } @@ -784,6 +788,10 @@ static int vfio_load_state(QEMUFile *f, void *opaque, int version_id) return ret; } + case VFIO_MIG_FLAG_DEV_CONFIG_LOAD_READY: + { + return vfio_load_state_config_load_ready(vbasedev); + } default: error_report("%s: Unknown tag 0x%"PRIx64, vbasedev->name, data); return -EINVAL; diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c index 1093b28..be05002 100644 --- a/hw/vfio/pci.c +++ b/hw/vfio/pci.c @@ -2893,10 +2893,6 @@ bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp) "vfio-vga-io@0x3c0", QEMU_PCI_VGA_IO_HI_SIZE); - pci_register_vga(&vdev->pdev, &vdev->vga->region[QEMU_PCI_VGA_MEM].mem, - &vdev->vga->region[QEMU_PCI_VGA_IO_LO].mem, - &vdev->vga->region[QEMU_PCI_VGA_IO_HI].mem); - return true; } @@ -3228,6 +3224,23 @@ bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp) vdev->sub_device_id); } + /* + * Class code is a 24-bit value at config space 0x09. Allow overriding it + * with any 24-bit value. + */ + if (vdev->class_code != PCI_ANY_ID) { + if (vdev->class_code > 0xffffff) { + error_setg(errp, "invalid PCI class code provided"); + return false; + } + /* Higher 24 bits of PCI_CLASS_REVISION are class code */ + vfio_add_emulated_long(vdev, PCI_CLASS_REVISION, + vdev->class_code << 8, ~0xff); + trace_vfio_pci_emulated_class_code(vbasedev->name, vdev->class_code); + } else { + vdev->class_code = pci_get_long(pdev->config + PCI_CLASS_REVISION) >> 8; + } + /* QEMU can change multi-function devices to single function, or reverse */ vdev->emulated_config_bits[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_MULTI_FUNCTION; @@ -3257,6 +3270,12 @@ bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp) vfio_bars_register(vdev); + if (vdev->vga && vfio_is_vga(vdev)) { + pci_register_vga(&vdev->pdev, &vdev->vga->region[QEMU_PCI_VGA_MEM].mem, + &vdev->vga->region[QEMU_PCI_VGA_IO_LO].mem, + &vdev->vga->region[QEMU_PCI_VGA_IO_HI].mem); + } + return true; } @@ -3623,6 +3642,11 @@ static const Property vfio_pci_dev_properties[] = { vbasedev.migration_multifd_transfer, vfio_pci_migration_multifd_transfer_prop, OnOffAuto, .set_default = true, .defval.i = ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO("x-migration-load-config-after-iter", VFIOPCIDevice, + vbasedev.migration_load_config_after_iter, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_SIZE("x-migration-max-queued-buffers-size", VFIOPCIDevice, + vbasedev.migration_max_queued_buffers_size, UINT64_MAX), DEFINE_PROP_BOOL("migration-events", VFIOPCIDevice, vbasedev.migration_events, false), DEFINE_PROP_BOOL("x-no-mmap", VFIOPCIDevice, vbasedev.no_mmap, false), @@ -3643,6 +3667,8 @@ static const Property vfio_pci_dev_properties[] = { sub_vendor_id, PCI_ANY_ID), DEFINE_PROP_UINT32("x-pci-sub-device-id", VFIOPCIDevice, sub_device_id, PCI_ANY_ID), + DEFINE_PROP_UINT32("x-pci-class-code", VFIOPCIDevice, + class_code, PCI_ANY_ID), DEFINE_PROP_UINT32("x-igd-gms", VFIOPCIDevice, igd_gms, 0), DEFINE_PROP_UNSIGNED_NODEFAULT("x-nv-gpudirect-clique", VFIOPCIDevice, nv_gpudirect_clique, @@ -3797,6 +3823,20 @@ static void vfio_pci_dev_class_init(ObjectClass *klass, const void *data) "x-migration-multifd-transfer", "Transfer this device state via " "multifd channels when live migrating it"); + object_class_property_set_description(klass, /* 10.1 */ + "x-migration-load-config-after-iter", + "Start the config load only after " + "all iterables were loaded (during " + "non-iterables loading phase) when " + "doing live migration of device state " + "via multifd channels"); + object_class_property_set_description(klass, /* 10.1 */ + "x-migration-max-queued-buffers-size", + "Maximum size of in-flight VFIO " + "device state buffers queued at the " + "destination when doing live " + "migration of device state via " + "multifd channels"); } static const TypeInfo vfio_pci_dev_info = { diff --git a/hw/vfio/pci.h b/hw/vfio/pci.h index 495fae7..4aa6461 100644 --- a/hw/vfio/pci.h +++ b/hw/vfio/pci.h @@ -157,6 +157,7 @@ struct VFIOPCIDevice { uint32_t device_id; uint32_t sub_vendor_id; uint32_t sub_device_id; + uint32_t class_code; uint32_t features; #define VFIO_FEATURE_ENABLE_VGA_BIT 0 #define VFIO_FEATURE_ENABLE_VGA (1 << VFIO_FEATURE_ENABLE_VGA_BIT) @@ -205,10 +206,7 @@ static inline bool vfio_pci_is(VFIOPCIDevice *vdev, uint32_t vendor, uint32_t de static inline bool vfio_is_vga(VFIOPCIDevice *vdev) { - PCIDevice *pdev = &vdev->pdev; - uint16_t class = pci_get_word(pdev->config + PCI_CLASS_DEVICE); - - return class == PCI_CLASS_DISPLAY_VGA; + return (vdev->class_code >> 8) == PCI_CLASS_DISPLAY_VGA; } /* MSI/MSI-X/INTx */ diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index 8ec0ad0..fc6ed23 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -48,6 +48,7 @@ vfio_pci_emulated_vendor_id(const char *name, uint16_t val) "%s 0x%04x" vfio_pci_emulated_device_id(const char *name, uint16_t val) "%s 0x%04x" vfio_pci_emulated_sub_vendor_id(const char *name, uint16_t val) "%s 0x%04x" vfio_pci_emulated_sub_device_id(const char *name, uint16_t val) "%s 0x%04x" +vfio_pci_emulated_class_code(const char *name, uint32_t val) "%s 0x%06x" # pci-quirks.c vfio_quirk_rom_in_denylist(const char *name, uint16_t vid, uint16_t did) "%s %04x:%04x" diff --git a/hw/vfio/vfio-helpers.h b/hw/vfio/vfio-helpers.h index 54a327f..ce31758 100644 --- a/hw/vfio/vfio-helpers.h +++ b/hw/vfio/vfio-helpers.h @@ -32,4 +32,6 @@ struct vfio_device_info *vfio_get_device_info(int fd); int vfio_kvm_device_add_fd(int fd, Error **errp); int vfio_kvm_device_del_fd(int fd, Error **errp); +bool vfio_arch_wants_loading_config_after_iter(void); + #endif /* HW_VFIO_VFIO_HELPERS_H */ diff --git a/hw/vfio/vfio-migration-internal.h b/hw/vfio/vfio-migration-internal.h index a8b456b..54141e2 100644 --- a/hw/vfio/vfio-migration-internal.h +++ b/hw/vfio/vfio-migration-internal.h @@ -32,6 +32,7 @@ #define VFIO_MIG_FLAG_DEV_SETUP_STATE (0xffffffffef100003ULL) #define VFIO_MIG_FLAG_DEV_DATA_STATE (0xffffffffef100004ULL) #define VFIO_MIG_FLAG_DEV_INIT_DATA_SENT (0xffffffffef100005ULL) +#define VFIO_MIG_FLAG_DEV_CONFIG_LOAD_READY (0xffffffffef100006ULL) typedef struct VFIODevice VFIODevice; typedef struct VFIOMultifd VFIOMultifd; diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index 164f6fd..3ea7b3c 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -1,6 +1,7 @@ system_virtio_ss = ss.source_set() system_virtio_ss.add(files('virtio-bus.c')) system_virtio_ss.add(files('iothread-vq-mapping.c')) +system_virtio_ss.add(files('virtio-config-io.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_PCI', if_true: files('virtio-pci.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_MMIO', if_true: files('virtio-mmio.c')) system_virtio_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto.c')) @@ -10,11 +11,11 @@ system_virtio_ss.add(when: 'CONFIG_VHOST_VDPA_DEV', if_true: files('vdpa-dev.c') specific_virtio_ss = ss.source_set() specific_virtio_ss.add(files('virtio.c')) -specific_virtio_ss.add(files('virtio-config-io.c', 'virtio-qmp.c')) +specific_virtio_ss.add(files('virtio-qmp.c')) if have_vhost system_virtio_ss.add(files('vhost.c')) - specific_virtio_ss.add(files('vhost-backend.c', 'vhost-iova-tree.c')) + system_virtio_ss.add(files('vhost-backend.c', 'vhost-iova-tree.c')) if have_vhost_user # fixme - this really should be generic specific_virtio_ss.add(files('vhost-user.c')) @@ -43,22 +44,22 @@ if have_vhost endif if have_vhost_vdpa system_virtio_ss.add(files('vhost-vdpa.c')) - specific_virtio_ss.add(files('vhost-shadow-virtqueue.c')) + system_virtio_ss.add(files('vhost-shadow-virtqueue.c')) endif else system_virtio_ss.add(files('vhost-stub.c')) endif +system_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c')) +system_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) specific_virtio_ss.add(when: 'CONFIG_VIRTIO_BALLOON', if_true: files('virtio-balloon.c')) specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs.c')) specific_virtio_ss.add(when: 'CONFIG_VIRTIO_PMEM', if_true: files('virtio-pmem.c')) specific_virtio_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock.c')) -specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock.c')) -specific_virtio_ss.add(when: 'CONFIG_VIRTIO_RNG', if_true: files('virtio-rng.c')) -specific_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('virtio-nsm.c', 'cbor-helpers.c'), libcbor]) specific_virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) -specific_virtio_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true: files('vhost-user-scmi.c')) -specific_virtio_ss.add(when: ['CONFIG_VIRTIO_PCI', 'CONFIG_VHOST_USER_SCMI'], if_true: files('vhost-user-scmi-pci.c')) +system_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: files('virtio-nsm.c')) +system_virtio_ss.add(when: 'CONFIG_VIRTIO_NSM', if_true: [files('cbor-helpers.c'), libcbor]) +system_virtio_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true: files('vhost-user-scmi.c')) virtio_pci_ss = ss.source_set() virtio_pci_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock-pci.c')) @@ -67,6 +68,7 @@ virtio_pci_ss.add(when: 'CONFIG_VHOST_USER_BLK', if_true: files('vhost-user-blk- virtio_pci_ss.add(when: 'CONFIG_VHOST_USER_SCSI', if_true: files('vhost-user-scsi-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VHOST_USER_FS', if_true: files('vhost-user-fs-pci.c')) +virtio_pci_ss.add(when: 'CONFIG_VHOST_USER_SCMI', if_true: files('vhost-user-scmi-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_CRYPTO', if_true: files('virtio-crypto-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true: files('virtio-input-host-pci.c')) @@ -85,7 +87,7 @@ virtio_pci_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VHOST_VDPA_DEV', if_true: files('vdpa-dev-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VIRTIO_MD', if_true: files('virtio-md-pci.c')) -specific_virtio_ss.add_all(when: 'CONFIG_VIRTIO_PCI', if_true: virtio_pci_ss) +system_virtio_ss.add_all(when: 'CONFIG_VIRTIO_PCI', if_true: virtio_pci_ss) system_ss.add_all(when: 'CONFIG_VIRTIO', if_true: system_virtio_ss) system_ss.add(when: 'CONFIG_VIRTIO', if_false: files('vhost-stub.c')) diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index fc43853..c30ea11 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -47,12 +47,6 @@ static struct vhost_log *vhost_log[VHOST_BACKEND_TYPE_MAX]; static struct vhost_log *vhost_log_shm[VHOST_BACKEND_TYPE_MAX]; static QLIST_HEAD(, vhost_dev) vhost_log_devs[VHOST_BACKEND_TYPE_MAX]; -/* Memslots used by backends that support private memslots (without an fd). */ -static unsigned int used_memslots; - -/* Memslots used by backends that only support shared memslots (with an fd). */ -static unsigned int used_shared_memslots; - static QLIST_HEAD(, vhost_dev) vhost_devices = QLIST_HEAD_INITIALIZER(vhost_devices); @@ -74,15 +68,15 @@ unsigned int vhost_get_free_memslots(void) QLIST_FOREACH(hdev, &vhost_devices, entry) { unsigned int r = hdev->vhost_ops->vhost_backend_memslots_limit(hdev); - unsigned int cur_free; + unsigned int cur_free = r - hdev->mem->nregions; - if (hdev->vhost_ops->vhost_backend_no_private_memslots && - hdev->vhost_ops->vhost_backend_no_private_memslots(hdev)) { - cur_free = r - used_shared_memslots; + if (unlikely(r < hdev->mem->nregions)) { + warn_report_once("used (%u) vhost backend memory slots exceed" + " the device limit (%u).", hdev->mem->nregions, r); + free = 0; } else { - cur_free = r - used_memslots; + free = MIN(free, cur_free); } - free = MIN(free, cur_free); } return free; } @@ -666,13 +660,6 @@ static void vhost_commit(MemoryListener *listener) dev->mem = g_realloc(dev->mem, regions_size); dev->mem->nregions = dev->n_mem_sections; - if (dev->vhost_ops->vhost_backend_no_private_memslots && - dev->vhost_ops->vhost_backend_no_private_memslots(dev)) { - used_shared_memslots = dev->mem->nregions; - } else { - used_memslots = dev->mem->nregions; - } - for (i = 0; i < dev->n_mem_sections; i++) { struct vhost_memory_region *cur_vmr = dev->mem->regions + i; struct MemoryRegionSection *mrs = dev->mem_sections + i; @@ -1367,25 +1354,30 @@ fail_alloc_desc: return r; } -int vhost_virtqueue_stop(struct vhost_dev *dev, - struct VirtIODevice *vdev, - struct vhost_virtqueue *vq, - unsigned idx) +static int do_vhost_virtqueue_stop(struct vhost_dev *dev, + struct VirtIODevice *vdev, + struct vhost_virtqueue *vq, + unsigned idx, bool force) { int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, idx); struct vhost_vring_state state = { .index = vhost_vq_index, }; - int r; + int r = 0; if (virtio_queue_get_desc_addr(vdev, idx) == 0) { /* Don't stop the virtqueue which might have not been started */ return 0; } - r = dev->vhost_ops->vhost_get_vring_base(dev, &state); - if (r < 0) { - VHOST_OPS_DEBUG(r, "vhost VQ %u ring restore failed: %d", idx, r); + if (!force) { + r = dev->vhost_ops->vhost_get_vring_base(dev, &state); + if (r < 0) { + VHOST_OPS_DEBUG(r, "vhost VQ %u ring restore failed: %d", idx, r); + } + } + + if (r < 0 || force) { /* Connection to the backend is broken, so let's sync internal * last avail idx to the device used idx. */ @@ -1414,6 +1406,14 @@ int vhost_virtqueue_stop(struct vhost_dev *dev, return r; } +int vhost_virtqueue_stop(struct vhost_dev *dev, + struct VirtIODevice *vdev, + struct vhost_virtqueue *vq, + unsigned idx) +{ + return do_vhost_virtqueue_stop(dev, vdev, vq, idx, false); +} + static int vhost_virtqueue_set_busyloop_timeout(struct vhost_dev *dev, int n, uint32_t timeout) { @@ -1619,15 +1619,11 @@ int vhost_dev_init(struct vhost_dev *hdev, void *opaque, QLIST_INSERT_HEAD(&vhost_devices, hdev, entry); /* - * The listener we registered properly updated the corresponding counter. - * So we can trust that these values are accurate. + * The listener we registered properly setup the number of required + * memslots in vhost_commit(). */ - if (hdev->vhost_ops->vhost_backend_no_private_memslots && - hdev->vhost_ops->vhost_backend_no_private_memslots(hdev)) { - used = used_shared_memslots; - } else { - used = used_memslots; - } + used = hdev->mem->nregions; + /* * We assume that all reserved memslots actually require a real memslot * in our vhost backend. This might not be true, for example, if the @@ -2136,7 +2132,8 @@ fail_features: } /* Host notifiers must be enabled at this point. */ -int vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) +static int do_vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, + bool vrings, bool force) { int i; int rc = 0; @@ -2158,10 +2155,11 @@ int vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) vhost_dev_set_vring_enable(hdev, false); } for (i = 0; i < hdev->nvqs; ++i) { - rc |= vhost_virtqueue_stop(hdev, - vdev, - hdev->vqs + i, - hdev->vq_index + i); + rc |= do_vhost_virtqueue_stop(hdev, + vdev, + hdev->vqs + i, + hdev->vq_index + i, + force); } if (hdev->vhost_ops->vhost_reset_status) { hdev->vhost_ops->vhost_reset_status(hdev); @@ -2181,6 +2179,17 @@ int vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) return rc; } +int vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) +{ + return do_vhost_dev_stop(hdev, vdev, vrings, false); +} + +int vhost_dev_force_stop(struct vhost_dev *hdev, VirtIODevice *vdev, + bool vrings) +{ + return do_vhost_dev_stop(hdev, vdev, vrings, true); +} + int vhost_net_set_backend(struct vhost_dev *hdev, struct vhost_vring_file *file) { diff --git a/hw/virtio/virtio-config-io.c b/hw/virtio/virtio-config-io.c index ad78e0b..f58d90b 100644 --- a/hw/virtio/virtio-config-io.c +++ b/hw/virtio/virtio-config-io.c @@ -11,7 +11,6 @@ #include "qemu/osdep.h" #include "hw/virtio/virtio.h" -#include "cpu.h" uint32_t virtio_config_readb(VirtIODevice *vdev, uint32_t addr) { diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index fba2372..767216d 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -30,6 +30,7 @@ #include "qemu/error-report.h" #include "qemu/log.h" #include "qemu/module.h" +#include "qemu/bswap.h" #include "hw/pci/msi.h" #include "hw/pci/msix.h" #include "hw/loader.h" diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 82a285a..2ab1d20 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -20,7 +20,7 @@ #include "qemu/log.h" #include "qemu/main-loop.h" #include "qemu/module.h" -#include "exec/tswap.h" +#include "qemu/target-info.h" #include "qom/object_interfaces.h" #include "hw/core/cpu.h" #include "hw/virtio/virtio.h" @@ -3270,13 +3270,6 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) config_len--; } - if (vdc->pre_load_queues) { - ret = vdc->pre_load_queues(vdev); - if (ret) { - return ret; - } - } - num = qemu_get_be32(f); if (num > VIRTIO_QUEUE_MAX) { @@ -3284,6 +3277,13 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id) return -1; } + if (vdc->pre_load_queues) { + ret = vdc->pre_load_queues(vdev, num); + if (ret) { + return ret; + } + } + for (i = 0; i < num; i++) { vdev->vq[i].vring.num = qemu_get_be32(f); if (k->has_variable_vring_alignment) { diff --git a/hw/vmapple/virtio-blk.c b/hw/vmapple/virtio-blk.c index 532b564..9de9aaa 100644 --- a/hw/vmapple/virtio-blk.c +++ b/hw/vmapple/virtio-blk.c @@ -19,7 +19,6 @@ #include "hw/vmapple/vmapple.h" #include "hw/virtio/virtio-blk.h" #include "hw/virtio/virtio-pci.h" -#include "qemu/bswap.h" #include "qemu/log.h" #include "qemu/module.h" #include "qapi/error.h" diff --git a/include/system/accel-ops.h b/include/accel/accel-cpu-ops.h index bf73835..0674764 100644 --- a/include/system/accel-ops.h +++ b/include/accel/accel-cpu-ops.h @@ -1,5 +1,5 @@ /* - * Accelerator OPS, used for cpus.c module + * Accelerator per-vCPU handlers * * Copyright 2021 SUSE LLC * @@ -7,8 +7,8 @@ * See the COPYING file in the top-level directory. */ -#ifndef ACCEL_OPS_H -#define ACCEL_OPS_H +#ifndef QEMU_ACCEL_CPU_OPS_H +#define QEMU_ACCEL_CPU_OPS_H #include "qemu/accel.h" #include "exec/vaddr.h" @@ -65,6 +65,9 @@ struct AccelOpsClass { /* handle_interrupt is mandatory. */ void (*handle_interrupt)(CPUState *cpu, int mask); + /* get_vcpu_stats: Append statistics of this @cpu to @buf */ + void (*get_vcpu_stats)(CPUState *cpu, GString *buf); + /** * @get_virtual_clock: fetch virtual clock * @set_virtual_clock: set virtual clock @@ -89,4 +92,4 @@ struct AccelOpsClass { void generic_handle_interrupt(CPUState *cpu, int mask); -#endif /* ACCEL_OPS_H */ +#endif /* QEMU_ACCEL_CPU_OPS_H */ diff --git a/include/accel/accel-ops.h b/include/accel/accel-ops.h new file mode 100644 index 0000000..23a8c24 --- /dev/null +++ b/include/accel/accel-ops.h @@ -0,0 +1,51 @@ +/* + * Accelerator handlers + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef ACCEL_OPS_H +#define ACCEL_OPS_H + +#include "exec/hwaddr.h" +#include "qemu/accel.h" +#include "qom/object.h" + +struct AccelState { + Object parent_obj; +}; + +struct AccelClass { + ObjectClass parent_class; + + const char *name; + /* Cached by accel_init_ops_interfaces() when created */ + AccelOpsClass *ops; + + int (*init_machine)(AccelState *as, MachineState *ms); + bool (*cpu_common_realize)(CPUState *cpu, Error **errp); + void (*cpu_common_unrealize)(CPUState *cpu); + /* get_stats: Append statistics to @buf */ + void (*get_stats)(AccelState *as, GString *buf); + + /* system related hooks */ + void (*setup_post)(AccelState *as); + void (*pre_resume_vm)(AccelState *as, bool step_pending); + bool (*has_memory)(AccelState *accel, AddressSpace *as, + hwaddr start_addr, hwaddr size); + + /* gdbstub related hooks */ + int (*gdbstub_supported_sstep_flags)(AccelState *as); + + bool *allowed; + /* + * Array of global properties that would be applied when specific + * accelerator is chosen. It works like MachineClass.compat_props + * but it's for accelerators not machines. Accelerator-provided + * global properties may be overridden by machine-type + * compat_props or user-provided global properties. + */ + GPtrArray *compat_props; +}; + +#endif /* ACCEL_OPS_H */ diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h index 84a2a4e..62da83c 100644 --- a/include/block/block-global-state.h +++ b/include/block/block-global-state.h @@ -74,13 +74,14 @@ int bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top, int GRAPH_WRLOCK bdrv_replace_node(BlockDriverState *from, BlockDriverState *to, Error **errp); -int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs, - Error **errp); -BlockDriverState *bdrv_insert_node(BlockDriverState *bs, QDict *node_options, - int flags, Error **errp); +int GRAPH_UNLOCKED +bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs, Error **errp); +BlockDriverState * GRAPH_UNLOCKED +bdrv_insert_node(BlockDriverState *bs, QDict *node_options, int flags, + Error **errp); int bdrv_drop_filter(BlockDriverState *bs, Error **errp); -BdrvChild * no_coroutine_fn +BdrvChild * no_coroutine_fn GRAPH_UNLOCKED bdrv_open_child(const char *filename, QDict *options, const char *bdref_key, BlockDriverState *parent, const BdrvChildClass *child_class, BdrvChildRole child_role, bool allow_none, Error **errp); @@ -90,9 +91,10 @@ bdrv_co_open_child(const char *filename, QDict *options, const char *bdref_key, BlockDriverState *parent, const BdrvChildClass *child_class, BdrvChildRole child_role, bool allow_none, Error **errp); -int bdrv_open_file_child(const char *filename, - QDict *options, const char *bdref_key, - BlockDriverState *parent, Error **errp); +int GRAPH_UNLOCKED +bdrv_open_file_child(const char *filename, QDict *options, + const char *bdref_key, BlockDriverState *parent, + Error **errp); BlockDriverState * no_coroutine_fn bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp); @@ -100,11 +102,9 @@ bdrv_open_blockdev_ref(BlockdevRef *ref, Error **errp); BlockDriverState * coroutine_fn no_co_wrapper bdrv_co_open_blockdev_ref(BlockdevRef *ref, Error **errp); -int bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd, - Error **errp); int GRAPH_WRLOCK -bdrv_set_backing_hd_drained(BlockDriverState *bs, BlockDriverState *backing_hd, - Error **errp); +bdrv_set_backing_hd(BlockDriverState *bs, BlockDriverState *backing_hd, + Error **errp); int bdrv_open_backing_file(BlockDriverState *bs, QDict *parent_options, const char *bdref_key, Error **errp); @@ -123,11 +123,12 @@ BlockDriverState *bdrv_new_open_driver_opts(BlockDriver *drv, Error **errp); BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name, int flags, Error **errp); -BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, - BlockDriverState *bs, QDict *options, - bool keep_old_opts); +BlockReopenQueue * GRAPH_UNLOCKED +bdrv_reopen_queue(BlockReopenQueue *bs_queue, BlockDriverState *bs, + QDict *options, bool keep_old_opts); void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue); -int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp); +int GRAPH_UNLOCKED +bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp); int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts, Error **errp); int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, @@ -143,9 +144,10 @@ int bdrv_commit(BlockDriverState *bs); int GRAPH_RDLOCK bdrv_make_empty(BdrvChild *c, Error **errp); void bdrv_register(BlockDriver *bdrv); -int bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base, - const char *backing_file_str, - bool backing_mask_protocol); +int GRAPH_UNLOCKED +bdrv_drop_intermediate(BlockDriverState *top, BlockDriverState *base, + const char *backing_file_str, + bool backing_mask_protocol); BlockDriverState * GRAPH_RDLOCK bdrv_find_overlay(BlockDriverState *active, BlockDriverState *bs); @@ -184,14 +186,14 @@ 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 +int no_coroutine_fn GRAPH_RDLOCK bdrv_inactivate(BlockDriverState *bs, Error **errp); void bdrv_activate_all(Error **errp); -int bdrv_inactivate_all(void); +int GRAPH_UNLOCKED bdrv_inactivate_all(void); int bdrv_flush_all(void); -void bdrv_close_all(void); +void GRAPH_UNLOCKED bdrv_close_all(void); void GRAPH_UNLOCKED bdrv_drain_all_begin(void); void bdrv_drain_all_begin_nopoll(void); void bdrv_drain_all_end(void); diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index 925a3e7..034c063 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -248,7 +248,7 @@ struct BlockDriver { int GRAPH_UNLOCKED_PTR (*bdrv_open)( BlockDriverState *bs, QDict *options, int flags, Error **errp); - void (*bdrv_close)(BlockDriverState *bs); + void GRAPH_UNLOCKED_PTR (*bdrv_close)(BlockDriverState *bs); int coroutine_fn GRAPH_UNLOCKED_PTR (*bdrv_co_create)( BlockdevCreateOptions *opts, Error **errp); @@ -1253,7 +1253,7 @@ struct BlockDriverState { /* do we need to tell the quest if we have a volatile write cache? */ int enable_write_cache; - /* Accessed with atomic ops. */ + /* Accessed only in the main thread. */ int quiesce_counter; unsigned int write_gen; /* Current data generation */ diff --git a/include/block/blockjob.h b/include/block/blockjob.h index 990f3e1..85284cb 100644 --- a/include/block/blockjob.h +++ b/include/block/blockjob.h @@ -151,7 +151,7 @@ block_job_add_bdrv(BlockJob *job, const char *name, BlockDriverState *bs, * Remove all BlockDriverStates from the list of nodes that are involved in the * job. This removes the blockers added with block_job_add_bdrv(). */ -void block_job_remove_all_bdrv(BlockJob *job); +void GRAPH_UNLOCKED block_job_remove_all_bdrv(BlockJob *job); /** * block_job_has_bdrv: diff --git a/include/block/graph-lock.h b/include/block/graph-lock.h index 2c26c72..95bf5ed 100644 --- a/include/block/graph-lock.h +++ b/include/block/graph-lock.h @@ -113,9 +113,20 @@ void no_coroutine_fn TSA_ACQUIRE(graph_lock) TSA_NO_TSA bdrv_graph_wrlock(void); /* + * bdrv_graph_wrlock_drained: + * Similar to bdrv_graph_wrlock, but will begin a drained section before + * locking. + */ +void no_coroutine_fn TSA_ACQUIRE(graph_lock) TSA_NO_TSA +bdrv_graph_wrlock_drained(void); + +/* * bdrv_graph_wrunlock: * Write finished, reset global has_writer to 0 and restart * all readers that are waiting. + * + * Also ends the drained section if bdrv_graph_wrlock_drained() was used to lock + * the graph. */ void no_coroutine_fn TSA_RELEASE(graph_lock) TSA_NO_TSA bdrv_graph_wrunlock(void); diff --git a/include/block/snapshot.h b/include/block/snapshot.h index 304cc6e..2316a43 100644 --- a/include/block/snapshot.h +++ b/include/block/snapshot.h @@ -90,9 +90,9 @@ int bdrv_snapshot_load_tmp_by_id_or_name(BlockDriverState *bs, bool bdrv_all_can_snapshot(bool has_devices, strList *devices, Error **errp); -int bdrv_all_delete_snapshot(const char *name, - bool has_devices, strList *devices, - Error **errp); +int GRAPH_UNLOCKED +bdrv_all_delete_snapshot(const char *name, bool has_devices, strList *devices, + Error **errp); int bdrv_all_goto_snapshot(const char *name, bool has_devices, strList *devices, Error **errp); diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h index 8db1d30..52ee955 100644 --- a/include/exec/memattrs.h +++ b/include/exec/memattrs.h @@ -54,6 +54,9 @@ typedef struct MemTxAttrs { */ unsigned int pid:8; + /* PCI - IOMMU operations, see PCIAddressType */ + unsigned int address_type:1; + /* * Bus masters which don't specify any attributes will get this * (via the MEMTXATTRS_UNSPECIFIED constant), so that we can diff --git a/include/exec/tswap.h b/include/exec/tswap.h index 49511f2..72219e2 100644 --- a/include/exec/tswap.h +++ b/include/exec/tswap.h @@ -9,18 +9,7 @@ #define TSWAP_H #include "qemu/bswap.h" - -/** - * target_big_endian: - * Returns true if the (default) endianness of the target is big endian, - * false otherwise. Common code should normally never need to know about the - * endianness of the target, so please do *not* use this function unless you - * know very well what you are doing! - */ -bool target_big_endian(void); -#ifdef COMPILING_PER_TARGET -#define target_big_endian() TARGET_BIG_ENDIAN -#endif +#include "qemu/target-info.h" /* * If we're in target-specific code, we can hard-code the swapping @@ -80,74 +69,4 @@ static inline void tswap64s(uint64_t *s) } } -/* Return ld{word}_{le,be}_p following target endianness. */ -#define LOAD_IMPL(word, args...) \ -do { \ - if (target_big_endian()) { \ - return glue(glue(ld, word), _be_p)(args); \ - } else { \ - return glue(glue(ld, word), _le_p)(args); \ - } \ -} while (0) - -static inline int lduw_p(const void *ptr) -{ - LOAD_IMPL(uw, ptr); -} - -static inline int ldsw_p(const void *ptr) -{ - LOAD_IMPL(sw, ptr); -} - -static inline int ldl_p(const void *ptr) -{ - LOAD_IMPL(l, ptr); -} - -static inline uint64_t ldq_p(const void *ptr) -{ - LOAD_IMPL(q, ptr); -} - -static inline uint64_t ldn_p(const void *ptr, int sz) -{ - LOAD_IMPL(n, ptr, sz); -} - -#undef LOAD_IMPL - -/* Call st{word}_{le,be}_p following target endianness. */ -#define STORE_IMPL(word, args...) \ -do { \ - if (target_big_endian()) { \ - glue(glue(st, word), _be_p)(args); \ - } else { \ - glue(glue(st, word), _le_p)(args); \ - } \ -} while (0) - - -static inline void stw_p(void *ptr, uint16_t v) -{ - STORE_IMPL(w, ptr, v); -} - -static inline void stl_p(void *ptr, uint32_t v) -{ - STORE_IMPL(l, ptr, v); -} - -static inline void stq_p(void *ptr, uint64_t v) -{ - STORE_IMPL(q, ptr, v); -} - -static inline void stn_p(void *ptr, int sz, uint64_t v) -{ - STORE_IMPL(n, ptr, sz, v); -} - -#undef STORE_IMPL - #endif /* TSWAP_H */ diff --git a/include/gdbstub/helpers.h b/include/gdbstub/helpers.h index 6f7cc48..b685afa 100644 --- a/include/gdbstub/helpers.h +++ b/include/gdbstub/helpers.h @@ -16,7 +16,8 @@ #error "gdbstub helpers should only be included by target specific code" #endif -#include "exec/tswap.h" +#include "qemu/bswap.h" +#include "qemu/target-info.h" #include "cpu-param.h" /* @@ -33,40 +34,49 @@ static inline int gdb_get_reg8(GByteArray *buf, uint8_t val) static inline int gdb_get_reg16(GByteArray *buf, uint16_t val) { - uint16_t to_word = tswap16(val); - g_byte_array_append(buf, (uint8_t *) &to_word, 2); + if (target_big_endian()) { + cpu_to_be16s(&val); + } else { + cpu_to_le16s(&val); + } + g_byte_array_append(buf, (uint8_t *) &val, 2); return 2; } static inline int gdb_get_reg32(GByteArray *buf, uint32_t val) { - uint32_t to_long = tswap32(val); - g_byte_array_append(buf, (uint8_t *) &to_long, 4); + if (target_big_endian()) { + cpu_to_be32s(&val); + } else { + cpu_to_le32s(&val); + } + g_byte_array_append(buf, (uint8_t *) &val, 4); return 4; } static inline int gdb_get_reg64(GByteArray *buf, uint64_t val) { - uint64_t to_quad = tswap64(val); - g_byte_array_append(buf, (uint8_t *) &to_quad, 8); + if (target_big_endian()) { + cpu_to_be64s(&val); + } else { + cpu_to_le64s(&val); + } + g_byte_array_append(buf, (uint8_t *) &val, 8); return 8; } static inline int gdb_get_reg128(GByteArray *buf, uint64_t val_hi, uint64_t val_lo) { - uint64_t to_quad; -#if TARGET_BIG_ENDIAN - to_quad = tswap64(val_hi); - g_byte_array_append(buf, (uint8_t *) &to_quad, 8); - to_quad = tswap64(val_lo); - g_byte_array_append(buf, (uint8_t *) &to_quad, 8); -#else - to_quad = tswap64(val_lo); - g_byte_array_append(buf, (uint8_t *) &to_quad, 8); - to_quad = tswap64(val_hi); - g_byte_array_append(buf, (uint8_t *) &to_quad, 8); -#endif + uint64_t tmp[2]; + if (target_big_endian()) { + tmp[0] = cpu_to_be64(val_hi); + tmp[1] = cpu_to_be64(val_lo); + } else { + tmp[0] = cpu_to_le64(val_lo); + tmp[1] = cpu_to_le64(val_hi); + } + g_byte_array_append(buf, (uint8_t *)&tmp, 16); return 16; } diff --git a/include/hw/acpi/generic_event_device.h b/include/hw/acpi/generic_event_device.h index d2dac87..2c5b055 100644 --- a/include/hw/acpi/generic_event_device.h +++ b/include/hw/acpi/generic_event_device.h @@ -63,12 +63,13 @@ #include "hw/acpi/memory_hotplug.h" #include "hw/acpi/ghes.h" #include "hw/acpi/cpu.h" +#include "hw/acpi/pcihp.h" #include "qom/object.h" #define ACPI_POWER_BUTTON_DEVICE "PWRB" #define TYPE_ACPI_GED "acpi-ged" -OBJECT_DECLARE_SIMPLE_TYPE(AcpiGedState, ACPI_GED) +OBJECT_DECLARE_TYPE(AcpiGedState, AcpiGedClass, ACPI_GED) #define ACPI_GED_EVT_SEL_OFFSET 0x0 #define ACPI_GED_EVT_SEL_LEN 0x4 @@ -101,6 +102,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(AcpiGedState, ACPI_GED) #define ACPI_GED_PWR_DOWN_EVT 0x2 #define ACPI_GED_NVDIMM_HOTPLUG_EVT 0x4 #define ACPI_GED_CPU_HOTPLUG_EVT 0x8 +#define ACPI_GED_PCI_HOTPLUG_EVT 0x10 typedef struct GEDState { MemoryRegion evt; @@ -108,18 +110,31 @@ typedef struct GEDState { uint32_t sel; } GEDState; +#define ACPI_PCIHP_REGION_NAME "pcihp container" +#define ACPI_MEMHP_REGION_NAME "memhp container" + struct AcpiGedState { SysBusDevice parent_obj; MemHotplugState memhp_state; MemoryRegion container_memhp; CPUHotplugState cpuhp_state; MemoryRegion container_cpuhp; + AcpiPciHpState pcihp_state; + MemoryRegion container_pcihp; GEDState ged_state; uint32_t ged_event_bitmap; qemu_irq irq; AcpiGhesState ghes_state; }; +typedef struct AcpiGedClass { + /* <private> */ + SysBusDeviceClass parent_class; + + /*< public >*/ + ResettablePhases parent_phases; +} AcpiGedClass; + void build_ged_aml(Aml *table, const char* name, HotplugHandler *hotplug_dev, uint32_t ged_irq, AmlRegionSpace rs, hwaddr ged_base); void acpi_dsdt_add_power_button(Aml *scope); diff --git a/include/hw/acpi/pci.h b/include/hw/acpi/pci.h index 6359d57..20b6725 100644 --- a/include/hw/acpi/pci.h +++ b/include/hw/acpi/pci.h @@ -36,11 +36,12 @@ typedef struct AcpiMcfgInfo { void build_mcfg(GArray *table_data, BIOSLinker *linker, AcpiMcfgInfo *info, const char *oem_id, const char *oem_table_id); -Aml *aml_pci_device_dsm(void); -void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus); void build_pci_bridge_aml(AcpiDevAmlIf *adev, Aml *scope); void build_srat_generic_affinity_structures(GArray *table_data); +Aml *build_pci_host_bridge_osc_method(bool enable_native_pcie_hotplug); +Aml *build_pci_bridge_edsm(void); + #endif diff --git a/include/hw/acpi/pcihp.h b/include/hw/acpi/pcihp.h index cdc0cb8..ca6a258 100644 --- a/include/hw/acpi/pcihp.h +++ b/include/hw/acpi/pcihp.h @@ -28,11 +28,18 @@ #define HW_ACPI_PCIHP_H #include "hw/acpi/acpi.h" +#include "hw/acpi/aml-build.h" #include "hw/hotplug.h" #define ACPI_PCIHP_IO_BASE_PROP "acpi-pcihp-io-base" #define ACPI_PCIHP_IO_LEN_PROP "acpi-pcihp-io-len" +/* PCI Hot-plug registers bases. See docs/specs/acpi_pci_hotplug.rst */ +#define ACPI_PCIHP_SEJ_BASE 0x8 +#define ACPI_PCIHP_BNMR_BASE 0x10 + +#define ACPI_PCIHP_SIZE 0x0018 + typedef struct AcpiPciHpPciStatus { uint32_t up; uint32_t down; @@ -55,7 +62,7 @@ typedef struct AcpiPciHpState { bool use_acpi_root_pci_hotplug; } AcpiPciHpState; -void acpi_pcihp_init(Object *owner, AcpiPciHpState *, PCIBus *root, +void acpi_pcihp_init(Object *owner, AcpiPciHpState *, MemoryRegion *io, uint16_t io_base); bool acpi_pcihp_is_hotpluggable_bus(AcpiPciHpState *s, BusState *bus); @@ -69,6 +76,14 @@ void acpi_pcihp_device_unplug_request_cb(HotplugHandler *hotplug_dev, AcpiPciHpState *s, DeviceState *dev, Error **errp); +void build_acpi_pci_hotplug(Aml *table, AmlRegionSpace rs, uint64_t pcihp_addr); +void build_append_pci_dsm_func0_common(Aml *ctx, Aml *retvar); +void build_append_pcihp_resources(Aml *table, + uint64_t io_addr, uint64_t io_len); +bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus); + +void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus); + /* Called on reset */ void acpi_pcihp_reset(AcpiPciHpState *s); diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 4375819..365a28b 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -80,6 +80,7 @@ enum { VIRT_ACPI_GED, VIRT_NVDIMM_ACPI, VIRT_PVTIME, + VIRT_ACPI_PCIHP, VIRT_LOWMEMMAP_LAST, }; diff --git a/include/hw/arm/xen_arch_hvm.h b/include/hw/arm/xen_arch_hvm.h deleted file mode 100644 index 8fd645e..0000000 --- a/include/hw/arm/xen_arch_hvm.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef HW_XEN_ARCH_ARM_HVM_H -#define HW_XEN_ARCH_ARM_HVM_H - -#include <xen/hvm/ioreq.h> -void arch_handle_ioreq(XenIOState *state, ioreq_t *req); -void arch_xen_set_memory(XenIOState *state, - MemoryRegionSection *section, - bool add); -#endif diff --git a/include/hw/boards.h b/include/hw/boards.h index f424b2b..f94713e 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -443,6 +443,7 @@ struct MachineState { SmpCache smp_cache; struct NVDIMMState *nvdimms_state; struct NumaState *numa_state; + bool acpi_spcr_enabled; }; /* diff --git a/include/hw/cxl/cxl.h b/include/hw/cxl/cxl.h index de66ab8..998f495 100644 --- a/include/hw/cxl/cxl.h +++ b/include/hw/cxl/cxl.h @@ -23,6 +23,7 @@ #define CXL_DEVICE_REG_BAR_IDX 2 #define CXL_WINDOW_MAX 10 +#define CXL_NUM_EXTENTS_SUPPORTED 512 typedef struct PXBCXLDev PXBCXLDev; diff --git a/include/hw/cxl/cxl_device.h b/include/hw/cxl/cxl_device.h index ed6cd50..89411c8 100644 --- a/include/hw/cxl/cxl_device.h +++ b/include/hw/cxl/cxl_device.h @@ -133,6 +133,15 @@ typedef enum { CXL_MBOX_MAX = 0x20 } CXLRetCode; +/* r3.2 Section 7.6.7.6.2: Table 7-66: DSMAS Flags Bits */ +typedef enum { + CXL_DSMAS_FLAGS_NONVOLATILE = 2, + CXL_DSMAS_FLAGS_SHARABLE = 3, + CXL_DSMAS_FLAGS_HW_MANAGED_COHERENCY = 4, + CXL_DSMAS_FLAGS_IC_SPECIFIC_DC_MANAGEMENT = 5, + CXL_DSMAS_FLAGS_RDONLY = 6, +} CXLDSMASFlags; + typedef struct CXLCCI CXLCCI; typedef struct cxl_device_state CXLDeviceState; struct cxl_cmd; @@ -530,6 +539,14 @@ typedef struct CXLDCRegion { uint32_t dsmadhandle; uint8_t flags; unsigned long *blk_bitmap; + uint64_t supported_blk_size_bitmask; + QemuMutex bitmap_lock; + /* Following bools make up dsmas flags, as defined in the CDAT */ + bool nonvolatile; + bool sharable; + bool hw_managed_coherency; + bool ic_specific_dc_management; + bool rdonly; } CXLDCRegion; typedef struct CXLSetFeatureInfo { @@ -618,6 +635,7 @@ struct CXLType3Dev { CXLDCExtentList extents; CXLDCExtentGroupList extents_pending; uint32_t total_extent_count; + uint32_t nr_extents_accepted; uint32_t ext_list_gen_seq; uint8_t num_regions; /* 0-8 regions */ @@ -696,11 +714,22 @@ CXLDCExtentGroup *cxl_insert_extent_to_extent_group(CXLDCExtentGroup *group, uint16_t shared_seq); void cxl_extent_group_list_insert_tail(CXLDCExtentGroupList *list, CXLDCExtentGroup *group); -void cxl_extent_group_list_delete_front(CXLDCExtentGroupList *list); +uint32_t cxl_extent_group_list_delete_front(CXLDCExtentGroupList *list); void ct3_set_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, uint64_t len); void ct3_clear_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, uint64_t len); bool ct3_test_region_block_backed(CXLType3Dev *ct3d, uint64_t dpa, uint64_t len); +void cxl_assign_event_header(CXLEventRecordHdr *hdr, + const QemuUUID *uuid, uint32_t flags, + uint8_t length, uint64_t timestamp); +void cxl_create_dc_event_records_for_extents(CXLType3Dev *ct3d, + CXLDCEventType type, + CXLDCExtentRaw extents[], + uint32_t ext_count); +bool cxl_extents_overlaps_dpa_range(CXLDCExtentList *list, + uint64_t dpa, uint64_t len); +bool cxl_extent_groups_overlaps_dpa_range(CXLDCExtentGroupList *list, + uint64_t dpa, uint64_t len); #endif diff --git a/include/hw/cxl/cxl_events.h b/include/hw/cxl/cxl_events.h index 38cadaa..758b075 100644 --- a/include/hw/cxl/cxl_events.h +++ b/include/hw/cxl/cxl_events.h @@ -184,4 +184,19 @@ typedef struct CXLEventDynamicCapacity { uint32_t tags_avail; } QEMU_PACKED CXLEventDynamicCapacity; +/* CXL r3.1 Table 8-50: Dynamic Capacity Event Record */ +static const QemuUUID dynamic_capacity_uuid = { + .data = UUID(0xca95afa7, 0xf183, 0x4018, 0x8c, 0x2f, + 0x95, 0x26, 0x8e, 0x10, 0x1a, 0x2a), +}; + +typedef enum CXLDCEventType { + DC_EVENT_ADD_CAPACITY = 0x0, + DC_EVENT_RELEASE_CAPACITY = 0x1, + DC_EVENT_FORCED_RELEASE_CAPACITY = 0x2, + DC_EVENT_REGION_CONFIG_UPDATED = 0x3, + DC_EVENT_ADD_CAPACITY_RSP = 0x4, + DC_EVENT_CAPACITY_RELEASED = 0x5, +} CXLDCEventType; + #endif /* CXL_EVENTS_H */ diff --git a/include/hw/cxl/cxl_mailbox.h b/include/hw/cxl/cxl_mailbox.h index 9008402..a05d7cb 100644 --- a/include/hw/cxl/cxl_mailbox.h +++ b/include/hw/cxl/cxl_mailbox.h @@ -8,6 +8,7 @@ #ifndef CXL_MAILBOX_H #define CXL_MAILBOX_H +#define CXL_MBOX_CONFIG_CHANGE_COLD_RESET (1) #define CXL_MBOX_IMMEDIATE_CONFIG_CHANGE (1 << 1) #define CXL_MBOX_IMMEDIATE_DATA_CHANGE (1 << 2) #define CXL_MBOX_IMMEDIATE_POLICY_CHANGE (1 << 3) @@ -15,5 +16,10 @@ #define CXL_MBOX_SECURITY_STATE_CHANGE (1 << 5) #define CXL_MBOX_BACKGROUND_OPERATION (1 << 6) #define CXL_MBOX_BACKGROUND_OPERATION_ABORT (1 << 7) +#define CXL_MBOX_SECONDARY_MBOX_SUPPORTED (1 << 8) +#define CXL_MBOX_REQUEST_ABORT_BACKGROUND_OP_SUPPORTED (1 << 9) +#define CXL_MBOX_CEL_10_TO_11_VALID (1 << 10) +#define CXL_MBOX_CONFIG_CHANGE_CONV_RESET (1 << 11) +#define CXL_MBOX_CONFIG_CHANGE_CXL_RESET (1 << 12) #endif diff --git a/include/hw/display/edid.h b/include/hw/display/edid.h index 520f8ec..91c0a42 100644 --- a/include/hw/display/edid.h +++ b/include/hw/display/edid.h @@ -1,6 +1,8 @@ #ifndef EDID_H #define EDID_H +#define EDID_NAME_MAX_LENGTH 12 + typedef struct qemu_edid_info { const char *vendor; /* http://www.uefi.org/pnp_id_list */ const char *name; diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h index fc460b8..8755cad 100644 --- a/include/hw/i386/x86.h +++ b/include/hw/i386/x86.h @@ -25,6 +25,7 @@ #include "hw/intc/ioapic.h" #include "hw/isa/isa.h" #include "qom/object.h" +#include "system/igvm-cfg.h" struct X86MachineClass { MachineClass parent; @@ -92,6 +93,8 @@ struct X86MachineState { * which means no limitation on the guest's bus locks. */ uint64_t bus_lock_ratelimit; + + IgvmCfg *igvm; }; #define X86_MACHINE_SMM "smm" diff --git a/include/hw/i386/xen_arch_hvm.h b/include/hw/i386/xen_arch_hvm.h deleted file mode 100644 index 1000f8f..0000000 --- a/include/hw/i386/xen_arch_hvm.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef HW_XEN_ARCH_I386_HVM_H -#define HW_XEN_ARCH_I386_HVM_H - -#include <xen/hvm/ioreq.h> -#include "hw/xen/xen-hvm-common.h" - -void arch_handle_ioreq(XenIOState *state, ioreq_t *req); -void arch_xen_set_memory(XenIOState *state, - MemoryRegionSection *section, - bool add); -#endif diff --git a/include/hw/pci-host/gpex.h b/include/hw/pci-host/gpex.h index 8447153..feaf827 100644 --- a/include/hw/pci-host/gpex.h +++ b/include/hw/pci-host/gpex.h @@ -45,6 +45,7 @@ struct GPEXConfig { MemMapEntry pio; int irq; PCIBus *bus; + bool pci_native_hotplug; }; typedef struct GPEXIrq GPEXIrq; diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index df3cc7b..6b7d3ac 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -134,6 +134,15 @@ struct PCIHostDeviceAddress { unsigned int function; }; +/* + * Represents the Address Type (AT) field in a PCI request, + * see MemTxAttrs.address_type + */ +typedef enum PCIAddressType { + PCI_AT_UNTRANSLATED = 0, /* Default when no attribute is set */ + PCI_AT_TRANSLATED = 1, +} PCIAddressType; + typedef void PCIConfigWriteFunc(PCIDevice *pci_dev, uint32_t address, uint32_t data, int len); typedef uint32_t PCIConfigReadFunc(PCIDevice *pci_dev, diff --git a/include/hw/qdev-properties-system.h b/include/hw/qdev-properties-system.h index b921392..9601a11 100644 --- a/include/hw/qdev-properties-system.h +++ b/include/hw/qdev-properties-system.h @@ -32,6 +32,7 @@ extern const PropertyInfo qdev_prop_cpus390entitlement; extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; extern const PropertyInfo qdev_prop_endian_mode; extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant; +extern const PropertyInfo qdev_prop_virtio_gpu_output_list; #define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t) @@ -110,4 +111,8 @@ extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant; qdev_prop_vmapple_virtio_blk_variant, \ VMAppleVirtioBlkVariant) +#define DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST(_name, _state, _field) \ + DEFINE_PROP(_name, _state, _field, qdev_prop_virtio_gpu_output_list, \ + VirtIOGPUOutputList *) + #endif diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index 2c99856..0197aa4 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -43,11 +43,22 @@ struct PropertyInfo { ObjectPropertyRelease *release; }; +/** + * struct OnOffAutoBit64 - OnOffAuto storage with 64 elements. + * @on_bits: Bitmap of elements with "on". + * @auto_bits: Bitmap of elements with "auto". + */ +typedef struct OnOffAutoBit64 { + uint64_t on_bits; + uint64_t auto_bits; +} OnOffAutoBit64; + /*** qdev-properties.c ***/ extern const PropertyInfo qdev_prop_bit; extern const PropertyInfo qdev_prop_bit64; +extern const PropertyInfo qdev_prop_on_off_auto_bit64; extern const PropertyInfo qdev_prop_bool; extern const PropertyInfo qdev_prop_uint8; extern const PropertyInfo qdev_prop_uint16; @@ -100,6 +111,13 @@ extern const PropertyInfo qdev_prop_link; .set_default = true, \ .defval.u = (bool)_defval) +#define DEFINE_PROP_ON_OFF_AUTO_BIT64(_name, _state, _field, _bit, _defval) \ + DEFINE_PROP(_name, _state, _field, qdev_prop_on_off_auto_bit64, \ + OnOffAutoBit64, \ + .bitnr = (_bit), \ + .set_default = true, \ + .defval.i = (OnOffAuto)_defval) + #define DEFINE_PROP_BOOL(_name, _state, _field, _defval) \ DEFINE_PROP(_name, _state, _field, qdev_prop_bool, bool, \ .set_default = true, \ diff --git a/include/hw/scsi/esp.h b/include/hw/scsi/esp.h index 533d856..3526bad 100644 --- a/include/hw/scsi/esp.h +++ b/include/hw/scsi/esp.h @@ -14,7 +14,11 @@ typedef void (*ESPDMAMemoryReadWriteFunc)(void *opaque, uint8_t *buf, int len); #define ESP_FIFO_SZ 16 #define ESP_CMDFIFO_SZ 32 -typedef struct ESPState ESPState; +enum ESPASCMode { + ESP_ASC_MODE_DIS = 0, /* Disconnected */ + ESP_ASC_MODE_INI = 1, /* Initiator */ + ESP_ASC_MODE_TGT = 2 /* Target */ +}; #define TYPE_ESP "esp" OBJECT_DECLARE_SIMPLE_TYPE(ESPState, ESP) @@ -40,6 +44,7 @@ struct ESPState { uint8_t cmdfifo_cdb_offset; uint8_t lun; uint32_t do_cmd; + uint8_t asc_mode; bool data_ready; int dma_enabled; @@ -106,6 +111,13 @@ struct SysBusESPState { #define CMD_DMA 0x80 #define CMD_CMD 0x7f +#define CMD_GRP_MASK 0x70 + +#define CMD_GRP_MISC 0x00 +#define CMD_GRP_INIT 0x01 +#define CMD_GRP_TRGT 0x02 +#define CMD_GRP_DISC 0x04 + #define CMD_NOP 0x00 #define CMD_FLUSH 0x01 #define CMD_RESET 0x02 @@ -140,6 +152,7 @@ struct SysBusESPState { #define INTR_FC 0x08 #define INTR_BS 0x10 #define INTR_DC 0x20 +#define INTR_IL 0x40 #define INTR_RST 0x80 #define SEQ_0 0x0 diff --git a/include/hw/sysbus.h b/include/hw/sysbus.h index 7dc88aa..18fde8a 100644 --- a/include/hw/sysbus.h +++ b/include/hw/sysbus.h @@ -82,6 +82,7 @@ void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq); bool sysbus_is_irq_connected(SysBusDevice *dev, int n); qemu_irq sysbus_get_connected_irq(SysBusDevice *dev, int n); void sysbus_mmio_map(SysBusDevice *dev, int n, hwaddr addr); +int sysbus_mmio_map_name(SysBusDevice *dev, const char*name, hwaddr addr); void sysbus_mmio_map_overlap(SysBusDevice *dev, int n, hwaddr addr, int priority); diff --git a/include/hw/vfio/vfio-device.h b/include/hw/vfio/vfio-device.h index 1901a35..6e4d5cc 100644 --- a/include/hw/vfio/vfio-device.h +++ b/include/hw/vfio/vfio-device.h @@ -67,6 +67,8 @@ typedef struct VFIODevice { bool ram_block_discard_allowed; OnOffAuto enable_migration; OnOffAuto migration_multifd_transfer; + OnOffAuto migration_load_config_after_iter; + uint64_t migration_max_queued_buffers_size; bool migration_events; bool use_region_fds; VFIODeviceOps *ops; diff --git a/include/hw/virtio/vhost-user-blk.h b/include/hw/virtio/vhost-user-blk.h index ea085ee..a10f785 100644 --- a/include/hw/virtio/vhost-user-blk.h +++ b/include/hw/virtio/vhost-user-blk.h @@ -50,6 +50,8 @@ struct VHostUserBlk { bool connected; /* vhost_user_blk_start/vhost_user_blk_stop */ bool started_vu; + + bool skip_get_vring_base_on_force_shutdown; }; #endif diff --git a/include/hw/virtio/vhost.h b/include/hw/virtio/vhost.h index 38800a7..66be6af 100644 --- a/include/hw/virtio/vhost.h +++ b/include/hw/virtio/vhost.h @@ -1,6 +1,7 @@ #ifndef VHOST_H #define VHOST_H +#include "net/vhost_net.h" #include "hw/virtio/vhost-backend.h" #include "hw/virtio/virtio.h" #include "system/memory.h" @@ -143,6 +144,10 @@ struct vhost_net { struct vhost_dev dev; struct vhost_virtqueue vqs[2]; int backend; + const int *feature_bits; + int max_tx_queue_size; + SaveAcketFeatures *save_acked_features; + bool is_vhost_user; NetClientState *nc; }; @@ -238,6 +243,21 @@ int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings); int vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings); /** + * vhost_dev_force_stop() - force stop the vhost device + * @hdev: common vhost_dev structure + * @vdev: the VirtIODevice structure + * @vrings: true to have vrings disabled in this call + * + * Force stop the vhost device. After the device is stopped the notifiers + * can be disabled (@vhost_dev_disable_notifiers) and the device can + * be torn down (@vhost_dev_cleanup). Unlike @vhost_dev_stop, this doesn't + * attempt to flush in-flight backend requests by skipping GET_VRING_BASE + * entirely. + */ +int vhost_dev_force_stop(struct vhost_dev *hdev, VirtIODevice *vdev, + bool vrings); + +/** * DOC: vhost device configuration handling * * The VirtIO device configuration space is used for rarely changing diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h index a42957c..9f16f89 100644 --- a/include/hw/virtio/virtio-gpu.h +++ b/include/hw/virtio/virtio-gpu.h @@ -20,6 +20,7 @@ #include "hw/virtio/virtio.h" #include "qemu/log.h" #include "system/vhost-user-backend.h" +#include "qapi/qapi-types-virtio.h" #include "standard-headers/linux/virtio_gpu.h" #include "standard-headers/linux/virtio_ids.h" @@ -128,6 +129,7 @@ struct virtio_gpu_base_conf { uint32_t xres; uint32_t yres; uint64_t hostmem; + VirtIOGPUOutputList *outputs; }; struct virtio_gpu_ctrl_command { @@ -167,6 +169,7 @@ struct VirtIOGPUBaseClass { #define VIRTIO_GPU_BASE_PROPERTIES(_state, _conf) \ DEFINE_PROP_UINT32("max_outputs", _state, _conf.max_outputs, 1), \ + DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST("outputs", _state, _conf.outputs), \ DEFINE_PROP_BIT("edid", _state, _conf.flags, \ VIRTIO_GPU_FLAG_EDID_ENABLED, true), \ DEFINE_PROP_UINT32("xres", _state, _conf.xres, 1280), \ diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index b9ea9e8..73fdefc 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -144,7 +144,11 @@ typedef struct VirtioNetRssData { bool enabled_software_rss; bool redirect; bool populate_hash; - uint32_t hash_types; + bool peer_hash_available; + uint32_t runtime_hash_types; + uint32_t supported_hash_types; + uint32_t peer_hash_types; + OnOffAutoBit64 specified_hash_types; uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE]; uint16_t indirections_len; uint16_t *indirections_table; diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index 214d4a7..c594764 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -210,8 +210,14 @@ struct VirtioDeviceClass { void (*guest_notifier_mask)(VirtIODevice *vdev, int n, bool mask); int (*start_ioeventfd)(VirtIODevice *vdev); void (*stop_ioeventfd)(VirtIODevice *vdev); - /* Called before loading queues. Useful to add queues before loading. */ - int (*pre_load_queues)(VirtIODevice *vdev); + /* + * Called before loading queues. + * If the number of queues change at runtime, use @n to know the + * number and add or remove queues accordingly. + * Note that this function is called in the middle of loading vmsd; + * no assumption should be made on states being loaded from vmsd. + */ + int (*pre_load_queues)(VirtIODevice *vdev, uint32_t n); /* Saving and loading of a device; trying to deprecate save/load * use vmsd for new devices. */ diff --git a/include/hw/xen/arch_hvm.h b/include/hw/xen/arch_hvm.h index df39c81..8bacaa4 100644 --- a/include/hw/xen/arch_hvm.h +++ b/include/hw/xen/arch_hvm.h @@ -1,5 +1,11 @@ -#if defined(TARGET_I386) || defined(TARGET_X86_64) -#include "hw/i386/xen_arch_hvm.h" -#elif defined(TARGET_ARM) || defined(TARGET_AARCH64) -#include "hw/arm/xen_arch_hvm.h" +#ifndef HW_XEN_ARCH_HVM_H +#define HW_XEN_ARCH_HVM_H + +#include <xen/hvm/ioreq.h> +#include "hw/xen/xen-hvm-common.h" + +void arch_handle_ioreq(XenIOState *state, ioreq_t *req); +void arch_xen_set_memory(XenIOState *state, + MemoryRegionSection *section, + bool add); #endif diff --git a/include/net/net.h b/include/net/net.h index cdd5b10..84ee18e 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -60,6 +60,7 @@ typedef bool (HasVnetHdrLen)(NetClientState *, int); typedef void (SetOffload)(NetClientState *, int, int, int, int, int, int, int); typedef int (GetVnetHdrLen)(NetClientState *); typedef void (SetVnetHdrLen)(NetClientState *, int); +typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *); typedef int (SetVnetLE)(NetClientState *, bool); typedef int (SetVnetBE)(NetClientState *, bool); typedef struct SocketReadState SocketReadState; @@ -67,6 +68,7 @@ typedef void (SocketReadStateFinalize)(SocketReadState *rs); typedef void (NetAnnounce)(NetClientState *); typedef bool (SetSteeringEBPF)(NetClientState *, int); typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **); +typedef struct vhost_net *(GetVHostNet)(NetClientState *nc); typedef struct NetClientInfo { NetClientDriver type; @@ -89,9 +91,11 @@ typedef struct NetClientInfo { SetVnetHdrLen *set_vnet_hdr_len; SetVnetLE *set_vnet_le; SetVnetBE *set_vnet_be; + GetVnetHashSupportedTypes *get_vnet_hash_supported_types; NetAnnounce *announce; SetSteeringEBPF *set_steering_ebpf; NetCheckPeerType *check_peer_type; + GetVHostNet *get_vhost_net; } NetClientInfo; struct NetClientState { @@ -189,6 +193,7 @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int ecn, int ufo, int uso4, int uso6); int qemu_get_vnet_hdr_len(NetClientState *nc); void qemu_set_vnet_hdr_len(NetClientState *nc, int len); +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types); int qemu_set_vnet_le(NetClientState *nc, bool is_le); int qemu_set_vnet_be(NetClientState *nc, bool is_be); void qemu_macaddr_default_if_unset(MACAddr *macaddr); @@ -298,6 +303,7 @@ void net_client_parse(QemuOptsList *opts_list, const char *optstr); void show_netdevs(void); void net_init_clients(void); void net_check_clients(void); +void net_client_set_link(NetClientState **ncs, int queues, bool up); void net_cleanup(void); void hmp_host_net_add(Monitor *mon, const QDict *qdict); void hmp_host_net_remove(Monitor *mon, const QDict *qdict); diff --git a/include/net/tap.h b/include/net/tap.h index 5d58551..6f34f13 100644 --- a/include/net/tap.h +++ b/include/net/tap.h @@ -33,7 +33,4 @@ int tap_disable(NetClientState *nc); int tap_get_fd(NetClientState *nc); -struct vhost_net; -struct vhost_net *tap_get_vhost_net(NetClientState *nc); - #endif /* QEMU_NET_TAP_H */ diff --git a/include/net/vhost-user.h b/include/net/vhost-user.h deleted file mode 100644 index 35bf619..0000000 --- a/include/net/vhost-user.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * vhost-user.h - * - * Copyright (c) 2013 Virtual Open Systems Sarl. - * - * This work is licensed under the terms of the GNU GPL, version 2 or later. - * See the COPYING file in the top-level directory. - * - */ - -#ifndef VHOST_USER_H -#define VHOST_USER_H - -struct vhost_net; -struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc); -uint64_t vhost_user_get_acked_features(NetClientState *nc); -void vhost_user_save_acked_features(NetClientState *nc); - -#endif /* VHOST_USER_H */ diff --git a/include/net/vhost-vdpa.h b/include/net/vhost-vdpa.h index b81f9a6..f8d7d6c 100644 --- a/include/net/vhost-vdpa.h +++ b/include/net/vhost-vdpa.h @@ -14,8 +14,4 @@ #define TYPE_VHOST_VDPA "vhost-vdpa" -struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc); - -extern const int vdpa_feature_bits[]; - #endif /* VHOST_VDPA_H */ diff --git a/include/net/vhost_net.h b/include/net/vhost_net.h index c6a5361..879781d 100644 --- a/include/net/vhost_net.h +++ b/include/net/vhost_net.h @@ -7,11 +7,19 @@ struct vhost_net; typedef struct vhost_net VHostNetState; +typedef uint64_t (GetAckedFeatures)(NetClientState *nc); +typedef void (SaveAcketFeatures)(NetClientState *nc); + typedef struct VhostNetOptions { VhostBackendType backend_type; NetClientState *net_backend; uint32_t busyloop_timeout; unsigned int nvqs; + const int *feature_bits; + int max_tx_queue_size; + bool is_vhost_user; + GetAckedFeatures *get_acked_features; + SaveAcketFeatures *save_acked_features; void *opaque; } VhostNetOptions; @@ -41,7 +49,7 @@ void vhost_net_config_mask(VHostNetState *net, VirtIODevice *dev, bool mask); int vhost_net_notify_migration_done(VHostNetState *net, char* mac_addr); VHostNetState *get_vhost_net(NetClientState *nc); -int vhost_set_vring_enable(NetClientState * nc, int enable); +int vhost_net_set_vring_enable(NetClientState *nc, int enable); uint64_t vhost_net_get_acked_features(VHostNetState *net); diff --git a/include/qemu/accel.h b/include/qemu/accel.h index 9e821d0..d3638c7 100644 --- a/include/qemu/accel.h +++ b/include/qemu/accel.h @@ -26,43 +26,8 @@ #include "qom/object.h" #include "exec/hwaddr.h" -struct AccelState { - /*< private >*/ - Object parent_obj; -}; - -typedef struct AccelClass { - /*< private >*/ - ObjectClass parent_class; - /*< public >*/ - - const char *name; - /* Cached by accel_init_ops_interfaces() when created */ - AccelOpsClass *ops; - - int (*init_machine)(AccelState *as, MachineState *ms); - bool (*cpu_common_realize)(CPUState *cpu, Error **errp); - void (*cpu_common_unrealize)(CPUState *cpu); - - /* system related hooks */ - void (*setup_post)(AccelState *as); - void (*pre_resume_vm)(AccelState *as, bool step_pending); - bool (*has_memory)(AccelState *accel, AddressSpace *as, - hwaddr start_addr, hwaddr size); - - /* gdbstub related hooks */ - int (*gdbstub_supported_sstep_flags)(AccelState *as); - - bool *allowed; - /* - * Array of global properties that would be applied when specific - * accelerator is chosen. It works like MachineClass.compat_props - * but it's for accelerators not machines. Accelerator-provided - * global properties may be overridden by machine-type - * compat_props or user-provided global properties. - */ - GPtrArray *compat_props; -} AccelClass; +typedef struct AccelState AccelState; +typedef struct AccelClass AccelClass; #define TYPE_ACCEL "accel" diff --git a/include/qemu/bswap.h b/include/qemu/bswap.h index 9a11764..39ba640 100644 --- a/include/qemu/bswap.h +++ b/include/qemu/bswap.h @@ -1,6 +1,8 @@ #ifndef BSWAP_H #define BSWAP_H +#include "qemu/target-info.h" + #undef bswap16 #define bswap16(_x) __builtin_bswap16(_x) #undef bswap32 @@ -432,4 +434,75 @@ DO_STN_LDN_P(be) #undef le_bswaps #undef be_bswaps + +/* Return ld{word}_{le,be}_p following target endianness. */ +#define LOAD_IMPL(word, args...) \ +do { \ + if (target_big_endian()) { \ + return glue(glue(ld, word), _be_p)(args); \ + } else { \ + return glue(glue(ld, word), _le_p)(args); \ + } \ +} while (0) + +static inline int lduw_p(const void *ptr) +{ + LOAD_IMPL(uw, ptr); +} + +static inline int ldsw_p(const void *ptr) +{ + LOAD_IMPL(sw, ptr); +} + +static inline int ldl_p(const void *ptr) +{ + LOAD_IMPL(l, ptr); +} + +static inline uint64_t ldq_p(const void *ptr) +{ + LOAD_IMPL(q, ptr); +} + +static inline uint64_t ldn_p(const void *ptr, int sz) +{ + LOAD_IMPL(n, ptr, sz); +} + +#undef LOAD_IMPL + +/* Call st{word}_{le,be}_p following target endianness. */ +#define STORE_IMPL(word, args...) \ +do { \ + if (target_big_endian()) { \ + glue(glue(st, word), _be_p)(args); \ + } else { \ + glue(glue(st, word), _le_p)(args); \ + } \ +} while (0) + + +static inline void stw_p(void *ptr, uint16_t v) +{ + STORE_IMPL(w, ptr, v); +} + +static inline void stl_p(void *ptr, uint32_t v) +{ + STORE_IMPL(l, ptr, v); +} + +static inline void stq_p(void *ptr, uint64_t v) +{ + STORE_IMPL(q, ptr, v); +} + +static inline void stn_p(void *ptr, int sz, uint64_t v) +{ + STORE_IMPL(n, ptr, sz, v); +} + +#undef STORE_IMPL + #endif /* BSWAP_H */ diff --git a/include/qemu/job.h b/include/qemu/job.h index a5a0415..ead3157 100644 --- a/include/qemu/job.h +++ b/include/qemu/job.h @@ -263,7 +263,7 @@ struct JobDriver { * This callback will not be invoked if the job has already failed. * If it fails, abort and then clean will be called. */ - int (*prepare)(Job *job); + int GRAPH_UNLOCKED_PTR (*prepare)(Job *job); /** * If the callback is not NULL, it will be invoked when all the jobs @@ -283,7 +283,7 @@ struct JobDriver { * All jobs will complete with a call to either .commit() or .abort() but * never both. */ - void (*abort)(Job *job); + void GRAPH_UNLOCKED_PTR (*abort)(Job *job); /** * If the callback is not NULL, it will be invoked after a call to either diff --git a/include/qemu/log.h b/include/qemu/log.h index 60da703..aae7298 100644 --- a/include/qemu/log.h +++ b/include/qemu/log.h @@ -84,6 +84,8 @@ typedef struct QEMULogItem { extern const QEMULogItem qemu_log_items[]; +ssize_t rust_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); + bool qemu_set_log(int log_flags, Error **errp); bool qemu_set_log_filename(const char *filename, Error **errp); bool qemu_set_log_filename_flags(const char *name, int flags, Error **errp); diff --git a/include/qemu/target-info-impl.h b/include/qemu/target-info-impl.h index 1b51cbc..17887f6 100644 --- a/include/qemu/target-info-impl.h +++ b/include/qemu/target-info-impl.h @@ -9,17 +9,21 @@ #ifndef QEMU_TARGET_INFO_IMPL_H #define QEMU_TARGET_INFO_IMPL_H -#include "qemu/target-info.h" +#include "qapi/qapi-types-machine.h" typedef struct TargetInfo { /* runtime equivalent of TARGET_NAME definition */ const char *target_name; + /* related to TARGET_ARCH definition */ + SysEmuTarget target_arch; /* runtime equivalent of TARGET_LONG_BITS definition */ unsigned long_bits; /* runtime equivalent of CPU_RESOLVING_TYPE definition */ const char *cpu_type; /* QOM typename machines for this binary must implement */ const char *machine_typename; + /* related to TARGET_BIG_ENDIAN definition */ + EndianMode endianness; } TargetInfo; /** diff --git a/include/qemu/target-info-qapi.h b/include/qemu/target-info-qapi.h new file mode 100644 index 0000000..d5ce052 --- /dev/null +++ b/include/qemu/target-info-qapi.h @@ -0,0 +1,29 @@ +/* + * QEMU target info API (returning QAPI types) + * + * Copyright (c) Linaro + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_TARGET_INFO_EXTRA_H +#define QEMU_TARGET_INFO_EXTRA_H + +#include "qapi/qapi-types-common.h" +#include "qapi/qapi-types-machine.h" + +/** + * target_arch: + * + * Returns: QAPI SysEmuTarget enum (e.g. SYS_EMU_TARGET_X86_64). + */ +SysEmuTarget target_arch(void); + +/** + * target_endian_mode: + * + * Returns: QAPI EndianMode enum (e.g. ENDIAN_MODE_LITTLE). + */ +EndianMode target_endian_mode(void); + +#endif diff --git a/include/qemu/target-info.h b/include/qemu/target-info.h index 850a295..abcf25d 100644 --- a/include/qemu/target-info.h +++ b/include/qemu/target-info.h @@ -1,5 +1,5 @@ /* - * QEMU target info API + * QEMU target info API (returning native types) * * Copyright (c) Linaro * @@ -38,4 +38,16 @@ const char *target_machine_typename(void); */ const char *target_cpu_type(void); +/** + * target_big_endian: + * + * Returns: %true if the (default) endianness of the target is big endian, + * %false otherwise. + * + * Common code should normally never need to know about the endianness of + * the target, so please do *not* use this function unless you know very + * well what you are doing! + */ +bool target_big_endian(void); + #endif diff --git a/include/system/block-backend-global-state.h b/include/system/block-backend-global-state.h index 35b5e83..c384964 100644 --- a/include/system/block-backend-global-state.h +++ b/include/system/block-backend-global-state.h @@ -55,7 +55,7 @@ void monitor_remove_blk(BlockBackend *blk); BlockBackendPublic *blk_get_public(BlockBackend *blk); -void blk_remove_bs(BlockBackend *blk); +void GRAPH_UNLOCKED blk_remove_bs(BlockBackend *blk); int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp); int blk_replace_bs(BlockBackend *blk, BlockDriverState *new_bs, Error **errp); bool GRAPH_RDLOCK bdrv_has_blk(BlockDriverState *bs); @@ -78,8 +78,8 @@ int blk_make_zero(BlockBackend *blk, BdrvRequestFlags flags); void blk_aio_cancel(BlockAIOCB *acb); int blk_commit_all(void); bool blk_in_drain(BlockBackend *blk); -void blk_drain(BlockBackend *blk); -void blk_drain_all(void); +void GRAPH_UNLOCKED blk_drain(BlockBackend *blk); +void GRAPH_UNLOCKED blk_drain_all(void); void blk_set_on_error(BlockBackend *blk, BlockdevOnError on_read_error, BlockdevOnError on_write_error); bool blk_supports_write_perm(BlockBackend *blk); @@ -109,7 +109,7 @@ int blk_probe_blocksizes(BlockBackend *blk, BlockSizes *bsz); int blk_probe_geometry(BlockBackend *blk, HDGeometry *geo); void blk_set_io_limits(BlockBackend *blk, ThrottleConfig *cfg); -void blk_io_limits_disable(BlockBackend *blk); +void GRAPH_UNLOCKED blk_io_limits_disable(BlockBackend *blk); void blk_io_limits_enable(BlockBackend *blk, const char *group); void blk_io_limits_update_group(BlockBackend *blk, const char *group); void blk_set_force_allow_inactivate(BlockBackend *blk); diff --git a/include/system/confidential-guest-support.h b/include/system/confidential-guest-support.h index ea46b50..0cc8b26 100644 --- a/include/system/confidential-guest-support.h +++ b/include/system/confidential-guest-support.h @@ -19,6 +19,7 @@ #define QEMU_CONFIDENTIAL_GUEST_SUPPORT_H #include "qom/object.h" +#include "exec/hwaddr.h" #define TYPE_CONFIDENTIAL_GUEST_SUPPORT "confidential-guest-support" OBJECT_DECLARE_TYPE(ConfidentialGuestSupport, @@ -26,6 +27,40 @@ OBJECT_DECLARE_TYPE(ConfidentialGuestSupport, CONFIDENTIAL_GUEST_SUPPORT) +typedef enum ConfidentialGuestPlatformType { + CGS_PLATFORM_SEV, + CGS_PLATFORM_SEV_ES, + CGS_PLATFORM_SEV_SNP, +} ConfidentialGuestPlatformType; + +typedef enum ConfidentialGuestMemoryType { + CGS_MEM_RAM, + CGS_MEM_RESERVED, + CGS_MEM_ACPI, + CGS_MEM_NVS, + CGS_MEM_UNUSABLE, +} ConfidentialGuestMemoryType; + +typedef struct ConfidentialGuestMemoryMapEntry { + uint64_t gpa; + uint64_t size; + ConfidentialGuestMemoryType type; +} ConfidentialGuestMemoryMapEntry; + +typedef enum ConfidentialGuestPageType { + CGS_PAGE_TYPE_NORMAL, + CGS_PAGE_TYPE_VMSA, + CGS_PAGE_TYPE_ZERO, + CGS_PAGE_TYPE_UNMEASURED, + CGS_PAGE_TYPE_SECRETS, + CGS_PAGE_TYPE_CPUID, + CGS_PAGE_TYPE_REQUIRED_MEMORY, +} ConfidentialGuestPageType; + +typedef enum ConfidentialGuestPolicyType { + GUEST_POLICY_SEV, +} ConfidentialGuestPolicyType; + struct ConfidentialGuestSupport { Object parent; @@ -64,6 +99,59 @@ typedef struct ConfidentialGuestSupportClass { int (*kvm_init)(ConfidentialGuestSupport *cgs, Error **errp); int (*kvm_reset)(ConfidentialGuestSupport *cgs, Error **errp); + + /* + * Check to see if this confidential guest supports a particular + * platform or configuration. + * + * Return true if supported or false if not supported. + */ + bool (*check_support)(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary); + + /* + * Configure part of the state of a guest for a particular set of data, page + * type and gpa. This can be used for example to pre-populate and measure + * guest memory contents, define private ranges or set the initial CPU state + * for one or more CPUs. + * + * If memory_type is CGS_PAGE_TYPE_VMSA then ptr points to the initial CPU + * context for a virtual CPU. The format of the data depends on the type of + * confidential virtual machine. For example, for SEV-ES ptr will point to a + * vmcb_save_area structure that should be copied into guest memory at the + * address specified in gpa. The cpu_index parameter contains the index of + * the CPU the VMSA applies to. + */ + int (*set_guest_state)(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp); + + /* + * Set the guest policy. The policy can be used to configure the + * confidential platform, such as if debug is enabled or not and can contain + * information about expected launch measurements, signed verification of + * guest configuration and other platform data. + * + * The format of the policy data is specific to each platform. For example, + * SEV-SNP uses a policy bitfield in the 'policy' argument and provides an + * ID block and ID authentication in the 'policy_data' parameters. The type + * of policy data is identified by the 'policy_type' argument. + */ + int (*set_guest_policy)(ConfidentialGuestPolicyType policy_type, + uint64_t policy, + void *policy_data1, uint32_t policy_data1_size, + void *policy_data2, uint32_t policy_data2_size, + Error **errp); + + /* + * Iterate the system memory map, getting the entry with the given index + * that can be populated into guest memory. + * + * Returns 0 for ok, 1 if the index is out of range and -1 on error. + */ + int (*get_mem_map_entry)(int index, ConfidentialGuestMemoryMapEntry *entry, + Error **errp); } ConfidentialGuestSupportClass; static inline int confidential_guest_kvm_init(ConfidentialGuestSupport *cgs, diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h index 5150c7d..a3b06a3 100644 --- a/include/system/hvf_int.h +++ b/include/system/hvf_int.h @@ -14,6 +14,7 @@ #include "qemu/queue.h" #include "exec/vaddr.h" #include "qom/object.h" +#include "accel/accel-ops.h" #ifdef __aarch64__ #include <Hypervisor/Hypervisor.h> @@ -45,7 +46,7 @@ typedef struct hvf_vcpu_caps { } hvf_vcpu_caps; struct HVFState { - AccelState parent; + AccelState parent_obj; hvf_slot slots[32]; int num_slots; diff --git a/include/system/igvm-cfg.h b/include/system/igvm-cfg.h new file mode 100644 index 0000000..944f23a --- /dev/null +++ b/include/system/igvm-cfg.h @@ -0,0 +1,49 @@ +/* + * QEMU IGVM interface + * + * Copyright (C) 2024 SUSE + * + * Authors: + * Roy Hopkins <roy.hopkins@randomman.co.uk> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_IGVM_CFG_H +#define QEMU_IGVM_CFG_H + +#include "qom/object.h" + +typedef struct IgvmCfg { + ObjectClass parent_class; + + /* + * filename: Filename that specifies a file that contains the configuration + * of the guest in Independent Guest Virtual Machine (IGVM) + * format. + */ + char *filename; +} IgvmCfg; + +typedef struct IgvmCfgClass { + ObjectClass parent_class; + + /* + * If an IGVM filename has been specified then process the IGVM file. + * Performs a no-op if no filename has been specified. + * If onlyVpContext is true then only the IGVM_VHT_VP_CONTEXT entries + * in the IGVM file will be processed, allowing information about the + * CPU state to be determined before processing the entire file. + * + * Returns 0 for ok and -1 on error. + */ + int (*process)(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, + bool onlyVpContext, Error **errp); + +} IgvmCfgClass; + +#define TYPE_IGVM_CFG "igvm-cfg" + +OBJECT_DECLARE_TYPE(IgvmCfg, IgvmCfgClass, IGVM_CFG) + +#endif diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h index 756a3c0..9247493 100644 --- a/include/system/kvm_int.h +++ b/include/system/kvm_int.h @@ -14,6 +14,7 @@ #include "qemu/accel.h" #include "qemu/queue.h" #include "system/kvm.h" +#include "accel/accel-ops.h" #include "hw/boards.h" #include "hw/i386/topology.h" #include "io/channel-socket.h" diff --git a/include/system/memory.h b/include/system/memory.h index 46248d4..e2cd6ed 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -19,7 +19,6 @@ #include "exec/memattrs.h" #include "exec/memop.h" #include "exec/ramlist.h" -#include "exec/tswap.h" #include "qemu/bswap.h" #include "qemu/queue.h" #include "qemu/int128.h" @@ -109,15 +108,34 @@ struct MemoryRegionSection { typedef struct IOMMUTLBEntry IOMMUTLBEntry; -/* See address_space_translate: bit 0 is read, bit 1 is write. */ +/* + * See address_space_translate: + * - bit 0 : read + * - bit 1 : write + * - bit 2 : exec + * - bit 3 : priv + * - bit 4 : global + * - bit 5 : untranslated only + */ typedef enum { IOMMU_NONE = 0, IOMMU_RO = 1, IOMMU_WO = 2, IOMMU_RW = 3, + IOMMU_EXEC = 4, + IOMMU_PRIV = 8, + IOMMU_GLOBAL = 16, + IOMMU_UNTRANSLATED_ONLY = 32, } IOMMUAccessFlags; -#define IOMMU_ACCESS_FLAG(r, w) (((r) ? IOMMU_RO : 0) | ((w) ? IOMMU_WO : 0)) +#define IOMMU_ACCESS_FLAG(r, w) (((r) ? IOMMU_RO : 0) | \ + ((w) ? IOMMU_WO : 0)) +#define IOMMU_ACCESS_FLAG_FULL(r, w, x, p, g, uo) \ + (IOMMU_ACCESS_FLAG(r, w) | \ + ((x) ? IOMMU_EXEC : 0) | \ + ((p) ? IOMMU_PRIV : 0) | \ + ((g) ? IOMMU_GLOBAL : 0) | \ + ((uo) ? IOMMU_UNTRANSLATED_ONLY : 0)) struct IOMMUTLBEntry { AddressSpace *target_as; @@ -125,6 +143,7 @@ struct IOMMUTLBEntry { hwaddr translated_addr; hwaddr addr_mask; /* 0xfff = 4k translation */ IOMMUAccessFlags perm; + uint32_t pasid; }; /* diff --git a/include/system/runstate.h b/include/system/runstate.h index fdd5c4a..929379a 100644 --- a/include/system/runstate.h +++ b/include/system/runstate.h @@ -14,11 +14,51 @@ void runstate_replay_enable(void); typedef void VMChangeStateHandler(void *opaque, bool running, RunState state); typedef int VMChangeStateHandlerWithRet(void *opaque, bool running, RunState state); +/** + * qemu_add_vm_change_state_handler: + * @cb: the callback to invoke + * @opaque: user data passed to the callback + * + * Register a callback function that is invoked when the vm starts or stops + * running. + * + * Returns: an entry to be freed using qemu_del_vm_change_state_handler() + */ VMChangeStateEntry *qemu_add_vm_change_state_handler(VMChangeStateHandler *cb, void *opaque); +/** + * qemu_add_vm_change_state_handler_prio: + * @cb: the callback to invoke + * @opaque: user data passed to the callback + * @priority: low priorities execute first when the vm runs and the reverse is + * true when the vm stops + * + * Register a callback function that is invoked when the vm starts or stops + * running. + * + * Returns: an entry to be freed using qemu_del_vm_change_state_handler() + */ VMChangeStateEntry *qemu_add_vm_change_state_handler_prio( VMChangeStateHandler *cb, void *opaque, int priority); VMChangeStateEntry * +/** + * qemu_add_vm_change_state_handler_prio_full: + * @cb: the main callback to invoke + * @prepare_cb: a callback to invoke before the main callback + * @cb_ret: the main callback to invoke with return value + * @opaque: user data passed to the callbacks + * @priority: low priorities execute first when the vm runs and the reverse is + * true when the vm stops + * + * Register a main callback function and an optional prepare callback function + * that are invoked when the vm starts or stops running. The main callback and + * the prepare callback are called in two separate phases: First all prepare + * callbacks are called and only then all main callbacks are called. As its + * name suggests, the prepare callback can be used to do some preparatory work + * before invoking the main callback. + * + * Returns: an entry to be freed using qemu_del_vm_change_state_handler() + */ qemu_add_vm_change_state_handler_prio_full(VMChangeStateHandler *cb, VMChangeStateHandler *prepare_cb, VMChangeStateHandlerWithRet *cb_ret, @@ -107,6 +147,7 @@ void qemu_system_vmstop_request(RunState reason); void qemu_system_vmstop_request_prepare(void); bool qemu_vmstop_requested(RunState *r); ShutdownCause qemu_shutdown_requested_get(void); +bool qemu_force_shutdown_requested(void); ShutdownCause qemu_reset_requested_get(void); void qemu_system_killed(int signal, pid_t pid); void qemu_system_reset(ShutdownCause reason); diff --git a/include/tcg/tcg.h b/include/tcg/tcg.h index 0c2a319..a6d9aa5 100644 --- a/include/tcg/tcg.h +++ b/include/tcg/tcg.h @@ -1005,5 +1005,7 @@ static inline const TCGOpcode *tcg_swap_vecop_list(const TCGOpcode *n) bool tcg_can_emit_vecop_list(const TCGOpcode *, TCGType, unsigned); void tcg_dump_ops(TCGContext *s, FILE *f, bool have_prefs); +/* tcg_dump_stats: Append TCG statistics to @buf */ +void tcg_dump_stats(GString *buf); #endif /* TCG_H */ diff --git a/include/ui/console.h b/include/ui/console.h index 46b3128..98feaa5 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -422,6 +422,9 @@ bool console_gl_check_format(DisplayChangeListener *dcl, pixman_format_code_t format); void surface_gl_create_texture(QemuGLShader *gls, DisplaySurface *surface); +bool surface_gl_create_texture_from_fd(DisplaySurface *surface, + int fd, GLuint *texture, + GLuint *mem_obj); void surface_gl_update_texture(QemuGLShader *gls, DisplaySurface *surface, int x, int y, int w, int h); diff --git a/include/ui/gtk.h b/include/ui/gtk.h index d394404..3e6ce3c 100644 --- a/include/ui/gtk.h +++ b/include/ui/gtk.h @@ -41,6 +41,7 @@ typedef struct VirtualGfxConsole { DisplaySurface *ds; pixman_image_t *convert; cairo_surface_t *surface; + double preferred_scale; double scale_x; double scale_y; #if defined(CONFIG_OPENGL) @@ -140,6 +141,7 @@ struct GtkDisplayState { GdkCursor *null_cursor; Notifier mouse_mode_notifier; gboolean free_scale; + gboolean keep_aspect_ratio; bool external_pause_update; diff --git a/include/ui/spice-display.h b/include/ui/spice-display.h index e1a9b36..690ece7 100644 --- a/include/ui/spice-display.h +++ b/include/ui/spice-display.h @@ -132,6 +132,9 @@ struct SimpleSpiceDisplay { egl_fb guest_fb; egl_fb blit_fb; egl_fb cursor_fb; + bool backing_y_0_top; + bool blit_scanout_texture; + bool new_scanout_texture; bool have_hot; #endif }; @@ -151,6 +154,8 @@ struct SimpleSpiceCursor { }; extern bool spice_opengl; +extern bool spice_remote_client; +extern int spice_max_refresh_rate; int qemu_spice_rect_is_empty(const QXLRect* r); void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r); diff --git a/include/ui/surface.h b/include/ui/surface.h index f16f7be..006b198 100644 --- a/include/ui/surface.h +++ b/include/ui/surface.h @@ -22,6 +22,7 @@ typedef struct DisplaySurface { GLenum glformat; GLenum gltype; GLuint texture; + GLuint mem_obj; #endif qemu_pixman_shareable share_handle; uint32_t share_handle_offset; diff --git a/include/user/abitypes.h b/include/user/abitypes.h index 7528124..be7a876 100644 --- a/include/user/abitypes.h +++ b/include/user/abitypes.h @@ -6,7 +6,6 @@ #endif #include "exec/cpu-defs.h" -#include "exec/tswap.h" #include "user/tswap-target.h" #ifdef TARGET_ABI32 diff --git a/linux-headers/asm-x86/kvm.h b/linux-headers/asm-x86/kvm.h index cd275ae..f0c1a73 100644 --- a/linux-headers/asm-x86/kvm.h +++ b/linux-headers/asm-x86/kvm.h @@ -963,7 +963,13 @@ struct kvm_tdx_cmd { struct kvm_tdx_capabilities { __u64 supported_attrs; __u64 supported_xfam; - __u64 reserved[254]; + + __u64 kernel_tdvmcallinfo_1_r11; + __u64 user_tdvmcallinfo_1_r11; + __u64 kernel_tdvmcallinfo_1_r12; + __u64 user_tdvmcallinfo_1_r12; + + __u64 reserved[250]; /* Configurable CPUID bits for userspace */ struct kvm_cpuid2 cpuid; diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 0690743..32c5885 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -459,6 +459,10 @@ struct kvm_run { __u64 leaf; __u64 r11, r12, r13, r14; } get_tdvmcall_info; + struct { + __u64 ret; + __u64 vector; + } setup_event_notify; }; } tdx; /* Fix the size of the union. */ diff --git a/linux-user/main.c b/linux-user/main.c index f4f2007..68972f0 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -42,6 +42,7 @@ #include "user/page-protection.h" #include "exec/gdbstub.h" #include "gdbstub/user.h" +#include "accel/accel-ops.h" #include "tcg/startup.h" #include "qemu/timer.h" #include "qemu/envlist.h" diff --git a/meson.build b/meson.build index b5f74aa..c2bc3ee 100644 --- a/meson.build +++ b/meson.build @@ -1285,6 +1285,10 @@ if not get_option('slirp').auto() or have_system endif endif +enable_passt = get_option('passt') \ + .require(host_os == 'linux', error_message: 'passt is supported only on Linux') \ + .allowed() + vde = not_found if not get_option('vde').auto() or have_system or have_tools vde = cc.find_library('vdeplug', has_headers: ['libvdeplug.h'], @@ -1424,6 +1428,12 @@ if host_os == 'linux' and (have_system or have_tools) method: 'pkg-config', required: get_option('libudev')) endif +igvm = not_found +if not get_option('igvm').auto() or have_system + igvm = dependency('igvm', version: '>= 0.3.0', + method: 'pkg-config', + required: get_option('igvm')) +endif mpathlibs = [libudev] mpathpersist = not_found @@ -2538,6 +2548,7 @@ if seccomp.found() config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc) endif config_host_data.set('CONFIG_PIXMAN', pixman.found()) +config_host_data.set('CONFIG_PASST', enable_passt) config_host_data.set('CONFIG_SLIRP', slirp.found()) config_host_data.set('CONFIG_SNAPPY', snappy.found()) config_host_data.set('CONFIG_SOLARIS', host_os == 'sunos') @@ -2601,6 +2612,7 @@ config_host_data.set('CONFIG_CFI', get_option('cfi')) config_host_data.set('CONFIG_SELINUX', selinux.found()) config_host_data.set('CONFIG_XEN_BACKEND', xen.found()) config_host_data.set('CONFIG_LIBDW', libdw.found()) +config_host_data.set('CONFIG_IGVM', igvm.found()) if xen.found() # protect from xen.version() having less than three components xen_version = xen.version().split('.') + ['0', '0'] @@ -4926,6 +4938,7 @@ if host_os == 'darwin' summary_info += {'vmnet.framework support': vmnet} endif summary_info += {'AF_XDP support': libxdp} +summary_info += {'passt support': enable_passt} summary_info += {'slirp support': slirp} summary_info += {'vde support': vde} summary_info += {'netmap support': have_netmap} @@ -4965,6 +4978,7 @@ summary_info += {'seccomp support': seccomp} summary_info += {'GlusterFS support': glusterfs} summary_info += {'hv-balloon support': hv_balloon} summary_info += {'TPM support': have_tpm} +summary_info += {'IGVM support': igvm} summary_info += {'libssh support': libssh} summary_info += {'lzo support': lzo} summary_info += {'snappy support': snappy} diff --git a/meson_options.txt b/meson_options.txt index a442be2..fff1521 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -117,6 +117,8 @@ option('tpm', type : 'feature', value : 'auto', description: 'TPM support') option('valgrind', type : 'feature', value: 'auto', description: 'valgrind debug support for coroutine stacks') +option('igvm', type: 'feature', value: 'auto', + description: 'Independent Guest Virtual Machine (IGVM) file support') # Do not enable it by default even for Mingw32, because it doesn't # work on Wine. @@ -234,6 +236,8 @@ option('pixman', type : 'feature', value : 'auto', description: 'pixman support') option('slirp', type: 'feature', value: 'auto', description: 'libslirp user mode network backend support') +option('passt', type: 'feature', value: 'auto', + description: 'passt network backend support') option('vde', type : 'feature', value : 'auto', description: 'vde network backend support') option('vmnet', type : 'feature', value : 'auto', diff --git a/net/af-xdp.c b/net/af-xdp.c index 01c5fb9..14f302e 100644 --- a/net/af-xdp.c +++ b/net/af-xdp.c @@ -49,9 +49,12 @@ typedef struct AFXDPState { char *buffer; struct xsk_umem *umem; - uint32_t n_queues; uint32_t xdp_flags; bool inhibit; + + char *map_path; + int map_fd; + uint32_t map_start_index; } AFXDPState; #define AF_XDP_BATCH_SIZE 64 @@ -261,6 +264,7 @@ static void af_xdp_send(void *opaque) static void af_xdp_cleanup(NetClientState *nc) { AFXDPState *s = DO_UPCAST(AFXDPState, nc, nc); + int idx; qemu_purge_queued_packets(nc); @@ -275,13 +279,17 @@ static void af_xdp_cleanup(NetClientState *nc) qemu_vfree(s->buffer); s->buffer = NULL; - /* Remove the program if it's the last open queue. */ - if (!s->inhibit && nc->queue_index == s->n_queues - 1 && s->xdp_flags - && bpf_xdp_detach(s->ifindex, s->xdp_flags, NULL) != 0) { - fprintf(stderr, - "af-xdp: unable to remove XDP program from '%s', ifindex: %d\n", - s->ifname, s->ifindex); + if (s->map_fd >= 0) { + idx = nc->queue_index + s->map_start_index; + if (bpf_map_delete_elem(s->map_fd, &idx)) { + fprintf(stderr, "af-xdp: unable to remove AF_XDP socket from map" + " %s\n", s->map_path); + } + close(s->map_fd); + s->map_fd = -1; } + g_free(s->map_path); + s->map_path = NULL; } static int af_xdp_umem_create(AFXDPState *s, int sock_fd, Error **errp) @@ -323,7 +331,7 @@ static int af_xdp_umem_create(AFXDPState *s, int sock_fd, Error **errp) s->pool = g_new(uint64_t, n_descs); /* Fill the pool in the opposite order, because it's a LIFO queue. */ - for (i = n_descs; i >= 0; i--) { + for (i = n_descs - 1; i >= 0; i--) { s->pool[i] = i * XSK_UMEM__DEFAULT_FRAME_SIZE; } s->n_pool = n_descs; @@ -345,7 +353,6 @@ static int af_xdp_socket_create(AFXDPState *s, }; int queue_id, error = 0; - s->inhibit = opts->has_inhibit && opts->inhibit; if (s->inhibit) { cfg.libxdp_flags |= XSK_LIBXDP_FLAGS__INHIBIT_PROG_LOAD; } @@ -396,6 +403,35 @@ static int af_xdp_socket_create(AFXDPState *s, return 0; } +static int af_xdp_update_xsk_map(AFXDPState *s, Error **errp) +{ + int xsk_fd, idx, error = 0; + + if (!s->map_path) { + return 0; + } + + s->map_fd = bpf_obj_get(s->map_path); + if (s->map_fd < 0) { + error = errno; + } else { + xsk_fd = xsk_socket__fd(s->xsk); + idx = s->nc.queue_index + s->map_start_index; + if (bpf_map_update_elem(s->map_fd, &idx, &xsk_fd, 0)) { + error = errno; + } + } + + if (error) { + error_setg_errno(errp, error, + "failed to insert AF_XDP socket into map %s", + s->map_path); + return -1; + } + + return 0; +} + /* NetClientInfo methods. */ static NetClientInfo net_af_xdp_info = { .type = NET_CLIENT_DRIVER_AF_XDP, @@ -444,12 +480,14 @@ int net_init_af_xdp(const Netdev *netdev, { const NetdevAFXDPOptions *opts = &netdev->u.af_xdp; NetClientState *nc, *nc0 = NULL; + int32_t map_start_index; unsigned int ifindex; uint32_t prog_id = 0; g_autofree int *sock_fds = NULL; int64_t i, queues; Error *err = NULL; AFXDPState *s; + bool inhibit; ifindex = if_nametoindex(opts->ifname); if (!ifindex) { @@ -465,8 +503,28 @@ int net_init_af_xdp(const Netdev *netdev, return -1; } - if ((opts->has_inhibit && opts->inhibit) != !!opts->sock_fds) { - error_setg(errp, "'inhibit=on' requires 'sock-fds' and vice versa"); + inhibit = opts->has_inhibit && opts->inhibit; + if (inhibit && !opts->sock_fds && !opts->map_path) { + error_setg(errp, "'inhibit=on' requires 'sock-fds' or 'map-path'"); + return -1; + } + if (!inhibit && (opts->sock_fds || opts->map_path)) { + error_setg(errp, "'sock-fds' and 'map-path' require 'inhibit=on'"); + return -1; + } + if (opts->sock_fds && opts->map_path) { + error_setg(errp, "'sock-fds' and 'map-path' are mutually exclusive"); + return -1; + } + if (!opts->map_path && opts->has_map_start_index) { + error_setg(errp, "'map-start-index' requires 'map-path'"); + return -1; + } + + map_start_index = opts->has_map_start_index ? opts->map_start_index : 0; + if (map_start_index < 0) { + error_setg(errp, "'map-start-index' cannot be negative (%d)", + map_start_index); return -1; } @@ -490,21 +548,23 @@ int net_init_af_xdp(const Netdev *netdev, pstrcpy(s->ifname, sizeof(s->ifname), opts->ifname); s->ifindex = ifindex; - s->n_queues = queues; + s->inhibit = inhibit; + + s->map_path = g_strdup(opts->map_path); + s->map_start_index = map_start_index; + s->map_fd = -1; - if (af_xdp_umem_create(s, sock_fds ? sock_fds[i] : -1, errp) - || af_xdp_socket_create(s, opts, errp)) { - /* Make sure the XDP program will be removed. */ - s->n_queues = i; - error_propagate(errp, err); + if (af_xdp_umem_create(s, sock_fds ? sock_fds[i] : -1, &err) || + af_xdp_socket_create(s, opts, &err) || + af_xdp_update_xsk_map(s, &err)) { goto err; } } - if (nc0) { + if (nc0 && !inhibit) { s = DO_UPCAST(AFXDPState, nc, nc0); if (bpf_xdp_query_id(s->ifindex, s->xdp_flags, &prog_id) || !prog_id) { - error_setg_errno(errp, errno, + error_setg_errno(&err, errno, "no XDP program loaded on '%s', ifindex: %d", s->ifname, s->ifindex); goto err; @@ -518,6 +578,7 @@ int net_init_af_xdp(const Netdev *netdev, err: if (nc0) { qemu_del_net_client(nc0); + error_propagate(errp, err); } return -1; diff --git a/net/clients.h b/net/clients.h index be53794..e786ab4 100644 --- a/net/clients.h +++ b/net/clients.h @@ -29,6 +29,10 @@ int net_init_dump(const Netdev *netdev, const char *name, NetClientState *peer, Error **errp); +#ifdef CONFIG_PASST +int net_init_passt(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp); +#endif #ifdef CONFIG_SLIRP int net_init_slirp(const Netdev *netdev, const char *name, NetClientState *peer, Error **errp); @@ -285,6 +285,9 @@ void net_hub_check_clients(void) case NET_CLIENT_DRIVER_NIC: has_nic = 1; break; +#ifdef CONFIG_PASST + case NET_CLIENT_DRIVER_PASST: +#endif case NET_CLIENT_DRIVER_USER: case NET_CLIENT_DRIVER_TAP: case NET_CLIENT_DRIVER_SOCKET: diff --git a/net/meson.build b/net/meson.build index bb97b4d..da6ea63 100644 --- a/net/meson.build +++ b/net/meson.build @@ -1,6 +1,7 @@ system_ss.add(files( 'announce.c', 'checksum.c', + 'dgram.c', 'dump.c', 'eth.c', 'filter-buffer.c', @@ -12,7 +13,7 @@ system_ss.add(files( 'queue.c', 'socket.c', 'stream.c', - 'dgram.c', + 'stream_data.c', 'util.c', )) @@ -33,6 +34,9 @@ system_ss.add(when: 'CONFIG_TCG', if_true: files('filter-replay.c')) if have_l2tpv3 system_ss.add(files('l2tpv3.c')) endif +if enable_passt + system_ss.add(files('passt.c')) +endif system_ss.add(when: slirp, if_true: files('slirp.c')) system_ss.add(when: vde, if_true: files('vde.c')) if have_netmap @@ -573,6 +573,15 @@ void qemu_set_vnet_hdr_len(NetClientState *nc, int len) nc->info->set_vnet_hdr_len(nc, len); } +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types) +{ + if (!nc || !nc->info->get_vnet_hash_supported_types) { + return false; + } + + return nc->info->get_vnet_hash_supported_types(nc, types); +} + int qemu_set_vnet_le(NetClientState *nc, bool is_le) { #if HOST_BIG_ENDIAN @@ -1248,6 +1257,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( const char *name, NetClientState *peer, Error **errp) = { [NET_CLIENT_DRIVER_NIC] = net_init_nic, +#ifdef CONFIG_PASST + [NET_CLIENT_DRIVER_PASST] = net_init_passt, +#endif #ifdef CONFIG_SLIRP [NET_CLIENT_DRIVER_USER] = net_init_slirp, #endif @@ -1353,6 +1365,7 @@ void show_netdevs(void) "dgram", "hubport", "tap", + "passt", #ifdef CONFIG_SLIRP "user", #endif @@ -1601,21 +1614,11 @@ void colo_notify_filters_event(int event, Error **errp) } } -void qmp_set_link(const char *name, bool up, Error **errp) +void net_client_set_link(NetClientState **ncs, int queues, bool up) { - NetClientState *ncs[MAX_QUEUE_NUM]; NetClientState *nc; - int queues, i; - - queues = qemu_find_net_clients_except(name, ncs, - NET_CLIENT_DRIVER__MAX, - MAX_QUEUE_NUM); + int i; - if (queues == 0) { - error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, - "Device '%s' not found", name); - return; - } nc = ncs[0]; for (i = 0; i < queues; i++) { @@ -1646,6 +1649,24 @@ void qmp_set_link(const char *name, bool up, Error **errp) } } +void qmp_set_link(const char *name, bool up, Error **errp) +{ + NetClientState *ncs[MAX_QUEUE_NUM]; + int queues; + + queues = qemu_find_net_clients_except(name, ncs, + NET_CLIENT_DRIVER__MAX, + MAX_QUEUE_NUM); + + if (queues == 0) { + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND, + "Device '%s' not found", name); + return; + } + + net_client_set_link(ncs, queues, up); +} + static void net_vm_change_state_handler(void *opaque, bool running, RunState state) { diff --git a/net/passt.c b/net/passt.c new file mode 100644 index 0000000..6f616ba --- /dev/null +++ b/net/passt.c @@ -0,0 +1,753 @@ +/* + * passt network backend + * + * Copyright Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include <glib/gstdio.h> +#include "qemu/error-report.h" +#include <gio/gio.h> +#include "net/net.h" +#include "clients.h" +#include "qapi/error.h" +#include "io/net-listener.h" +#include "chardev/char-fe.h" +#include "net/vhost_net.h" +#include "hw/virtio/vhost.h" +#include "hw/virtio/vhost-user.h" +#include "standard-headers/linux/virtio_net.h" +#include "stream_data.h" + +#ifdef CONFIG_VHOST_USER +static const int user_feature_bits[] = { + VIRTIO_F_NOTIFY_ON_EMPTY, + VIRTIO_F_NOTIFICATION_DATA, + VIRTIO_RING_F_INDIRECT_DESC, + VIRTIO_RING_F_EVENT_IDX, + + VIRTIO_F_ANY_LAYOUT, + VIRTIO_F_VERSION_1, + VIRTIO_NET_F_CSUM, + VIRTIO_NET_F_GUEST_CSUM, + VIRTIO_NET_F_GSO, + VIRTIO_NET_F_GUEST_TSO4, + VIRTIO_NET_F_GUEST_TSO6, + VIRTIO_NET_F_GUEST_ECN, + VIRTIO_NET_F_GUEST_UFO, + VIRTIO_NET_F_HOST_TSO4, + VIRTIO_NET_F_HOST_TSO6, + VIRTIO_NET_F_HOST_ECN, + VIRTIO_NET_F_HOST_UFO, + VIRTIO_NET_F_MRG_RXBUF, + VIRTIO_NET_F_MTU, + VIRTIO_F_IOMMU_PLATFORM, + VIRTIO_F_RING_PACKED, + VIRTIO_F_RING_RESET, + VIRTIO_F_IN_ORDER, + VIRTIO_NET_F_RSS, + VIRTIO_NET_F_RSC_EXT, + VIRTIO_NET_F_HASH_REPORT, + VIRTIO_NET_F_GUEST_USO4, + VIRTIO_NET_F_GUEST_USO6, + VIRTIO_NET_F_HOST_USO, + + /* This bit implies RARP isn't sent by QEMU out of band */ + VIRTIO_NET_F_GUEST_ANNOUNCE, + + VIRTIO_NET_F_MQ, + + VHOST_INVALID_FEATURE_BIT +}; +#endif + +typedef struct NetPasstState { + NetStreamData data; + GPtrArray *args; + gchar *pidfile; + pid_t pid; +#ifdef CONFIG_VHOST_USER + /* vhost user */ + VhostUserState *vhost_user; + VHostNetState *vhost_net; + CharBackend vhost_chr; + guint vhost_watch; + uint64_t acked_features; + bool started; +#endif +} NetPasstState; + +static int net_passt_stream_start(NetPasstState *s, Error **errp); + +static void net_passt_cleanup(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + +#ifdef CONFIG_VHOST_USER + if (s->vhost_net) { + vhost_net_cleanup(s->vhost_net); + g_free(s->vhost_net); + s->vhost_net = NULL; + } + if (s->vhost_watch) { + g_source_remove(s->vhost_watch); + s->vhost_watch = 0; + } + qemu_chr_fe_deinit(&s->vhost_chr, true); + if (s->vhost_user) { + vhost_user_cleanup(s->vhost_user); + g_free(s->vhost_user); + s->vhost_user = NULL; + } +#endif + + kill(s->pid, SIGTERM); + g_remove(s->pidfile); + g_free(s->pidfile); + g_ptr_array_free(s->args, TRUE); +} + +static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf, + size_t size) +{ + NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc); + + return net_stream_data_receive(d, buf, size); +} + +static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition, + gpointer data) +{ + if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) { + NetPasstState *s = DO_UPCAST(NetPasstState, data, data); + Error *error; + + /* we need to restart passt */ + kill(s->pid, SIGTERM); + if (net_passt_stream_start(s, &error) == -1) { + error_report_err(error); + } + + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +#ifdef CONFIG_VHOST_USER +static int passt_set_vnet_endianness(NetClientState *nc, bool enable) +{ + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + return 0; +} + +static bool passt_has_vnet_hdr(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + return s->vhost_user != NULL; +} + +static bool passt_has_ufo(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + return s->vhost_user != NULL; +} + +static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc, + Error **errp) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + const char *driver = object_class_get_name(oc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + if (s->vhost_user == NULL) { + return true; + } + + if (!g_str_has_prefix(driver, "virtio-net-")) { + error_setg(errp, "vhost-user requires frontend driver virtio-net-*"); + return false; + } + + return true; +} + +static struct vhost_net *passt_get_vhost_net(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + return s->vhost_net; +} + +static uint64_t passt_get_acked_features(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + return s->acked_features; +} + +static void passt_save_acked_features(NetClientState *nc) +{ + NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc); + + assert(nc->info->type == NET_CLIENT_DRIVER_PASST); + + if (s->vhost_net) { + uint64_t features = vhost_net_get_acked_features(s->vhost_net); + if (features) { + s->acked_features = features; + } + } +} +#endif + +static NetClientInfo net_passt_info = { + .type = NET_CLIENT_DRIVER_PASST, + .size = sizeof(NetPasstState), + .receive = net_passt_receive, + .cleanup = net_passt_cleanup, +#ifdef CONFIG_VHOST_USER + .has_vnet_hdr = passt_has_vnet_hdr, + .has_ufo = passt_has_ufo, + .set_vnet_be = passt_set_vnet_endianness, + .set_vnet_le = passt_set_vnet_endianness, + .check_peer_type = passt_check_peer_type, + .get_vhost_net = passt_get_vhost_net, +#endif +}; + +static void net_passt_client_connected(QIOTask *task, gpointer opaque) +{ + NetPasstState *s = opaque; + + if (net_stream_data_client_connected(task, &s->data) == 0) { + qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid); + } +} + +static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp) +{ + g_autoptr(GSubprocess) daemon = NULL; + g_autofree gchar *contents = NULL; + g_autoptr(GError) error = NULL; + GSubprocessLauncher *launcher; + + qemu_set_info_str(&s->data.nc, "launching passt"); + + launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE); + g_subprocess_launcher_take_fd(launcher, sock, 3); + + daemon = g_subprocess_launcher_spawnv(launcher, + (const gchar *const *)s->args->pdata, + &error); + g_object_unref(launcher); + + if (!daemon) { + error_setg(errp, "Error creating daemon: %s", error->message); + return -1; + } + + if (!g_subprocess_wait(daemon, NULL, &error)) { + error_setg(errp, "Error waiting for daemon: %s", error->message); + return -1; + } + + if (g_subprocess_get_if_exited(daemon) && + g_subprocess_get_exit_status(daemon)) { + return -1; + } + + if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) { + error_setg(errp, "Cannot read passt pid: %s", error->message); + return -1; + } + + s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10); + if (s->pid <= 0) { + error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile); + return -1; + } + + return 0; +} + +static int net_passt_stream_start(NetPasstState *s, Error **errp) +{ + QIOChannelSocket *sioc; + SocketAddress *addr; + int sv[2]; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { + error_setg_errno(errp, errno, "socketpair() failed"); + return -1; + } + + /* connect to passt */ + qemu_set_info_str(&s->data.nc, "connecting to passt"); + + /* create socket channel */ + sioc = qio_channel_socket_new(); + s->data.ioc = QIO_CHANNEL(sioc); + s->data.nc.link_down = true; + s->data.send = net_passt_send; + + addr = g_new0(SocketAddress, 1); + addr->type = SOCKET_ADDRESS_TYPE_FD; + addr->u.fd.str = g_strdup_printf("%d", sv[0]); + + qio_channel_socket_connect_async(sioc, addr, + net_passt_client_connected, s, + NULL, NULL); + + qapi_free_SocketAddress(addr); + + /* start passt */ + if (net_passt_start_daemon(s, sv[1], errp) == -1) { + close(sv[0]); + close(sv[1]); + return -1; + } + close(sv[1]); + + return 0; +} + +#ifdef CONFIG_VHOST_USER +static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond, + void *opaque) +{ + NetPasstState *s = opaque; + + qemu_chr_fe_disconnect(&s->vhost_chr); + + return G_SOURCE_CONTINUE; +} + +static void passt_vhost_user_event(void *opaque, QEMUChrEvent event); + +static void chr_closed_bh(void *opaque) +{ + NetPasstState *s = opaque; + + passt_save_acked_features(&s->data.nc); + + net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false); + + qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event, + NULL, s, NULL, true); +} + +static void passt_vhost_user_stop(NetPasstState *s) +{ + passt_save_acked_features(&s->data.nc); + vhost_net_cleanup(s->vhost_net); +} + +static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be) +{ + struct vhost_net *net = NULL; + VhostNetOptions options; + + options.backend_type = VHOST_BACKEND_TYPE_USER; + options.net_backend = &s->data.nc; + options.opaque = be; + options.busyloop_timeout = 0; + options.nvqs = 2; + options.feature_bits = user_feature_bits; + options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE; + options.get_acked_features = passt_get_acked_features; + options.save_acked_features = passt_save_acked_features; + options.is_vhost_user = true; + + net = vhost_net_init(&options); + if (!net) { + error_report("failed to init passt vhost_net"); + goto err; + } + + if (s->vhost_net) { + vhost_net_cleanup(s->vhost_net); + g_free(s->vhost_net); + } + s->vhost_net = net; + + return 0; +err: + if (net) { + vhost_net_cleanup(net); + g_free(net); + } + passt_vhost_user_stop(s); + return -1; +} + +static void passt_vhost_user_event(void *opaque, QEMUChrEvent event) +{ + NetPasstState *s = opaque; + Error *err = NULL; + + switch (event) { + case CHR_EVENT_OPENED: + if (passt_vhost_user_start(s, s->vhost_user) < 0) { + qemu_chr_fe_disconnect(&s->vhost_chr); + return; + } + s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP, + passt_vhost_user_watch, s); + net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true); + s->started = true; + break; + case CHR_EVENT_CLOSED: + if (s->vhost_watch) { + AioContext *ctx = qemu_get_current_aio_context(); + + g_source_remove(s->vhost_watch); + s->vhost_watch = 0; + qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, NULL, NULL, + NULL, NULL, false); + + aio_bh_schedule_oneshot(ctx, chr_closed_bh, s); + } + break; + case CHR_EVENT_BREAK: + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } + + if (err) { + error_report_err(err); + } +} + +static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) +{ + Chardev *chr; + int sv[2]; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { + error_setg_errno(errp, errno, "socketpair() failed"); + return -1; + } + + /* connect to passt */ + qemu_set_info_str(&s->data.nc, "connecting to passt"); + + /* create chardev */ + + chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET)); + if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) { + object_unref(OBJECT(chr)); + error_setg(errp, "Failed to make socket chardev"); + goto err; + } + + s->vhost_user = g_new0(struct VhostUserState, 1); + if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) || + !vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) { + goto err; + } + + /* start passt */ + if (net_passt_start_daemon(s, sv[1], errp) == -1) { + goto err; + } + + do { + if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) { + goto err; + } + + qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, + passt_vhost_user_event, NULL, s, NULL, + true); + } while (!s->started); + + qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid); + + close(sv[1]); + return 0; +err: + close(sv[0]); + close(sv[1]); + + return -1; +} +#else +static int net_passt_vhost_user_init(NetPasstState *s, Error **errp) +{ + error_setg(errp, "vhost-user support has not been built"); + + return -1; +} +#endif + +static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt, + gchar *pidfile, Error **errp) +{ + GPtrArray *args = g_ptr_array_new_with_free_func(g_free); + + if (passt->path) { + g_ptr_array_add(args, g_strdup(passt->path)); + } else { + g_ptr_array_add(args, g_strdup("passt")); + } + + if (passt->has_vhost_user && passt->vhost_user) { + g_ptr_array_add(args, g_strdup("--vhost-user")); + } + + /* by default, be quiet */ + if (!passt->has_quiet || passt->quiet) { + g_ptr_array_add(args, g_strdup("--quiet")); + } + + if (passt->has_mtu) { + g_ptr_array_add(args, g_strdup("--mtu")); + g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu)); + } + + if (passt->address) { + g_ptr_array_add(args, g_strdup("--address")); + g_ptr_array_add(args, g_strdup(passt->address)); + } + + if (passt->netmask) { + g_ptr_array_add(args, g_strdup("--netmask")); + g_ptr_array_add(args, g_strdup(passt->netmask)); + } + + if (passt->mac) { + g_ptr_array_add(args, g_strdup("--mac-addr")); + g_ptr_array_add(args, g_strdup(passt->mac)); + } + + if (passt->gateway) { + g_ptr_array_add(args, g_strdup("--gateway")); + g_ptr_array_add(args, g_strdup(passt->gateway)); + } + + if (passt->interface) { + g_ptr_array_add(args, g_strdup("--interface")); + g_ptr_array_add(args, g_strdup(passt->interface)); + } + + if (passt->outbound) { + g_ptr_array_add(args, g_strdup("--outbound")); + g_ptr_array_add(args, g_strdup(passt->outbound)); + } + + if (passt->outbound_if4) { + g_ptr_array_add(args, g_strdup("--outbound-if4")); + g_ptr_array_add(args, g_strdup(passt->outbound_if4)); + } + + if (passt->outbound_if6) { + g_ptr_array_add(args, g_strdup("--outbound-if6")); + g_ptr_array_add(args, g_strdup(passt->outbound_if6)); + } + + if (passt->dns) { + g_ptr_array_add(args, g_strdup("--dns")); + g_ptr_array_add(args, g_strdup(passt->dns)); + } + if (passt->fqdn) { + g_ptr_array_add(args, g_strdup("--fqdn")); + g_ptr_array_add(args, g_strdup(passt->fqdn)); + } + + if (passt->has_dhcp_dns && !passt->dhcp_dns) { + g_ptr_array_add(args, g_strdup("--no-dhcp-dns")); + } + + if (passt->has_dhcp_search && !passt->dhcp_search) { + g_ptr_array_add(args, g_strdup("--no-dhcp-search")); + } + + if (passt->map_host_loopback) { + g_ptr_array_add(args, g_strdup("--map-host-loopback")); + g_ptr_array_add(args, g_strdup(passt->map_host_loopback)); + } + + if (passt->map_guest_addr) { + g_ptr_array_add(args, g_strdup("--map-guest-addr")); + g_ptr_array_add(args, g_strdup(passt->map_guest_addr)); + } + + if (passt->dns_forward) { + g_ptr_array_add(args, g_strdup("--dns-forward")); + g_ptr_array_add(args, g_strdup(passt->dns_forward)); + } + + if (passt->dns_host) { + g_ptr_array_add(args, g_strdup("--dns-host")); + g_ptr_array_add(args, g_strdup(passt->dns_host)); + } + + if (passt->has_tcp && !passt->tcp) { + g_ptr_array_add(args, g_strdup("--no-tcp")); + } + + if (passt->has_udp && !passt->udp) { + g_ptr_array_add(args, g_strdup("--no-udp")); + } + + if (passt->has_icmp && !passt->icmp) { + g_ptr_array_add(args, g_strdup("--no-icmp")); + } + + if (passt->has_dhcp && !passt->dhcp) { + g_ptr_array_add(args, g_strdup("--no-dhcp")); + } + + if (passt->has_ndp && !passt->ndp) { + g_ptr_array_add(args, g_strdup("--no-ndp")); + } + if (passt->has_dhcpv6 && !passt->dhcpv6) { + g_ptr_array_add(args, g_strdup("--no-dhcpv6")); + } + + if (passt->has_ra && !passt->ra) { + g_ptr_array_add(args, g_strdup("--no-ra")); + } + + if (passt->has_freebind && passt->freebind) { + g_ptr_array_add(args, g_strdup("--freebind")); + } + + if (passt->has_ipv4 && !passt->ipv4) { + g_ptr_array_add(args, g_strdup("--ipv6-only")); + } + + if (passt->has_ipv6 && !passt->ipv6) { + g_ptr_array_add(args, g_strdup("--ipv4-only")); + } + + if (passt->has_search && passt->search) { + const StringList *list = passt->search; + GString *domains = g_string_new(list->value->str); + + list = list->next; + while (list) { + g_string_append(domains, " "); + g_string_append(domains, list->value->str); + list = list->next; + } + + g_ptr_array_add(args, g_strdup("--search")); + g_ptr_array_add(args, g_string_free(domains, FALSE)); + } + + if (passt->has_tcp_ports && passt->tcp_ports) { + const StringList *list = passt->tcp_ports; + GString *tcp_ports = g_string_new(list->value->str); + + list = list->next; + while (list) { + g_string_append(tcp_ports, ","); + g_string_append(tcp_ports, list->value->str); + list = list->next; + } + + g_ptr_array_add(args, g_strdup("--tcp-ports")); + g_ptr_array_add(args, g_string_free(tcp_ports, FALSE)); + } + + if (passt->has_udp_ports && passt->udp_ports) { + const StringList *list = passt->udp_ports; + GString *udp_ports = g_string_new(list->value->str); + + list = list->next; + while (list) { + g_string_append(udp_ports, ","); + g_string_append(udp_ports, list->value->str); + list = list->next; + } + + g_ptr_array_add(args, g_strdup("--udp-ports")); + g_ptr_array_add(args, g_string_free(udp_ports, FALSE)); + } + + if (passt->has_param && passt->param) { + const StringList *list = passt->param; + + while (list) { + g_ptr_array_add(args, g_strdup(list->value->str)); + list = list->next; + } + } + + /* provide a pid file to be able to kil passt on exit */ + g_ptr_array_add(args, g_strdup("--pid")); + g_ptr_array_add(args, g_strdup(pidfile)); + + /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */ + g_ptr_array_add(args, g_strdup("--fd")); + g_ptr_array_add(args, g_strdup("3")); + + g_ptr_array_add(args, NULL); + + return args; +} + +int net_init_passt(const Netdev *netdev, const char *name, + NetClientState *peer, Error **errp) +{ + g_autoptr(GError) error = NULL; + NetClientState *nc; + NetPasstState *s; + GPtrArray *args; + gchar *pidfile; + int pidfd; + + assert(netdev->type == NET_CLIENT_DRIVER_PASST); + + pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error); + if (pidfd == -1) { + error_setg(errp, "Failed to create temporary file: %s", error->message); + return -1; + } + close(pidfd); + + args = net_passt_decode_args(&netdev->u.passt, pidfile, errp); + if (args == NULL) { + g_free(pidfile); + return -1; + } + + nc = qemu_new_net_client(&net_passt_info, peer, "passt", name); + s = DO_UPCAST(NetPasstState, data.nc, nc); + + s->args = args; + s->pidfile = pidfile; + + if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) { + if (net_passt_vhost_user_init(s, errp) == -1) { + qemu_del_net_client(nc); + return -1; + } + + return 0; + } + + if (net_passt_stream_start(s, errp) == -1) { + qemu_del_net_client(nc); + return -1; + } + + return 0; +} diff --git a/net/stream.c b/net/stream.c index 6152d2a..d893f02 100644 --- a/net/stream.c +++ b/net/stream.c @@ -27,173 +27,50 @@ #include "net/net.h" #include "clients.h" -#include "monitor/monitor.h" #include "qapi/error.h" -#include "qemu/error-report.h" -#include "qemu/option.h" -#include "qemu/sockets.h" -#include "qemu/iov.h" -#include "qemu/main-loop.h" -#include "qemu/cutils.h" -#include "io/channel.h" -#include "io/channel-socket.h" #include "io/net-listener.h" #include "qapi/qapi-events-net.h" #include "qapi/qapi-visit-sockets.h" #include "qapi/clone-visitor.h" +#include "stream_data.h" + typedef struct NetStreamState { - NetClientState nc; - QIOChannel *listen_ioc; - QIONetListener *listener; - QIOChannel *ioc; - guint ioc_read_tag; - guint ioc_write_tag; - SocketReadState rs; - unsigned int send_index; /* number of bytes sent*/ + NetStreamData data; uint32_t reconnect_ms; guint timer_tag; SocketAddress *addr; } NetStreamState; -static void net_stream_listen(QIONetListener *listener, - QIOChannelSocket *cioc, - void *opaque); static void net_stream_arm_reconnect(NetStreamState *s); -static gboolean net_stream_writable(QIOChannel *ioc, - GIOCondition condition, - gpointer data) -{ - NetStreamState *s = data; - - s->ioc_write_tag = 0; - - qemu_flush_queued_packets(&s->nc); - - return G_SOURCE_REMOVE; -} - static ssize_t net_stream_receive(NetClientState *nc, const uint8_t *buf, size_t size) { - NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc); - uint32_t len = htonl(size); - struct iovec iov[] = { - { - .iov_base = &len, - .iov_len = sizeof(len), - }, { - .iov_base = (void *)buf, - .iov_len = size, - }, - }; - struct iovec local_iov[2]; - unsigned int nlocal_iov; - size_t remaining; - ssize_t ret; - - remaining = iov_size(iov, 2) - s->send_index; - nlocal_iov = iov_copy(local_iov, 2, iov, 2, s->send_index, remaining); - ret = qio_channel_writev(s->ioc, local_iov, nlocal_iov, NULL); - if (ret == QIO_CHANNEL_ERR_BLOCK) { - ret = 0; /* handled further down */ - } - if (ret == -1) { - s->send_index = 0; - return -errno; - } - if (ret < (ssize_t)remaining) { - s->send_index += ret; - s->ioc_write_tag = qio_channel_add_watch(s->ioc, G_IO_OUT, - net_stream_writable, s, NULL); - return 0; - } - s->send_index = 0; - return size; -} - -static gboolean net_stream_send(QIOChannel *ioc, - GIOCondition condition, - gpointer data); - -static void net_stream_send_completed(NetClientState *nc, ssize_t len) -{ - NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc); - - if (!s->ioc_read_tag) { - s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, - net_stream_send, s, NULL); - } -} + NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc); -static void net_stream_rs_finalize(SocketReadState *rs) -{ - NetStreamState *s = container_of(rs, NetStreamState, rs); - - if (qemu_send_packet_async(&s->nc, rs->buf, - rs->packet_len, - net_stream_send_completed) == 0) { - if (s->ioc_read_tag) { - g_source_remove(s->ioc_read_tag); - s->ioc_read_tag = 0; - } - } + return net_stream_data_receive(d, buf, size); } static gboolean net_stream_send(QIOChannel *ioc, GIOCondition condition, gpointer data) { - NetStreamState *s = data; - int size; - int ret; - QEMU_UNINITIALIZED char buf1[NET_BUFSIZE]; - const char *buf; - - size = qio_channel_read(s->ioc, buf1, sizeof(buf1), NULL); - if (size < 0) { - if (errno != EWOULDBLOCK) { - goto eoc; - } - } else if (size == 0) { - /* end of connection */ - eoc: - s->ioc_read_tag = 0; - if (s->ioc_write_tag) { - g_source_remove(s->ioc_write_tag); - s->ioc_write_tag = 0; - } - if (s->listener) { - qemu_set_info_str(&s->nc, "listening"); - qio_net_listener_set_client_func(s->listener, net_stream_listen, - s, NULL); - } - object_unref(OBJECT(s->ioc)); - s->ioc = NULL; - - net_socket_rs_init(&s->rs, net_stream_rs_finalize, false); - s->nc.link_down = true; + if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) { + NetStreamState *s = DO_UPCAST(NetStreamState, data, data); - qapi_event_send_netdev_stream_disconnected(s->nc.name); + qapi_event_send_netdev_stream_disconnected(s->data.nc.name); net_stream_arm_reconnect(s); return G_SOURCE_REMOVE; } - buf = buf1; - - ret = net_fill_rstate(&s->rs, (const uint8_t *)buf, size); - - if (ret == -1) { - goto eoc; - } return G_SOURCE_CONTINUE; } static void net_stream_cleanup(NetClientState *nc) { - NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc); + NetStreamState *s = DO_UPCAST(NetStreamState, data.nc, nc); if (s->timer_tag) { g_source_remove(s->timer_tag); s->timer_tag = 0; @@ -202,28 +79,28 @@ static void net_stream_cleanup(NetClientState *nc) qapi_free_SocketAddress(s->addr); s->addr = NULL; } - if (s->ioc) { - if (QIO_CHANNEL_SOCKET(s->ioc)->fd != -1) { - if (s->ioc_read_tag) { - g_source_remove(s->ioc_read_tag); - s->ioc_read_tag = 0; + if (s->data.ioc) { + if (QIO_CHANNEL_SOCKET(s->data.ioc)->fd != -1) { + if (s->data.ioc_read_tag) { + g_source_remove(s->data.ioc_read_tag); + s->data.ioc_read_tag = 0; } - if (s->ioc_write_tag) { - g_source_remove(s->ioc_write_tag); - s->ioc_write_tag = 0; + if (s->data.ioc_write_tag) { + g_source_remove(s->data.ioc_write_tag); + s->data.ioc_write_tag = 0; } } - object_unref(OBJECT(s->ioc)); - s->ioc = NULL; + object_unref(OBJECT(s->data.ioc)); + s->data.ioc = NULL; } - if (s->listen_ioc) { - if (s->listener) { - qio_net_listener_disconnect(s->listener); - object_unref(OBJECT(s->listener)); - s->listener = NULL; + if (s->data.listen_ioc) { + if (s->data.listener) { + qio_net_listener_disconnect(s->data.listener); + object_unref(OBJECT(s->data.listener)); + s->data.listener = NULL; } - object_unref(OBJECT(s->listen_ioc)); - s->listen_ioc = NULL; + object_unref(OBJECT(s->data.listen_ioc)); + s->data.listen_ioc = NULL; } } @@ -235,23 +112,13 @@ static NetClientInfo net_stream_info = { }; static void net_stream_listen(QIONetListener *listener, - QIOChannelSocket *cioc, - void *opaque) + QIOChannelSocket *cioc, gpointer data) { - NetStreamState *s = opaque; + NetStreamData *d = data; SocketAddress *addr; char *uri; - object_ref(OBJECT(cioc)); - - qio_net_listener_set_client_func(s->listener, NULL, s, NULL); - - s->ioc = QIO_CHANNEL(cioc); - qio_channel_set_name(s->ioc, "stream-server"); - s->nc.link_down = false; - - s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send, - s, NULL); + net_stream_data_listen(listener, cioc, data); if (cioc->localAddr.ss_family == AF_UNIX) { addr = qio_channel_socket_get_local_address(cioc, NULL); @@ -260,22 +127,22 @@ static void net_stream_listen(QIONetListener *listener, } g_assert(addr != NULL); uri = socket_uri(addr); - qemu_set_info_str(&s->nc, "%s", uri); + qemu_set_info_str(&d->nc, "%s", uri); g_free(uri); - qapi_event_send_netdev_stream_connected(s->nc.name, addr); + qapi_event_send_netdev_stream_connected(d->nc.name, addr); qapi_free_SocketAddress(addr); } static void net_stream_server_listening(QIOTask *task, gpointer opaque) { - NetStreamState *s = opaque; - QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(s->listen_ioc); + NetStreamData *d = opaque; + QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(d->listen_ioc); SocketAddress *addr; int ret; Error *err = NULL; if (qio_task_propagate_error(task, &err)) { - qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err)); + qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err)); error_free(err); return; } @@ -284,20 +151,21 @@ static void net_stream_server_listening(QIOTask *task, gpointer opaque) g_assert(addr != NULL); ret = qemu_socket_try_set_nonblock(listen_sioc->fd); if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) { - qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)", + qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)", addr->u.fd.str, -ret); return; } g_assert(ret == 0); qapi_free_SocketAddress(addr); - s->nc.link_down = true; - s->listener = qio_net_listener_new(); + d->nc.link_down = true; + d->listener = qio_net_listener_new(); - qemu_set_info_str(&s->nc, "listening"); - net_socket_rs_init(&s->rs, net_stream_rs_finalize, false); - qio_net_listener_set_client_func(s->listener, net_stream_listen, s, NULL); - qio_net_listener_add(s->listener, listen_sioc); + qemu_set_info_str(&d->nc, "listening"); + net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false); + qio_net_listener_set_client_func(d->listener, d->listen, d, + NULL); + qio_net_listener_add(d->listener, listen_sioc); } static int net_stream_server_init(NetClientState *peer, @@ -307,16 +175,18 @@ static int net_stream_server_init(NetClientState *peer, Error **errp) { NetClientState *nc; - NetStreamState *s; + NetStreamData *d; QIOChannelSocket *listen_sioc = qio_channel_socket_new(); nc = qemu_new_net_client(&net_stream_info, peer, model, name); - s = DO_UPCAST(NetStreamState, nc, nc); - qemu_set_info_str(&s->nc, "initializing"); + d = DO_UPCAST(NetStreamData, nc, nc); + d->send = net_stream_send; + d->listen = net_stream_listen; + qemu_set_info_str(&d->nc, "initializing"); - s->listen_ioc = QIO_CHANNEL(listen_sioc); + d->listen_ioc = QIO_CHANNEL(listen_sioc); qio_channel_socket_listen_async(listen_sioc, addr, 0, - net_stream_server_listening, s, + net_stream_server_listening, d, NULL, NULL); return 0; @@ -325,49 +195,23 @@ static int net_stream_server_init(NetClientState *peer, static void net_stream_client_connected(QIOTask *task, gpointer opaque) { NetStreamState *s = opaque; - QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc); + NetStreamData *d = &s->data; + QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc); SocketAddress *addr; gchar *uri; - int ret; - Error *err = NULL; - if (qio_task_propagate_error(task, &err)) { - qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err)); - error_free(err); - goto error; + if (net_stream_data_client_connected(task, d) == -1) { + net_stream_arm_reconnect(s); + return; } addr = qio_channel_socket_get_remote_address(sioc, NULL); g_assert(addr != NULL); uri = socket_uri(addr); - qemu_set_info_str(&s->nc, "%s", uri); + qemu_set_info_str(&d->nc, "%s", uri); g_free(uri); - - ret = qemu_socket_try_set_nonblock(sioc->fd); - if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) { - qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)", - addr->u.fd.str, -ret); - qapi_free_SocketAddress(addr); - goto error; - } - g_assert(ret == 0); - - net_socket_rs_init(&s->rs, net_stream_rs_finalize, false); - - /* Disable Nagle algorithm on TCP sockets to reduce latency */ - qio_channel_set_delay(s->ioc, false); - - s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send, - s, NULL); - s->nc.link_down = false; - qapi_event_send_netdev_stream_connected(s->nc.name, addr); + qapi_event_send_netdev_stream_connected(d->nc.name, addr); qapi_free_SocketAddress(addr); - - return; -error: - object_unref(OBJECT(s->ioc)); - s->ioc = NULL; - net_stream_arm_reconnect(s); } static gboolean net_stream_reconnect(gpointer data) @@ -378,7 +222,7 @@ static gboolean net_stream_reconnect(gpointer data) s->timer_tag = 0; sioc = qio_channel_socket_new(); - s->ioc = QIO_CHANNEL(sioc); + s->data.ioc = QIO_CHANNEL(sioc); qio_channel_socket_connect_async(sioc, s->addr, net_stream_client_connected, s, NULL, NULL); @@ -388,7 +232,7 @@ static gboolean net_stream_reconnect(gpointer data) static void net_stream_arm_reconnect(NetStreamState *s) { if (s->reconnect_ms && s->timer_tag == 0) { - qemu_set_info_str(&s->nc, "connecting"); + qemu_set_info_str(&s->data.nc, "connecting"); s->timer_tag = g_timeout_add(s->reconnect_ms, net_stream_reconnect, s); } } @@ -405,11 +249,13 @@ static int net_stream_client_init(NetClientState *peer, QIOChannelSocket *sioc = qio_channel_socket_new(); nc = qemu_new_net_client(&net_stream_info, peer, model, name); - s = DO_UPCAST(NetStreamState, nc, nc); - qemu_set_info_str(&s->nc, "connecting"); + s = DO_UPCAST(NetStreamState, data.nc, nc); + qemu_set_info_str(&s->data.nc, "connecting"); - s->ioc = QIO_CHANNEL(sioc); - s->nc.link_down = true; + s->data.ioc = QIO_CHANNEL(sioc); + s->data.nc.link_down = true; + s->data.send = net_stream_send; + s->data.listen = net_stream_listen; s->reconnect_ms = reconnect_ms; if (reconnect_ms) { diff --git a/net/stream_data.c b/net/stream_data.c new file mode 100644 index 0000000..5af27e0 --- /dev/null +++ b/net/stream_data.c @@ -0,0 +1,193 @@ +/* + * net stream generic functions + * + * Copyright Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/iov.h" +#include "qapi/error.h" +#include "net/net.h" +#include "io/channel.h" +#include "io/net-listener.h" + +#include "stream_data.h" + +static gboolean net_stream_data_writable(QIOChannel *ioc, + GIOCondition condition, gpointer data) +{ + NetStreamData *d = data; + + d->ioc_write_tag = 0; + + qemu_flush_queued_packets(&d->nc); + + return G_SOURCE_REMOVE; +} + +ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf, + size_t size) +{ + uint32_t len = htonl(size); + struct iovec iov[] = { + { + .iov_base = &len, + .iov_len = sizeof(len), + }, { + .iov_base = (void *)buf, + .iov_len = size, + }, + }; + struct iovec local_iov[2]; + unsigned int nlocal_iov; + size_t remaining; + ssize_t ret; + + remaining = iov_size(iov, 2) - d->send_index; + nlocal_iov = iov_copy(local_iov, 2, iov, 2, d->send_index, remaining); + ret = qio_channel_writev(d->ioc, local_iov, nlocal_iov, NULL); + if (ret == QIO_CHANNEL_ERR_BLOCK) { + ret = 0; /* handled further down */ + } + if (ret == -1) { + d->send_index = 0; + return -errno; + } + if (ret < (ssize_t)remaining) { + d->send_index += ret; + d->ioc_write_tag = qio_channel_add_watch(d->ioc, G_IO_OUT, + net_stream_data_writable, d, + NULL); + return 0; + } + d->send_index = 0; + return size; +} + +static void net_stream_data_send_completed(NetClientState *nc, ssize_t len) +{ + NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc); + + if (!d->ioc_read_tag) { + d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, + NULL); + } +} + +void net_stream_data_rs_finalize(SocketReadState *rs) +{ + NetStreamData *d = container_of(rs, NetStreamData, rs); + + if (qemu_send_packet_async(&d->nc, rs->buf, + rs->packet_len, + net_stream_data_send_completed) == 0) { + if (d->ioc_read_tag) { + g_source_remove(d->ioc_read_tag); + d->ioc_read_tag = 0; + } + } +} + +gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition, + NetStreamData *d) +{ + int size; + int ret; + QEMU_UNINITIALIZED char buf1[NET_BUFSIZE]; + const char *buf; + + size = qio_channel_read(d->ioc, buf1, sizeof(buf1), NULL); + if (size < 0) { + if (errno != EWOULDBLOCK) { + goto eoc; + } + } else if (size == 0) { + /* end of connection */ + eoc: + d->ioc_read_tag = 0; + if (d->ioc_write_tag) { + g_source_remove(d->ioc_write_tag); + d->ioc_write_tag = 0; + } + if (d->listener) { + qemu_set_info_str(&d->nc, "listening"); + qio_net_listener_set_client_func(d->listener, + d->listen, d, NULL); + } + object_unref(OBJECT(d->ioc)); + d->ioc = NULL; + + net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false); + d->nc.link_down = true; + + return G_SOURCE_REMOVE; + } + buf = buf1; + + ret = net_fill_rstate(&d->rs, (const uint8_t *)buf, size); + + if (ret == -1) { + goto eoc; + } + + return G_SOURCE_CONTINUE; +} + +void net_stream_data_listen(QIONetListener *listener, + QIOChannelSocket *cioc, + NetStreamData *d) +{ + object_ref(OBJECT(cioc)); + + qio_net_listener_set_client_func(d->listener, NULL, d, NULL); + + d->ioc = QIO_CHANNEL(cioc); + qio_channel_set_name(d->ioc, "stream-server"); + d->nc.link_down = false; + + d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL); +} + +int net_stream_data_client_connected(QIOTask *task, NetStreamData *d) +{ + QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc); + SocketAddress *addr; + int ret; + Error *err = NULL; + + if (qio_task_propagate_error(task, &err)) { + qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err)); + error_free(err); + goto error; + } + + addr = qio_channel_socket_get_remote_address(sioc, NULL); + g_assert(addr != NULL); + + ret = qemu_socket_try_set_nonblock(sioc->fd); + if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) { + qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)", + addr->u.fd.str, -ret); + qapi_free_SocketAddress(addr); + goto error; + } + g_assert(ret == 0); + qapi_free_SocketAddress(addr); + + net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false); + + /* Disable Nagle algorithm on TCP sockets to reduce latency */ + qio_channel_set_delay(d->ioc, false); + + d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL); + d->nc.link_down = false; + + return 0; +error: + object_unref(OBJECT(d->ioc)); + d->ioc = NULL; + + return -1; +} diff --git a/net/stream_data.h b/net/stream_data.h new file mode 100644 index 0000000..b868625 --- /dev/null +++ b/net/stream_data.h @@ -0,0 +1,31 @@ +/* + * net stream generic functions + * + * Copyright Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +typedef struct NetStreamData { + NetClientState nc; + QIOChannel *ioc; + guint ioc_read_tag; + guint ioc_write_tag; + SocketReadState rs; + unsigned int send_index; /* number of bytes sent*/ + QIOChannelFunc send; + /* server data */ + QIOChannel *listen_ioc; + QIONetListener *listener; + QIONetListenerClientFunc listen; +} NetStreamData; + +ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf, + size_t size); +void net_stream_data_rs_finalize(SocketReadState *rs); +gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition, + NetStreamData *d); +int net_stream_data_client_connected(QIOTask *task, NetStreamData *d); +void net_stream_data_listen(QIONetListener *listener, + QIOChannelSocket *cioc, + NetStreamData *d); diff --git a/net/tap-win32.c b/net/tap-win32.c index 671dee9..38baf90 100644 --- a/net/tap-win32.c +++ b/net/tap-win32.c @@ -704,11 +704,6 @@ static void tap_win32_send(void *opaque) } } -struct vhost_net *tap_get_vhost_net(NetClientState *nc) -{ - return NULL; -} - static NetClientInfo net_tap_win32_info = { .type = NET_CLIENT_DRIVER_TAP, .size = sizeof(TAPState), @@ -42,11 +42,29 @@ #include "qemu/error-report.h" #include "qemu/main-loop.h" #include "qemu/sockets.h" +#include "hw/virtio/vhost.h" #include "net/tap.h" #include "net/vhost_net.h" +static const int kernel_feature_bits[] = { + VIRTIO_F_NOTIFY_ON_EMPTY, + VIRTIO_RING_F_INDIRECT_DESC, + VIRTIO_RING_F_EVENT_IDX, + VIRTIO_NET_F_MRG_RXBUF, + VIRTIO_F_VERSION_1, + VIRTIO_NET_F_MTU, + VIRTIO_F_IOMMU_PLATFORM, + VIRTIO_F_RING_PACKED, + VIRTIO_F_RING_RESET, + VIRTIO_F_IN_ORDER, + VIRTIO_F_NOTIFICATION_DATA, + VIRTIO_NET_F_RSC_EXT, + VIRTIO_NET_F_HASH_REPORT, + VHOST_INVALID_FEATURE_BIT +}; + typedef struct TAPState { NetClientState nc; int fd; @@ -329,6 +347,18 @@ int tap_get_fd(NetClientState *nc) return s->fd; } +/* + * tap_get_vhost_net() can return NULL if a tap net-device backend is + * created with 'vhost=off' option, 'vhostforce=off' or no vhost or + * vhostforce or vhostfd options at all. Please see net_init_tap_one(). + */ +static VHostNetState *tap_get_vhost_net(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + assert(nc->info->type == NET_CLIENT_DRIVER_TAP); + return s->vhost_net; +} + /* fd support */ static NetClientInfo net_tap_info = { @@ -347,6 +377,7 @@ static NetClientInfo net_tap_info = { .set_vnet_le = tap_set_vnet_le, .set_vnet_be = tap_set_vnet_be, .set_steering_ebpf = tap_set_steering_ebpf, + .get_vhost_net = tap_get_vhost_net, }; static TAPState *net_tap_fd_init(NetClientState *peer, @@ -712,6 +743,11 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, } options.opaque = (void *)(uintptr_t)vhostfd; options.nvqs = 2; + options.feature_bits = kernel_feature_bits; + options.get_acked_features = NULL; + options.save_acked_features = NULL; + options.max_tx_queue_size = 0; + options.is_vhost_user = false; s->vhost_net = vhost_net_init(&options); if (!s->vhost_net) { @@ -980,13 +1016,6 @@ free_fail: return 0; } -VHostNetState *tap_get_vhost_net(NetClientState *nc) -{ - TAPState *s = DO_UPCAST(TAPState, nc, nc); - assert(nc->info->type == NET_CLIENT_DRIVER_TAP); - return s->vhost_net; -} - int tap_enable(NetClientState *nc) { TAPState *s = DO_UPCAST(TAPState, nc, nc); diff --git a/net/vhost-user-stub.c b/net/vhost-user-stub.c index 52ab4e1..283dee8 100644 --- a/net/vhost-user-stub.c +++ b/net/vhost-user-stub.c @@ -11,7 +11,6 @@ #include "qemu/osdep.h" #include "clients.h" #include "net/vhost_net.h" -#include "net/vhost-user.h" #include "qemu/error-report.h" #include "qapi/error.h" diff --git a/net/vhost-user.c b/net/vhost-user.c index 0b235e5..1c3b8b3 100644 --- a/net/vhost-user.c +++ b/net/vhost-user.c @@ -11,8 +11,9 @@ #include "qemu/osdep.h" #include "clients.h" #include "net/vhost_net.h" -#include "net/vhost-user.h" +#include "hw/virtio/vhost.h" #include "hw/virtio/vhost-user.h" +#include "standard-headers/linux/virtio_net.h" #include "chardev/char-fe.h" #include "qapi/error.h" #include "qapi/qapi-commands-net.h" @@ -22,6 +23,46 @@ #include "qemu/option.h" #include "trace.h" +static const int user_feature_bits[] = { + VIRTIO_F_NOTIFY_ON_EMPTY, + VIRTIO_F_NOTIFICATION_DATA, + VIRTIO_RING_F_INDIRECT_DESC, + VIRTIO_RING_F_EVENT_IDX, + + VIRTIO_F_ANY_LAYOUT, + VIRTIO_F_VERSION_1, + VIRTIO_NET_F_CSUM, + VIRTIO_NET_F_GUEST_CSUM, + VIRTIO_NET_F_GSO, + VIRTIO_NET_F_GUEST_TSO4, + VIRTIO_NET_F_GUEST_TSO6, + VIRTIO_NET_F_GUEST_ECN, + VIRTIO_NET_F_GUEST_UFO, + VIRTIO_NET_F_HOST_TSO4, + VIRTIO_NET_F_HOST_TSO6, + VIRTIO_NET_F_HOST_ECN, + VIRTIO_NET_F_HOST_UFO, + VIRTIO_NET_F_MRG_RXBUF, + VIRTIO_NET_F_MTU, + VIRTIO_F_IOMMU_PLATFORM, + VIRTIO_F_RING_PACKED, + VIRTIO_F_RING_RESET, + VIRTIO_F_IN_ORDER, + VIRTIO_NET_F_RSS, + VIRTIO_NET_F_RSC_EXT, + VIRTIO_NET_F_HASH_REPORT, + VIRTIO_NET_F_GUEST_USO4, + VIRTIO_NET_F_GUEST_USO6, + VIRTIO_NET_F_HOST_USO, + + /* This bit implies RARP isn't sent by QEMU out of band */ + VIRTIO_NET_F_GUEST_ANNOUNCE, + + VIRTIO_NET_F_MQ, + + VHOST_INVALID_FEATURE_BIT +}; + typedef struct NetVhostUserState { NetClientState nc; CharBackend chr; /* only queue index 0 */ @@ -32,21 +73,21 @@ typedef struct NetVhostUserState { bool started; } NetVhostUserState; -VHostNetState *vhost_user_get_vhost_net(NetClientState *nc) +static struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc) { NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc); assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER); return s->vhost_net; } -uint64_t vhost_user_get_acked_features(NetClientState *nc) +static uint64_t vhost_user_get_acked_features(NetClientState *nc) { NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc); assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER); return s->acked_features; } -void vhost_user_save_acked_features(NetClientState *nc) +static void vhost_user_save_acked_features(NetClientState *nc) { NetVhostUserState *s; @@ -96,6 +137,12 @@ static int vhost_user_start(int queues, NetClientState *ncs[], options.opaque = be; options.busyloop_timeout = 0; options.nvqs = 2; + options.feature_bits = user_feature_bits; + options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE; + options.get_acked_features = vhost_user_get_acked_features; + options.save_acked_features = vhost_user_save_acked_features; + options.is_vhost_user = true; + net = vhost_net_init(&options); if (!net) { error_report("failed to init vhost_net for queue %d", i); @@ -231,6 +278,7 @@ static NetClientInfo net_vhost_user_info = { .set_vnet_be = vhost_user_set_vnet_endianness, .set_vnet_le = vhost_user_set_vnet_endianness, .check_peer_type = vhost_user_check_peer_type, + .get_vhost_net = vhost_user_get_vhost_net, }; static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond, @@ -264,7 +312,7 @@ static void chr_closed_bh(void *opaque) vhost_user_save_acked_features(ncs[i]); } - qmp_set_link(name, false, &err); + net_client_set_link(ncs, queues, false); qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event, NULL, opaque, NULL, true); @@ -300,7 +348,7 @@ static void net_vhost_user_event(void *opaque, QEMUChrEvent event) } s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP, net_vhost_user_watch, s); - qmp_set_link(name, true, &err); + net_client_set_link(ncs, queues, true); s->started = true; qapi_event_send_netdev_vhost_user_connected(name, chr->label); break; diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index 58d7389..6a30a44 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -55,7 +55,7 @@ typedef struct VhostVDPAState { * with the exception of VHOST_INVALID_FEATURE_BIT, * which should always be the last entry. */ -const int vdpa_feature_bits[] = { +static const int vdpa_feature_bits[] = { VIRTIO_F_ANY_LAYOUT, VIRTIO_F_IOMMU_PLATFORM, VIRTIO_F_NOTIFY_ON_EMPTY, @@ -132,7 +132,7 @@ static const uint64_t vdpa_svq_device_features = #define VHOST_VDPA_NET_CVQ_ASID 1 -VHostNetState *vhost_vdpa_get_vhost_net(NetClientState *nc) +static struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc) { VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc); assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -201,6 +201,11 @@ static int vhost_vdpa_add(NetClientState *ncs, void *be, options.opaque = be; options.busyloop_timeout = 0; options.nvqs = nvqs; + options.feature_bits = vdpa_feature_bits; + options.get_acked_features = NULL; + options.save_acked_features = NULL; + options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE; + options.is_vhost_user = false; net = vhost_net_init(&options); if (!net) { @@ -239,15 +244,35 @@ static void vhost_vdpa_cleanup(NetClientState *nc) g_free(s->vhost_vdpa.shared); } -/** Dummy SetSteeringEBPF to support RSS for vhost-vdpa backend */ -static bool vhost_vdpa_set_steering_ebpf(NetClientState *nc, int prog_fd) +static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) { + assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); + return true; } -static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) +static bool vhost_vdpa_get_vnet_hash_supported_types(NetClientState *nc, + uint32_t *types) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); + VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc); + uint64_t features = s->vhost_vdpa.dev->features; + int fd = s->vhost_vdpa.shared->device_fd; + struct { + struct vhost_vdpa_config hdr; + uint32_t supported_hash_types; + } config; + + if (!virtio_has_feature(features, VIRTIO_NET_F_HASH_REPORT) && + !virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + return false; + } + + config.hdr.off = offsetof(struct virtio_net_config, supported_hash_types); + config.hdr.len = sizeof(config.supported_hash_types); + + assert(!ioctl(fd, VHOST_VDPA_GET_CONFIG, &config)); + *types = le32_to_cpu(config.supported_hash_types); return true; } @@ -428,10 +453,11 @@ static NetClientInfo net_vhost_vdpa_info = { .stop = vhost_vdpa_net_client_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .set_vnet_le = vhost_vdpa_set_vnet_le, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, + .get_vhost_net = vhost_vdpa_get_vhost_net, }; static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index, @@ -838,13 +864,13 @@ static int vhost_vdpa_net_load_rss(VhostVDPAState *s, const VirtIONet *n, * configuration only at live migration. */ if (!n->rss_data.enabled || - n->rss_data.hash_types == VIRTIO_NET_HASH_REPORT_NONE) { + n->rss_data.runtime_hash_types == VIRTIO_NET_HASH_REPORT_NONE) { return 0; } table = g_malloc_n(n->rss_data.indirections_len, sizeof(n->rss_data.indirections_table[0])); - cfg.hash_types = cpu_to_le32(n->rss_data.hash_types); + cfg.hash_types = cpu_to_le32(n->rss_data.runtime_hash_types); if (do_rss) { /* @@ -1284,9 +1310,10 @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .stop = vhost_vdpa_net_cvq_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, + .get_vhost_net = vhost_vdpa_get_vhost_net, }; /* diff --git a/pc-bios/hppa-firmware.img b/pc-bios/hppa-firmware.img Binary files differindex d5f6f2f..d5f6f2f 100755..100644 --- a/pc-bios/hppa-firmware.img +++ b/pc-bios/hppa-firmware.img diff --git a/pc-bios/hppa-firmware64.img b/pc-bios/hppa-firmware64.img Binary files differindex 577b0a1..577b0a1 100755..100644 --- a/pc-bios/hppa-firmware64.img +++ b/pc-bios/hppa-firmware64.img diff --git a/python/tests/qapi-isort.sh b/python/tests/qapi-isort.sh index 78dd947..067c16d 100755 --- a/python/tests/qapi-isort.sh +++ b/python/tests/qapi-isort.sh @@ -3,6 +3,6 @@ python3 -m isort --sp . -c ../scripts/qapi/ # Force isort to recognize "compat" as a local module and not third-party -python3 -m isort --sp . -c -p compat -p qapidoc_legacy \ +python3 -m isort --sp . -c -p compat \ ../docs/sphinx/qapi_domain.py \ ../docs/sphinx/qapidoc.py diff --git a/qapi/accelerator.json b/qapi/accelerator.json new file mode 100644 index 0000000..28d5ff4 --- /dev/null +++ b/qapi/accelerator.json @@ -0,0 +1,54 @@ +# -*- Mode: Python -*- +# vim: filetype=python +# +# SPDX-License-Identifier: GPL-2.0-or-later + +## +# = Accelerators +## + +{ 'include': 'common.json' } + +## +# @KvmInfo: +# +# Information about support for KVM acceleration +# +# @enabled: true if KVM acceleration is active +# +# @present: true if KVM acceleration is built into this executable +# +# Since: 0.14 +## +{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } + +## +# @query-kvm: +# +# Return information about KVM acceleration +# +# Since: 0.14 +# +# .. qmp-example:: +# +# -> { "execute": "query-kvm" } +# <- { "return": { "enabled": true, "present": true } } +## +{ 'command': 'query-kvm', 'returns': 'KvmInfo' } + +## +# @x-accel-stats: +# +# Query accelerator statistics +# +# Features: +# +# @unstable: This command is meant for debugging. +# +# Returns: accelerator statistics +# +# Since: 10.1 +## +{ 'command': 'x-accel-stats', + 'returns': 'HumanReadableText', + 'features': [ 'unstable' ] } diff --git a/qapi/acpi.json b/qapi/acpi.json index 2d53b82..906b368 100644 --- a/qapi/acpi.json +++ b/qapi/acpi.json @@ -6,7 +6,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later ## -# = ACPI +# **** +# ACPI +# **** ## ## @@ -106,7 +108,7 @@ ## # @query-acpi-ospm-status: # -# Return a list of ACPIOSTInfo for devices that support status +# Return a list of `ACPIOSTInfo` for devices that support status # reporting via ACPI _OST method. # # Since: 2.1 diff --git a/qapi/audio.json b/qapi/audio.json index 16de231..5314208 100644 --- a/qapi/audio.json +++ b/qapi/audio.json @@ -7,7 +7,9 @@ # See the COPYING file in the top-level directory. ## -# = Audio +# ***** +# Audio +# ***** ## ## @@ -535,8 +537,6 @@ # # Return information about audiodev configuration # -# Returns: array of @Audiodev -# # Since: 8.0 ## { 'command': 'query-audiodevs', diff --git a/qapi/authz.json b/qapi/authz.json index 7fc6e30..bc1123c 100644 --- a/qapi/authz.json +++ b/qapi/authz.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = User authorization +# ****************** +# User authorization +# ****************** ## ## @@ -75,7 +77,7 @@ # Properties for authz-listfile objects. # # @filename: File name to load the configuration from. The file must -# contain valid JSON for AuthZListProperties. +# contain valid JSON for `AuthZListProperties`. # # @refresh: If true, inotify is used to monitor the file, # automatically reloading changes. If an error occurs during diff --git a/qapi/block-core.json b/qapi/block-core.json index 1df6644..ebbe95b 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2,7 +2,8 @@ # vim: filetype=python ## -# == Block core (VM unrelated) +# Block core (VM unrelated) +# ========================= ## { 'include': 'common.json' } @@ -31,7 +32,7 @@ # @icount: Current instruction count. Appears when execution # record/replay is enabled. Used for "time-traveling" to match # the moment in the recorded execution with the snapshots. This -# counter may be obtained through @query-replay command +# counter may be obtained through `query-replay` command # (since 5.2) # # Since: 1.3 @@ -223,7 +224,7 @@ { 'struct': 'ImageInfoSpecificLUKSWrapper', 'data': { 'data': 'QCryptoBlockInfoLUKS' } } # If we need to add block driver specific parameters for -# LUKS in future, then we'll subclass QCryptoBlockInfoLUKS +# LUKS in future, then we'll subclass `QCryptoBlockInfoLUKS` # to define a ImageInfoSpecificLUKS ## @@ -332,7 +333,7 @@ # node, annotated with information about that node in relation to its # parent. # -# @name: Child name of the root node in the BlockGraphInfo struct, in +# @name: Child name of the root node in the `BlockGraphInfo` struct, in # its role as the child of some undescribed parent node # # @info: Block graph information starting at this node @@ -349,7 +350,7 @@ # @BlockGraphInfo: # # Information about all nodes in a block (sub)graph in the form of -# BlockNodeInfo data. The base BlockNodeInfo struct contains the +# `BlockNodeInfo` data. The base `BlockNodeInfo` struct contains the # information for the (sub)graph's root node. # # @children: Array of links to this node's child nodes' information @@ -462,6 +463,19 @@ 'no-flush': 'bool' } } ## +# @BlockdevChild: +# +# @child: The name of the child, for example 'file' or 'backing'. +# +# @node-name: The name of the child's block driver node. +# +# Since: 10.1 +## +{ 'struct': 'BlockdevChild', + 'data': { 'child': 'str', + 'node-name': 'str' } } + +## # @BlockDeviceInfo: # # Information about the backing device for a block device. @@ -486,6 +500,8 @@ # @backing_file_depth: number of files in the backing file chain # (since: 1.2) # +# @children: Information about child block nodes. (since: 10.1) +# # @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) @@ -558,8 +574,9 @@ # Since: 0.14 ## { 'struct': 'BlockDeviceInfo', - 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', + 'data': { 'file': 'str', 'node-name': 'str', 'ro': 'bool', 'drv': 'str', '*backing_file': 'str', 'backing_file_depth': 'int', + 'children': ['BlockdevChild'], 'active': 'bool', 'encrypted': 'bool', 'detect_zeroes': 'BlockdevDetectZeroesOptions', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', @@ -614,7 +631,7 @@ # @inconsistent: true if this is a persistent bitmap that was # improperly stored. Implies @persistent to be true; @recording # and @busy to be false. This bitmap cannot be used. To remove -# it, use @block-dirty-bitmap-remove. (Since 4.0) +# it, use `block-dirty-bitmap-remove`. (Since 4.0) # # Since: 1.3 ## @@ -709,12 +726,12 @@ # @tray_open: True if the device's tray is open (only present if it # has a tray) # -# @io-status: @BlockDeviceIoStatus. Only present if the device +# @io-status: `BlockDeviceIoStatus`. Only present if the device # supports it and the VM is configured to stop on errors # (supported device models: virtio-blk, IDE, SCSI except # scsi-generic) # -# @inserted: @BlockDeviceInfo describing the device if media is +# @inserted: `BlockDeviceInfo` describing the device if media is # present # # Since: 0.14 @@ -761,9 +778,9 @@ ## # @query-block: # -# Get a list of BlockInfo for all virtual block devices. +# Get a list of `BlockInfo` for all virtual block devices. # -# Returns: a list of @BlockInfo describing each virtual block device. +# Returns: a list describing each virtual block device. # Filter nodes that were created implicitly are skipped over. # # Since: 0.14 @@ -1026,14 +1043,14 @@ # @timed_stats: Statistics specific to the set of previously defined # intervals of time (Since 2.5) # -# @rd_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) +# @rd_latency_histogram: `BlockLatencyHistogramInfo`. (Since 4.0) # -# @wr_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) +# @wr_latency_histogram: `BlockLatencyHistogramInfo`. (Since 4.0) # -# @zone_append_latency_histogram: @BlockLatencyHistogramInfo. +# @zone_append_latency_histogram: `BlockLatencyHistogramInfo`. # (since 8.1) # -# @flush_latency_histogram: @BlockLatencyHistogramInfo. (Since 4.0) +# @flush_latency_histogram: `BlockLatencyHistogramInfo`. (Since 4.0) # # Since: 0.14 ## @@ -1134,7 +1151,7 @@ # @qdev: The qdev ID, or if no ID is assigned, the QOM path of the # block device. (since 3.0) # -# @stats: A @BlockDeviceStats for the device. +# @stats: A `BlockDeviceStats` for the device. # # @driver-specific: Optional driver-specific stats. (Since 4.2) # @@ -1158,7 +1175,7 @@ ## # @query-blockstats: # -# Query the @BlockStats for all virtual block devices. +# Query the `BlockStats` for all virtual block devices. # # @query-nodes: If true, the command will query all the block nodes # that have a node name, in a list which will include "parent" @@ -1168,7 +1185,7 @@ # nodes that were created implicitly are skipped over in this # mode. (Since 2.3) # -# Returns: A list of @BlockStats for each virtual block devices. +# Returns: A list of statistics for each virtual block device. # # Since: 0.14 # @@ -1289,8 +1306,8 @@ # @report: for guest operations, report the error to the guest; for # jobs, cancel the job # -# @ignore: ignore the error, only report a QMP event (BLOCK_IO_ERROR -# or BLOCK_JOB_ERROR). The backup, mirror and commit block jobs +# @ignore: ignore the error, only report a QMP event (`BLOCK_IO_ERROR` +# or `BLOCK_JOB_ERROR`). The backup, mirror and commit block jobs # retry the failing request later and may still complete # successfully. The stream block job continues to stream and will # complete with an error. @@ -1323,7 +1340,7 @@ # (since: 2.4) # # @bitmap: only copy data described by the dirty bitmap. Behavior on -# completion is determined by the BitmapSyncMode. (since: 4.2) +# completion is determined by the `BitmapSyncMode`. (since: 4.2) # # Since: 1.3 ## @@ -1440,7 +1457,7 @@ # # Return information about long-running block device operations. # -# Returns: a list of @BlockJobInfo for each active block job +# Returns: a list of job info for each active block job # # Since: 1.1 ## @@ -1530,7 +1547,7 @@ # @overlay: reference to the existing block device that will become # the overlay of @node, as part of taking the snapshot. It must # not have a current backing file (this can be achieved by passing -# "backing": null to blockdev-add). +# "backing": null to `blockdev-add`). # # Since: 2.5 ## @@ -1585,7 +1602,7 @@ # @bitmap: The name of a dirty bitmap to use. Must be present if sync # is "bitmap" or "incremental". Can be present if sync is "full" # or "top". Must not be present otherwise. -# (Since 2.4 (drive-backup), 3.1 (blockdev-backup)) +# (Since 2.4 (`drive-backup`), 3.1 (`blockdev-backup`)) # # @bitmap-mode: Specifies the type of data the bitmap should contain # after the operation concludes. Must be present if a bitmap was @@ -1596,7 +1613,7 @@ # # @on-source-error: the action to take on an error on the source, # default 'report'. 'stop' and 'enospc' can only be used if the -# block device supports io-status (see BlockInfo). +# block device supports io-status (see `BlockInfo`). # # @on-target-error: the action to take on an error on the target, # default 'report' (no limitations, since this applies to a @@ -1606,14 +1623,14 @@ # copy-before-write jobs; defaults to break-guest-write. (Since 10.1) # # @auto-finalize: When false, this job will wait in a PENDING state -# after it has finished its work, waiting for @job-finalize before +# after it has finished its work, waiting for `job-finalize` before # making any block graph changes. When true, this job will # automatically perform its abort or commit actions. Defaults to # true. (Since 2.12) # # @auto-dismiss: When false, this job will wait in a CONCLUDED state # after it has completely ceased all work, and awaits -# @job-dismiss. When true, this job will automatically disappear +# `job-dismiss`. When true, this job will automatically disappear # without user intervention. Defaults to true. (Since 2.12) # # @filter-node-name: the node name that should be assigned to the @@ -1719,7 +1736,7 @@ # # @allow-write-only-overlay: If present, the check whether this # operation is safe was relaxed so that it can be used to change -# backing file of a destination of a blockdev-mirror. (since 5.0) +# backing file of a destination of a `blockdev-mirror`. (since 5.0) # # Since: 2.5 # @@ -1784,7 +1801,7 @@ # If top == base, that is an error. If top has no overlays on top of # it, or if it is in use by a writer, the job will not be completed by # itself. The user needs to complete the job with the -# job-complete command after getting the ready event. (Since 2.0) +# `job-complete` command after getting the ready event. (Since 2.0) # # If the base image is smaller than top, then the base image will be # resized to be the same size as top. If top is smaller than the base @@ -1846,14 +1863,14 @@ # autogenerated. (Since: 2.9) # # @auto-finalize: When false, this job will wait in a PENDING state -# after it has finished its work, waiting for @job-finalize before +# after it has finished its work, waiting for `job-finalize` before # making any block graph changes. When true, this job will # automatically perform its abort or commit actions. Defaults to # true. (Since 3.1) # # @auto-dismiss: When false, this job will wait in a CONCLUDED state # after it has completely ceased all work, and awaits -# @job-dismiss. When true, this job will automatically disappear +# `job-dismiss`. When true, this job will automatically disappear # without user intervention. Defaults to true. (Since 3.1) # # Features: @@ -1889,14 +1906,14 @@ # @drive-backup: # # Start a point-in-time copy of a block device to a new destination. -# The status of ongoing drive-backup operations can be checked with -# query-block-jobs where the BlockJobInfo.type field has the value +# The status of ongoing `drive-backup` operations can be checked with +# `query-block-jobs` where the `BlockJobInfo`.type field has the value # 'backup'. The operation can be stopped before it has completed -# using the job-cancel or block-job-cancel command. +# using the `job-cancel` or `block-job-cancel` command. # # Features: # -# @deprecated: This command is deprecated. Use @blockdev-backup +# @deprecated: This command is deprecated. Use `blockdev-backup` # instead. # # Errors: @@ -1920,10 +1937,10 @@ # @blockdev-backup: # # Start a point-in-time copy of a block device to a new destination. -# The status of ongoing blockdev-backup operations can be checked with -# query-block-jobs where the BlockJobInfo.type field has the value +# The status of ongoing `blockdev-backup` operations can be checked with +# `query-block-jobs` where the `BlockJobInfo`.type field has the value # 'backup'. The operation can be stopped before it has completed -# using the job-cancel or block-job-cancel command. +# using the `job-cancel` or `block-job-cancel` command. # # Errors: # - If @device is not a valid block device, DeviceNotFound @@ -1950,8 +1967,6 @@ # @flat: Omit the nested data about backing image ("backing-image" # key) if true. Default is false (Since 5.0) # -# Returns: the list of BlockDeviceInfo -# # Since: 2.0 # # .. qmp-example:: @@ -2026,7 +2041,7 @@ # @XDbgBlockGraphNode: # # @id: Block graph node identifier. This @id is generated only for -# x-debug-query-block-graph and does not relate to any other +# `x-debug-query-block-graph` and does not relate to any other # identifiers in QEMU. # # @type: Type of graph node. Can be one of block-backend, block-job @@ -2075,7 +2090,7 @@ ## # @XDbgBlockGraphEdge: # -# Block Graph edge description for x-debug-query-block-graph. +# Block Graph edge description for `x-debug-query-block-graph`. # # @parent: parent id # @@ -2193,7 +2208,7 @@ # # @on-source-error: the action to take on an error on the source, # default 'report'. 'stop' and 'enospc' can only be used if the -# block device supports io-status (see BlockInfo). +# block device supports io-status (see `BlockInfo`). # # @on-target-error: the action to take on an error on the target, # default 'report' (no limitations, since this applies to a @@ -2209,14 +2224,14 @@ # 'background' (Since: 3.0) # # @auto-finalize: When false, this job will wait in a PENDING state -# after it has finished its work, waiting for @job-finalize before +# after it has finished its work, waiting for `job-finalize` before # making any block graph changes. When true, this job will # automatically perform its abort or commit actions. Defaults to # true. (Since 3.1) # # @auto-dismiss: When false, this job will wait in a CONCLUDED state # after it has completely ceased all work, and awaits -# @job-dismiss. When true, this job will automatically disappear +# `job-dismiss`. When true, this job will automatically disappear # without user intervention. Defaults to true. (Since 3.1) # # Since: 1.3 @@ -2251,16 +2266,16 @@ # @name: name of the dirty bitmap (must be less than 1024 bytes) # # @granularity: the bitmap granularity, default is 64k for -# block-dirty-bitmap-add +# `block-dirty-bitmap-add` # # @persistent: the bitmap is persistent, i.e. it will be saved to the # corresponding block device image file on its close. For now # only Qcow2 disks support persistent bitmaps. Default is false -# for block-dirty-bitmap-add. (Since: 2.10) +# for `block-dirty-bitmap-add`. (Since: 2.10) # # @disabled: the bitmap is created in the disabled state, which means # that it will not track drive changes. The bitmap may be enabled -# with block-dirty-bitmap-enable. Default is false. (Since: 4.0) +# with `block-dirty-bitmap-enable`. Default is false. (Since: 4.0) # # Since: 2.4 ## @@ -2290,7 +2305,7 @@ # @target: name of the destination dirty bitmap # # @bitmaps: name(s) of the source dirty bitmap(s) at @node and/or -# fully specified BlockDirtyBitmap elements. The latter are +# fully specified `BlockDirtyBitmap` elements. The latter are # supported since 4.1. # # Since: 4.0 @@ -2325,7 +2340,7 @@ # @block-dirty-bitmap-remove: # # Stop write tracking and remove the dirty bitmap that was created -# with block-dirty-bitmap-add. If the bitmap is persistent, remove it +# with `block-dirty-bitmap-add`. If the bitmap is persistent, remove it # from its storage too. # # Errors: @@ -2464,9 +2479,6 @@ # # @unstable: This command is meant for debugging. # -# Returns: -# BlockDirtyBitmapSha256 -# # Errors: # - If @node is not a valid block device, DeviceNotFound # - If @name is not found or if hashing has failed, GenericError @@ -2512,7 +2524,7 @@ # # @on-source-error: the action to take on an error on the source, # default 'report'. 'stop' and 'enospc' can only be used if the -# block device supports io-status (see BlockInfo). +# block device supports io-status (see `BlockInfo`). # # @on-target-error: the action to take on an error on the target, # default 'report' (no limitations, since this applies to a @@ -2527,14 +2539,14 @@ # 'background' (Since: 3.0) # # @auto-finalize: When false, this job will wait in a PENDING state -# after it has finished its work, waiting for @job-finalize before +# after it has finished its work, waiting for `job-finalize` before # making any block graph changes. When true, this job will # automatically perform its abort or commit actions. Defaults to # true. (Since 3.1) # # @auto-dismiss: When false, this job will wait in a CONCLUDED state # after it has completely ceased all work, and awaits -# @job-dismiss. When true, this job will automatically disappear +# `job-dismiss`. When true, this job will automatically disappear # without user intervention. Defaults to true. (Since 3.1) # # @target-is-zero: Assume the destination reads as all zeroes before @@ -2782,9 +2794,9 @@ # The block streaming operation is performed in the background until # the entire backing file has been copied. This command returns # immediately once streaming has started. The status of ongoing block -# streaming operations can be checked with query-block-jobs. The +# streaming operations can be checked with `query-block-jobs`. The # operation can be stopped before it has completed using the -# job-cancel or block-job-cancel command. +# `job-cancel` or `block-job-cancel` command. # # The node that receives the data is called the top image, can be # located in any part of the chain (but always above the base image; @@ -2803,9 +2815,9 @@ # will be the new backing file. # # On successful completion the image file is updated to drop the -# backing file and the BLOCK_JOB_COMPLETED event is emitted. +# backing file and the `BLOCK_JOB_COMPLETED` event is emitted. # -# In case @device is a filter node, block-stream modifies the first +# In case @device is a filter node, `block-stream` modifies the first # non-filter overlay node below it to point to the new backing node # instead of modifying @device itself. # @@ -2846,7 +2858,7 @@ # # @on-error: the action to take on an error (default report). 'stop' # and 'enospc' can only be used if the block device supports -# io-status (see BlockInfo). (Since 1.3) +# io-status (see `BlockInfo`). (Since 1.3) # # @filter-node-name: the node name that should be assigned to the # filter driver that the stream job inserts into the graph above @@ -2854,14 +2866,14 @@ # autogenerated. (Since: 6.0) # # @auto-finalize: When false, this job will wait in a PENDING state -# after it has finished its work, waiting for @job-finalize before +# after it has finished its work, waiting for `job-finalize` before # making any block graph changes. When true, this job will # automatically perform its abort or commit actions. Defaults to # true. (Since 3.1) # # @auto-dismiss: When false, this job will wait in a CONCLUDED state # after it has completely ceased all work, and awaits -# @job-dismiss. When true, this job will automatically disappear +# `job-dismiss`. When true, this job will automatically disappear # without user intervention. Defaults to true. (Since 3.1) # # Errors: @@ -2922,13 +2934,13 @@ # command if no operation is in progress. # # The operation will cancel as soon as possible and then emit the -# BLOCK_JOB_CANCELLED event. Before that happens the job is still -# visible when enumerated using query-block-jobs. +# `BLOCK_JOB_CANCELLED` event. Before that happens the job is still +# visible when enumerated using `query-block-jobs`. # -# Note that if you issue 'block-job-cancel' after 'drive-mirror' has -# indicated (via the event BLOCK_JOB_READY) that the source and +# Note that if you issue `block-job-cancel` after `drive-mirror` has +# indicated (via the event `BLOCK_JOB_READY`) that the source and # destination are synchronized, then the event triggered by this -# command changes to BLOCK_JOB_COMPLETED, to indicate that the +# command changes to `BLOCK_JOB_COMPLETED`, to indicate that the # mirroring has ended and the destination now has a point-in-time copy # tied to the time of the cancellation. # @@ -2942,7 +2954,7 @@ # values. # # @force: If true, and the job has already emitted the event -# BLOCK_JOB_READY, abandon the job immediately (even if it is +# `BLOCK_JOB_READY`, abandon the job immediately (even if it is # paused) instead of waiting for the destination to complete its # final synchronization (since 1.3) # @@ -2965,7 +2977,7 @@ # # The job will pause as soon as possible, which means transitioning # into the PAUSED state if it was RUNNING, or into STANDBY if it was -# READY. The corresponding JOB_STATUS_CHANGE event will be emitted. +# READY. The corresponding `JOB_STATUS_CHANGE` event will be emitted. # # Cancelling a paused job automatically resumes it. # @@ -2975,7 +2987,7 @@ # # Features: # -# @deprecated: This command is deprecated. Use @job-pause +# @deprecated: This command is deprecated. Use `job-pause` # instead. # # Errors: @@ -3004,7 +3016,7 @@ # # Features: # -# @deprecated: This command is deprecated. Use @job-resume +# @deprecated: This command is deprecated. Use `job-resume` # instead. # # Errors: @@ -3025,14 +3037,14 @@ # # This is supported only for drive mirroring, where it also switches # the device to write to the target path only. Note that drive -# mirroring includes drive-mirror, blockdev-mirror and block-commit +# mirroring includes `drive-mirror`, `blockdev-mirror` and `block-commit` # job (only in case of "active commit", when the node being commited # is used by the guest). The ability to complete is signaled with a -# BLOCK_JOB_READY event. +# `BLOCK_JOB_READY` event. # # This command completes an active background block operation # synchronously. The ordering of this command's return with the -# BLOCK_JOB_COMPLETED event is not defined. Note that if an I/O error +# `BLOCK_JOB_COMPLETED` event is not defined. Note that if an I/O error # occurs during the processing of this command: 1) the command itself # will fail; 2) the error will be processed according to the # rerror/werror arguments that were specified when starting the @@ -3044,7 +3056,7 @@ # # Features: # -# @deprecated: This command is deprecated. Use @job-complete +# @deprecated: This command is deprecated. Use `job-complete` # instead. # # Errors: @@ -3063,21 +3075,21 @@ # Deletes a job that is in the CONCLUDED state. This command only # needs to be run explicitly for jobs that don't have automatic # dismiss enabled. In turn, automatic dismiss may be enabled only -# for jobs that have @auto-dismiss option, which are drive-backup, -# blockdev-backup, drive-mirror, blockdev-mirror, block-commit and -# block-stream. @auto-dismiss is enabled by default for these +# for jobs that have @auto-dismiss option, which are `drive-backup`, +# `blockdev-backup`, `drive-mirror`, `blockdev-mirror`, `block-commit` and +# `block-stream`. @auto-dismiss is enabled by default for these # jobs. # # This command will refuse to operate on any job that has not yet # reached its terminal state, CONCLUDED. For jobs that make use of -# the BLOCK_JOB_READY event, job-cancel, block-job-cancel or -# job-complete will still need to be used as appropriate. +# the `BLOCK_JOB_READY` event, `job-cancel`, `block-job-cancel` or +# `job-complete` will still need to be used as appropriate. # # @id: The job identifier. # # Features: # -# @deprecated: This command is deprecated. Use @job-dismiss +# @deprecated: This command is deprecated. Use `job-dismiss` # instead. # # Since: 2.12 @@ -3105,7 +3117,7 @@ # # Features: # -# @deprecated: This command is deprecated. Use @job-finalize +# @deprecated: This command is deprecated. Use `job-finalize` # instead. # # Since: 2.12 @@ -3177,7 +3189,7 @@ # @on: Enabled # # @unmap: Enabled and even try to unmap blocks if possible. This -# requires also that @BlockdevDiscardOptions is set to unmap for +# requires also that `BlockdevDiscardOptions` is set to unmap for # this device. # # Since: 2.1 @@ -4719,7 +4731,7 @@ # @driver: block driver name # # @node-name: the node name of the new node. This option is required -# on the top level of blockdev-add. Valid node names start with +# on the top level of `blockdev-add`. Valid node names start with # an alphabetic character and may contain only alphanumeric # characters, '-', '.' and '_'. Their maximum length is 31 # characters. (Since 2.0) @@ -4923,7 +4935,7 @@ # cancelled. # # The command receives a list of block devices to reopen. For each -# one of them, the top-level @node-name option (from BlockdevOptions) +# one of them, the top-level @node-name option (from `BlockdevOptions`) # must be specified and is used to select the block device to be # reopened. Other @node-name options must be either omitted or set to # the current name of the appropriate node. This command won't change @@ -4932,7 +4944,7 @@ # In the case of options that refer to child nodes, the behavior of # this command depends on the value: # -# 1) A set of options (BlockdevOptions): the child is reopened with +# 1) A set of options (`BlockdevOptions`): the child is reopened with # the specified set of options. # # 2) A reference to the current child: the child is reopened using @@ -4946,7 +4958,7 @@ # Options (1) and (2) are supported in all cases. Option (3) is # supported for @file and @backing, and option (4) for @backing only. # -# Unlike with blockdev-add, the @backing option must always be present +# Unlike with `blockdev-add`, the @backing option must always be present # unless the node being reopened does not have a backing file and its # image does not have a default backing file name as part of its # metadata. @@ -4960,7 +4972,7 @@ ## # @blockdev-del: # -# Deletes a block device that has been added using blockdev-add. The +# Deletes a block device that has been added using `blockdev-add`. The # command will fail if the node is attached to a device or is # otherwise being used. # @@ -5515,7 +5527,7 @@ # @blockdev-create: # # Starts a job to create an image format on a given node. The job is -# automatically finalized, but a manual job-dismiss is required. +# automatically finalized, but a manual `job-dismiss` is required. # # @job-id: Identifier for the newly created job. # @@ -5575,7 +5587,7 @@ # # Starts a job to amend format specific options of an existing open # block device. The job is automatically finalized, but a manual -# job-dismiss is required. +# `job-dismiss` is required. # # @job-id: Identifier for the newly created job. # @@ -5644,10 +5656,10 @@ # # @fatal: if set, the image is marked corrupt and therefore unusable # after this event and must be repaired (Since 2.2; before, every -# BLOCK_IMAGE_CORRUPTED event was fatal) +# `BLOCK_IMAGE_CORRUPTED` event was fatal) # -# .. note:: If action is "stop", a STOP event will eventually follow -# the BLOCK_IO_ERROR event. +# .. note:: If action is "stop", a `STOP` event will eventually follow +# the `BLOCK_IO_ERROR` event. # # .. qmp-example:: # @@ -5688,15 +5700,15 @@ # # @nospace: true if I/O error was caused due to a no-space condition. # This key is only present if query-block's io-status is present, -# please see query-block documentation for more information +# please see `query-block` documentation for more information # (since: 2.2) # # @reason: human readable string describing the error cause. (This # field is a debugging aid for humans, it should not be parsed by # applications) (since: 2.2) # -# .. note:: If action is "stop", a STOP event will eventually follow -# the BLOCK_IO_ERROR event. +# .. note:: If action is "stop", a `STOP` event will eventually follow +# the `BLOCK_IO_ERROR` event. # # .. note:: This event is rate-limited. # @@ -5838,7 +5850,7 @@ # @speed: rate limit, bytes per second # # .. note:: The "ready to complete" status is always reset by a -# @BLOCK_JOB_ERROR event. +# `BLOCK_JOB_ERROR` event. # # Since: 1.3 # @@ -5860,7 +5872,7 @@ # @BLOCK_JOB_PENDING: # # Emitted when a block job is awaiting explicit authorization to -# finalize graph changes via @job-finalize. If this job is part +# finalize graph changes via `job-finalize`. If this job is part # of a transaction, it will not emit this event until the transaction # has converged first. # @@ -5909,7 +5921,7 @@ # configured write threshold. For thin-provisioned devices, this # means the device should be extended to avoid pausing for disk # exhaustion. The event is one shot. Once triggered, it needs to be -# re-registered with another block-set-write-threshold command. +# re-registered with another `block-set-write-threshold` command. # # @node-name: graph node name on which the threshold was exceeded. # @@ -5980,7 +5992,7 @@ # # TODO: Removing children from a quorum node means introducing # gaps in the child indices. This cannot be represented in the -# 'children' list of BlockdevOptionsQuorum, as returned by +# 'children' list of `BlockdevOptionsQuorum`, as returned by # .bdrv_refresh_filename(). # # Since: 2.7 @@ -6193,7 +6205,7 @@ # Synchronously delete an internal snapshot of a block device, when # the format of the image used support it. The snapshot is identified # by name or id or both. One of the name or id is required. Return -# SnapshotInfo for the successfully deleted snapshot. +# `SnapshotInfo` for the successfully deleted snapshot. # # @device: the device name or node-name of a root node to delete the # snapshot from @@ -6202,9 +6214,6 @@ # # @name: optional the snapshot's name to be deleted # -# Returns: -# SnapshotInfo -# # Errors: # - If @device is not a valid block device, GenericError # - If snapshot not found, GenericError diff --git a/qapi/block-export.json b/qapi/block-export.json index ed4deb5..6878b89 100644 --- a/qapi/block-export.json +++ b/qapi/block-export.json @@ -2,7 +2,8 @@ # vim: filetype=python ## -# == Block device exports +# Block device exports +# ==================== ## { 'include': 'sockets.json' } @@ -37,9 +38,9 @@ ## # @NbdServerOptions: # -# Keep this type consistent with the NbdServerOptionsLegacy type. The -# only intended difference is using SocketAddress instead of -# SocketAddressLegacy. +# Keep this type consistent with the `NbdServerOptionsLegacy` type. The +# only intended difference is using `SocketAddress` instead of +# `SocketAddressLegacy`. # # @addr: Address on which to listen (since 4.2). ## @@ -50,9 +51,9 @@ ## # @NbdServerOptionsLegacy: # -# Keep this type consistent with the NbdServerOptions type. The only -# intended difference is using SocketAddressLegacy instead of -# SocketAddress. +# Keep this type consistent with the `NbdServerOptions` type. The only +# intended difference is using `SocketAddressLegacy` instead of +# `SocketAddress`. # # @addr: Address on which to listen (since 1.3). ## @@ -64,7 +65,7 @@ # @nbd-server-start: # # Start an NBD server listening on the given host and port. Block -# devices can then be exported using @nbd-server-add. The NBD server +# devices can then be exported using `nbd-server-add`. The NBD server # will present them as named exports; for example, another QEMU # instance could refer to them as "nbd:HOST:PORT:exportname=NAME". # @@ -80,8 +81,8 @@ ## # @BlockExportOptionsNbdBase: # -# An NBD block export (common options shared between nbd-server-add -# and the NBD branch of block-export-add). +# An NBD block export (common options shared between `nbd-server-add` +# and the NBD branch of `block-export-add`). # # @name: Export name. If unspecified, the @device parameter is used # as the export name. (Since 2.12) @@ -98,7 +99,7 @@ # @BlockExportOptionsNbd: # # An NBD block export (distinct options used in the NBD branch of -# block-export-add). +# `block-export-add`). # # @bitmaps: Also export each of the named dirty bitmaps reachable from # @device, so the NBD client can use NBD_OPT_SET_META_CONTEXT with @@ -124,7 +125,7 @@ # A vhost-user-blk block export. # # @addr: The vhost-user socket on which to listen. Both 'unix' and -# 'fd' SocketAddress types are supported. Passed fds must be UNIX +# 'fd' `SocketAddress` types are supported. Passed fds must be UNIX # domain sockets. # # @logical-block-size: Logical block size in bytes. Defaults to 512 @@ -216,7 +217,7 @@ ## # @NbdServerAddOptions: # -# An NBD block export, per legacy nbd-server-add command. +# An NBD block export, per legacy `nbd-server-add` command. # # @device: The device name or node name of the node to be exported # @@ -245,7 +246,7 @@ # # Features: # -# @deprecated: This command is deprecated. Use @block-export-add +# @deprecated: This command is deprecated. Use `block-export-add` # instead. # # Errors: @@ -288,12 +289,12 @@ # # @name: Block export id. # -# @mode: Mode of command operation. See @BlockExportRemoveMode +# @mode: Mode of command operation. See `BlockExportRemoveMode` # description. Default is 'safe'. # # Features: # -# @deprecated: This command is deprecated. Use @block-export-del +# @deprecated: This command is deprecated. Use `block-export-del` # instead. # # Errors: @@ -312,7 +313,7 @@ # @nbd-server-stop: # # Stop QEMU's embedded NBD server, and unregister all devices -# previously added via @nbd-server-add. +# previously added via `nbd-server-add`. # # Since: 1.3 ## @@ -421,7 +422,7 @@ # # @id: Block export id. # -# @mode: Mode of command operation. See @BlockExportRemoveMode +# @mode: Mode of command operation. See `BlockExportRemoveMode` # description. Default is 'safe'. # # Errors: @@ -459,7 +460,7 @@ # @node-name: The node name of the block node that is exported # # @shutting-down: True if the export is shutting down (e.g. after a -# block-export-del command, but before the shutdown has completed) +# `block-export-del` command, but before the shutdown has completed) # # Since: 5.2 ## @@ -472,7 +473,7 @@ ## # @query-block-exports: # -# Returns: A list of BlockExportInfo describing all block exports +# Returns: A list describing all block exports # # Since: 5.2 ## diff --git a/qapi/block.json b/qapi/block.json index 1490a1a..46955bb 100644 --- a/qapi/block.json +++ b/qapi/block.json @@ -2,13 +2,16 @@ # vim: filetype=python ## -# = Block devices +# ************* +# Block devices +# ************* ## { 'include': 'block-core.json' } ## -# == Additional block stuff (VM related) +# Additional block stuff (VM related) +# =================================== ## ## @@ -86,7 +89,7 @@ # Return a list of information about each persistent reservation # manager. # -# Returns: a list of @PRManagerInfo for each persistent reservation +# Returns: a list of manager info for each persistent reservation # manager # # Since: 3.0 @@ -137,7 +140,7 @@ # # If the tray was already open before, this will be a no-op. # -# Once the tray opens, a DEVICE_TRAY_MOVED event is emitted. There +# Once the tray opens, a `DEVICE_TRAY_MOVED` event is emitted. There # are cases in which no such event will be generated, these include: # # - if the guest has locked the tray, @force is false and the guest @@ -296,7 +299,7 @@ # @BlockdevChangeReadOnlyMode: # # Specifies the new read-only mode of a block device subject to the -# @blockdev-change-medium command. +# `blockdev-change-medium` command. # # @retain: Retains the current read-only mode # @@ -314,9 +317,9 @@ # # Changes the medium inserted into a block device by ejecting the # current medium and loading a new image file which is inserted as the -# new medium (this command combines blockdev-open-tray, -# blockdev-remove-medium, blockdev-insert-medium and -# blockdev-close-tray). +# new medium (this command combines `blockdev-open-tray`, +# `blockdev-remove-medium`, `blockdev-insert-medium` and +# `blockdev-close-tray`). # # @device: Block device name # @@ -331,7 +334,7 @@ # to 'retain' # # @force: if false (the default), an eject request through -# blockdev-open-tray will be sent to the guest if it has locked +# `blockdev-open-tray` will be sent to the guest if it has locked # the tray (and the tray will not be opened immediately); if true, # the tray will be opened regardless of whether it is locked. # (since 7.1) @@ -519,7 +522,7 @@ # @id: The name or QOM path of the guest device. # # @boundaries: list of interval boundary values (see description in -# BlockLatencyHistogramInfo definition). If specified, all +# `BlockLatencyHistogramInfo` definition). If specified, all # latency histograms are removed, and empty ones created for all # io types with intervals corresponding to @boundaries (except for # io types, for which specific boundaries are set through the diff --git a/qapi/char.json b/qapi/char.json index df6e325..f0a53f7 100644 --- a/qapi/char.json +++ b/qapi/char.json @@ -3,7 +3,9 @@ # ## -# = Character devices +# ***************** +# Character devices +# ***************** ## { 'include': 'sockets.json' } @@ -36,8 +38,6 @@ # # Return information about current character devices. # -# Returns: a list of @ChardevInfo -# # Since: 0.14 # # .. qmp-example:: @@ -82,8 +82,6 @@ # # Return information about character device backends. # -# Returns: a list of @ChardevBackendInfo -# # Since: 2.0 # # .. qmp-example:: @@ -772,8 +770,6 @@ # # @backend: backend type and parameters # -# Returns: ChardevReturn. -# # Since: 1.4 # # .. qmp-example:: @@ -812,8 +808,6 @@ # # @backend: new backend type and parameters # -# Returns: ChardevReturn. -# # Since: 2.10 # # .. qmp-example:: diff --git a/qapi/common.json b/qapi/common.json index 0e3a0bb..af7e3d6 100644 --- a/qapi/common.json +++ b/qapi/common.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = Common data types +# ***************** +# Common data types +# ***************** ## ## diff --git a/qapi/compat.json b/qapi/compat.json index 42034d9..90b8d51 100644 --- a/qapi/compat.json +++ b/qapi/compat.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = Compatibility policy +# ******************** +# Compatibility policy +# ******************** ## ## diff --git a/qapi/control.json b/qapi/control.json index 34b733f..9a53021 100644 --- a/qapi/control.json +++ b/qapi/control.json @@ -3,7 +3,9 @@ # ## -# = QMP monitor control +# ******************* +# QMP monitor control +# ******************* ## ## @@ -11,7 +13,7 @@ # # Enable QMP capabilities. # -# @enable: An optional list of QMPCapability values to enable. The +# @enable: An optional list of `QMPCapability` values to enable. The # client must not enable any capability that is not mentioned in # the QMP greeting message. If the field is not provided, it # means no QMP capabilities will be enabled. (since 2.12) @@ -93,8 +95,7 @@ # # Return the current version of QEMU. # -# Returns: A @VersionInfo object describing the current version of -# QEMU. +# Returns: An object describing the current version of QEMU. # # Since: 0.14 # @@ -131,7 +132,7 @@ # # Return a list of supported QMP commands by this server # -# Returns: A list of @CommandInfo for all supported commands +# Returns: A list of all supported commands # # Since: 0.14 # @@ -158,10 +159,11 @@ ## # @quit: # -# This command will cause the QEMU process to exit gracefully. While -# every attempt is made to send the QMP response before terminating, -# this is not guaranteed. When using this interface, a premature EOF -# would not be unexpected. +# Request graceful QEMU process termination. +# +# While every attempt is made to send the QMP response before +# terminating, this is not guaranteed. When using this interface, a +# premature EOF would not be unexpected. # # Since: 0.14 # diff --git a/qapi/crypto.json b/qapi/crypto.json index 9ec6301..ab6eda4 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -3,7 +3,9 @@ # ## -# = Cryptography +# ************ +# Cryptography +# ************ ## ## @@ -589,9 +591,9 @@ # # Specific parameters for RSA algorithm. # -# @hash-alg: QCryptoHashAlgo +# @hash-alg: `QCryptoHashAlgo` # -# @padding-alg: QCryptoRSAPaddingAlgo +# @padding-alg: `QCryptoRSAPaddingAlgo` # # Since: 7.1 ## diff --git a/qapi/cryptodev.json b/qapi/cryptodev.json index b13db26..eb309c2 100644 --- a/qapi/cryptodev.json +++ b/qapi/cryptodev.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = Cryptography devices +# ******************** +# Cryptography devices +# ******************** ## ## @@ -96,8 +98,6 @@ # # Return information about current crypto devices. # -# Returns: a list of @QCryptodevInfo -# # Since: 8.0 ## { 'command': 'query-cryptodev', 'returns': ['QCryptodevInfo']} diff --git a/qapi/cxl.json b/qapi/cxl.json index 8f2e923..52cc5d4 100644 --- a/qapi/cxl.json +++ b/qapi/cxl.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = CXL devices +# *********** +# CXL devices +# *********** ## ## diff --git a/qapi/dump.json b/qapi/dump.json index d0ba1f0..32c8c1f 100644 --- a/qapi/dump.json +++ b/qapi/dump.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = Dump guest memory +# ***************** +# Dump guest memory +# ***************** ## ## @@ -110,7 +112,7 @@ # # Describe the status of a long-running background guest memory dump. # -# @none: no dump-guest-memory has started yet. +# @none: no `dump-guest-memory` has started yet. # # @active: there is one dump running in background. # @@ -126,9 +128,9 @@ ## # @DumpQueryResult: # -# The result format for 'query-dump'. +# The result format for `query-dump`. # -# @status: enum of @DumpStatus, which shows current dump status +# @status: enum of `DumpStatus`, which shows current dump status # # @completed: bytes written in latest dump (uncompressed) # @@ -146,7 +148,7 @@ # # Query latest dump status. # -# Returns: A @DumpStatus object showing the dump status. +# Returns: An object showing the dump status. # # Since: 2.6 # @@ -184,7 +186,7 @@ ## # @DumpGuestMemoryCapability: # -# @formats: the available formats for dump-guest-memory +# @formats: the available formats for `dump-guest-memory` # # Since: 2.0 ## @@ -195,10 +197,9 @@ ## # @query-dump-guest-memory-capability: # -# Return the available formats for dump-guest-memory +# Return the available formats for `dump-guest-memory` # -# Returns: A @DumpGuestMemoryCapability object listing available -# formats for dump-guest-memory +# Returns: An object listing available formats for `dump-guest-memory` # # Since: 2.0 # diff --git a/qapi/ebpf.json b/qapi/ebpf.json index db19ae8..f025795 100644 --- a/qapi/ebpf.json +++ b/qapi/ebpf.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = eBPF Objects +# ************ +# eBPF Objects +# ************ # # eBPF object is an ELF binary that contains the eBPF program and eBPF # map description(BTF). Overall, eBPF object should contain the @@ -32,7 +34,7 @@ ## # @EbpfProgramID: # -# The eBPF programs that can be gotten with request-ebpf. +# The eBPF programs that can be gotten with `request-ebpf`. # # @rss: Receive side scaling, technology that allows steering traffic # between queues by calculation hash. Users may set up diff --git a/qapi/error.json b/qapi/error.json index 135c1e8..54cb02f 100644 --- a/qapi/error.json +++ b/qapi/error.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = QMP errors +# ********** +# QMP errors +# ********** ## ## diff --git a/qapi/introspect.json b/qapi/introspect.json index e9e0297..5310071 100644 --- a/qapi/introspect.json +++ b/qapi/introspect.json @@ -10,17 +10,19 @@ # See the COPYING file in the top-level directory. ## -# = QMP introspection +# ***************** +# QMP introspection +# ***************** ## ## # @query-qmp-schema: # -# Command query-qmp-schema exposes the QMP wire ABI as an array of -# SchemaInfo. This lets QMP clients figure out what commands and +# Command `query-qmp-schema` exposes the QMP wire ABI as an array of +# `SchemaInfo`. This lets QMP clients figure out what commands and # events are available in this QEMU, and their parameters and results. # -# However, the SchemaInfo can't reflect all the rules and restrictions +# However, the `SchemaInfo` can't reflect all the rules and restrictions # that apply to QMP. It's interface introspection (figuring out # what's there), not interface specification. The specification is in # the QAPI schema. @@ -34,10 +36,10 @@ # string into a specific enum or from one specific type into an # alternate that includes the original type alongside something else. # -# Returns: array of @SchemaInfo, where each element describes an -# entity in the ABI: command, event, type, ... +# Returns: an array where each element describes an entity in the ABI: +# command, event, type, ... # -# The order of the various SchemaInfo is unspecified; however, all +# The order of the various elements is unspecified; however, all # names are guaranteed to be unique (no name will be duplicated # with different meta-types). # @@ -54,7 +56,7 @@ ## # @SchemaMetaType: # -# This is a @SchemaInfo's meta type, i.e. the kind of entity it +# This is a `SchemaInfo`'s meta type, i.e. the kind of entity it # describes. # # @builtin: a predefined type such as 'int' or 'bool'. @@ -80,7 +82,7 @@ ## # @SchemaInfo: # -# @name: the entity's name, inherited from @base. The SchemaInfo is +# @name: the entity's name, inherited from @base. The `SchemaInfo` is # always referenced by this name. Commands and events have the # name defined in the QAPI schema. Unlike command and event # names, type names are not part of the wire ABI. Consequently, @@ -111,7 +113,7 @@ ## # @SchemaInfoBuiltin: # -# Additional SchemaInfo members for meta-type 'builtin'. +# Additional `SchemaInfo` members for meta-type 'builtin'. # # @json-type: the JSON type used for this type on the wire. # @@ -152,7 +154,7 @@ ## # @SchemaInfoEnum: # -# Additional SchemaInfo members for meta-type 'enum'. +# Additional `SchemaInfo` members for meta-type 'enum'. # # @members: the enum type's members, in no particular order. # (since 6.2) @@ -192,7 +194,7 @@ ## # @SchemaInfoArray: # -# Additional SchemaInfo members for meta-type 'array'. +# Additional `SchemaInfo` members for meta-type 'array'. # # @element-type: the array type's element type. # @@ -206,7 +208,7 @@ ## # @SchemaInfoObject: # -# Additional SchemaInfo members for meta-type 'object'. +# Additional `SchemaInfo` members for meta-type 'object'. # # @members: the object type's (non-variant) members, in no particular # order. @@ -271,7 +273,7 @@ ## # @SchemaInfoAlternate: # -# Additional SchemaInfo members for meta-type 'alternate'. +# Additional `SchemaInfo` members for meta-type 'alternate'. # # @members: the alternate type's members, in no particular order. The # members' wire encoding is distinct, see @@ -299,7 +301,7 @@ ## # @SchemaInfoCommand: # -# Additional SchemaInfo members for meta-type 'command'. +# Additional `SchemaInfo` members for meta-type 'command'. # # @arg-type: the name of the object type that provides the command's # parameters. @@ -321,7 +323,7 @@ ## # @SchemaInfoEvent: # -# Additional SchemaInfo members for meta-type 'event'. +# Additional `SchemaInfo` members for meta-type 'event'. # # @arg-type: the name of the object type that provides the event's # parameters. diff --git a/qapi/job.json b/qapi/job.json index 126fa5c..8b08350 100644 --- a/qapi/job.json +++ b/qapi/job.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = Background jobs +# *************** +# Background jobs +# *************** ## ## @@ -10,26 +12,26 @@ # # Type of a background job. # -# @commit: block commit job type, see "block-commit" +# @commit: block commit job type, see `block-commit` # -# @stream: block stream job type, see "block-stream" +# @stream: block stream job type, see `block-stream` # -# @mirror: drive mirror job type, see "drive-mirror" +# @mirror: drive mirror job type, see `drive-mirror` # -# @backup: drive backup job type, see "drive-backup" +# @backup: drive backup job type, see `drive-backup` # -# @create: image creation job type, see "blockdev-create" (since 3.0) +# @create: image creation job type, see `blockdev-create` (since 3.0) # -# @amend: image options amend job type, see "x-blockdev-amend" +# @amend: image options amend job type, see `x-blockdev-amend` # (since 5.1) # -# @snapshot-load: snapshot load job type, see "snapshot-load" +# @snapshot-load: snapshot load job type, see `snapshot-load` # (since 6.0) # -# @snapshot-save: snapshot save job type, see "snapshot-save" +# @snapshot-save: snapshot save job type, see `snapshot-save` # (since 6.0) # -# @snapshot-delete: snapshot delete job type, see "snapshot-delete" +# @snapshot-delete: snapshot delete job type, see `snapshot-delete` # (since 6.0) # # Since: 1.7 @@ -65,7 +67,7 @@ # # @pending: The job has finished its work, but has finalization steps # that it needs to make prior to completing. These changes will -# require manual intervention via @job-finalize if auto-finalize +# require manual intervention via `job-finalize` if auto-finalize # was set to false. These pending changes may still fail. # # @aborting: The job is in the process of being aborted, and will @@ -75,7 +77,7 @@ # # @concluded: The job has finished all work. If auto-dismiss was set # to false, the job will remain in this state until it is -# dismissed via @job-dismiss. +# dismissed via `job-dismiss`. # # @null: The job is in the process of being dismantled. This state # should not ever be visible externally. @@ -91,21 +93,21 @@ # # Represents command verbs that can be applied to a job. # -# @cancel: see @job-cancel +# @cancel: see `job-cancel` # -# @pause: see @job-pause +# @pause: see `job-pause` # -# @resume: see @job-resume +# @resume: see `job-resume` # -# @set-speed: see @block-job-set-speed +# @set-speed: see `block-job-set-speed` # -# @complete: see @job-complete +# @complete: see `job-complete` # -# @dismiss: see @job-dismiss +# @dismiss: see `job-dismiss` # -# @finalize: see @job-finalize +# @finalize: see `job-finalize` # -# @change: see @block-job-change (since 8.2) +# @change: see `block-job-change` (since 8.2) # # Since: 2.12 ## @@ -138,7 +140,7 @@ # # The job will pause as soon as possible, which means transitioning # into the PAUSED state if it was RUNNING, or into STANDBY if it was -# READY. The corresponding JOB_STATUS_CHANGE event will be emitted. +# READY. The corresponding `JOB_STATUS_CHANGE` event will be emitted. # # Cancelling a paused job automatically resumes it. # @@ -173,7 +175,7 @@ # cancellation. # # The job will cancel as soon as possible and then emit a -# JOB_STATUS_CHANGE event. Usually, the status will change to +# `JOB_STATUS_CHANGE` event. Usually, the status will change to # ABORTING, but it is possible that a job successfully completes (e.g. # because it was almost done and there was no opportunity to cancel # earlier than completing the job) and transitions to PENDING instead. @@ -192,14 +194,14 @@ # # This is supported only for drive mirroring, where it also switches # the device to write to the target path only. Note that drive -# mirroring includes drive-mirror, blockdev-mirror and block-commit +# mirroring includes `drive-mirror`, `blockdev-mirror` and `block-commit` # job (only in case of "active commit", when the node being commited # is used by the guest). The ability to complete is signaled with a -# BLOCK_JOB_READY event. +# `BLOCK_JOB_READY` event. # # This command completes an active background block operation # synchronously. The ordering of this command's return with the -# BLOCK_JOB_COMPLETED event is not defined. Note that if an I/O error +# `BLOCK_JOB_COMPLETED` event is not defined. Note that if an I/O error # occurs during the processing of this command: 1) the command itself # will fail; 2) the error will be processed according to the # rerror/werror arguments that were specified when starting the @@ -217,14 +219,14 @@ # Deletes a job that is in the CONCLUDED state. This command only # needs to be run explicitly for jobs that don't have automatic # dismiss enabled. In turn, automatic dismiss may be enabled only -# for jobs that have @auto-dismiss option, which are drive-backup, -# blockdev-backup, drive-mirror, blockdev-mirror, block-commit and -# block-stream. @auto-dismiss is enabled by default for these +# for jobs that have @auto-dismiss option, which are `drive-backup`, +# `blockdev-backup`, `drive-mirror`, `blockdev-mirror`, `block-commit` and +# `block-stream`. @auto-dismiss is enabled by default for these # jobs. # # This command will refuse to operate on any job that has not yet # reached its terminal state, CONCLUDED. For jobs that make use of -# the JOB_READY event, job-cancel or job-complete will still need to +# the JOB_READY event, `job-cancel` or `job-complete` will still need to # be used as appropriate. # # @id: The job identifier. @@ -295,7 +297,7 @@ # # Return information about jobs. # -# Returns: a list with a @JobInfo for each active job +# Returns: a list with info for each active job # # Since: 3.0 ## diff --git a/qapi/machine-common.json b/qapi/machine-common.json index 298e51f..ed3d20a 100644 --- a/qapi/machine-common.json +++ b/qapi/machine-common.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = Common machine types +# ******************** +# Common machine types +# ******************** ## ## @@ -26,29 +28,29 @@ # # @thread: thread level, which would also be called SMT level or # logical processor level. The @threads option in -# SMPConfiguration is used to configure the topology of this +# `SMPConfiguration` is used to configure the topology of this # level. # -# @core: core level. The @cores option in SMPConfiguration is used +# @core: core level. The @cores option in `SMPConfiguration` is used # to configure the topology of this level. # -# @module: module level. The @modules option in SMPConfiguration is +# @module: module level. The @modules option in `SMPConfiguration` is # used to configure the topology of this level. # -# @cluster: cluster level. The @clusters option in SMPConfiguration +# @cluster: cluster level. The @clusters option in `SMPConfiguration` # is used to configure the topology of this level. # -# @die: die level. The @dies option in SMPConfiguration is used to +# @die: die level. The @dies option in `SMPConfiguration` is used to # configure the topology of this level. # # @socket: socket level, which would also be called package level. -# The @sockets option in SMPConfiguration is used to configure +# The @sockets option in `SMPConfiguration` is used to configure # the topology of this level. # -# @book: book level. The @books option in SMPConfiguration is used +# @book: book level. The @books option in `SMPConfiguration` is used # to configure the topology of this level. # -# @drawer: drawer level. The @drawers option in SMPConfiguration is +# @drawer: drawer level. The @drawers option in `SMPConfiguration` is # used to configure the topology of this level. # # @default: default level. Some architectures will have default @@ -102,9 +104,9 @@ ## # @SmpCachePropertiesWrapper: # -# List wrapper of SmpCacheProperties. +# List wrapper of `SmpCacheProperties`. # -# @caches: the list of SmpCacheProperties. +# @caches: the list of `SmpCacheProperties`. # # Since 9.2 ## diff --git a/qapi/machine.json b/qapi/machine.json index f712e7d..6f59f70 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = Machines +# ******** +# Machines +# ******** ## { 'include': 'common.json' } @@ -76,6 +78,8 @@ # # @cpu-index: index of the virtual CPU # +# @qom-type: QOM type name of the CPU (since 10.1) +# # @qom-path: path to the CPU object in the QOM tree # # @thread-id: ID of the underlying host thread @@ -89,6 +93,7 @@ ## { 'union' : 'CpuInfoFast', 'base' : { 'cpu-index' : 'int', + 'qom-type' : 'str', 'qom-path' : 'str', 'thread-id' : 'int', '*props' : 'CpuInstanceProperties', @@ -101,8 +106,6 @@ # # Return information about all virtual CPUs. # -# Returns: list of @CpuInfoFast -# # Since: 2.12 # # .. qmp-example:: @@ -188,7 +191,7 @@ # @acpi: machine type supports ACPI (since 8.0) # # @compat-props: The machine type's compatibility properties. Only -# present when query-machines argument @compat-props is true. +# present when `query-machines` argument @compat-props is true. # (since 9.1) # # Features: @@ -218,8 +221,6 @@ # # @unstable: Argument @compat-props is experimental. # -# Returns: a list of MachineInfo -# # Since: 1.2 # # .. qmp-example:: @@ -268,8 +269,6 @@ # # Return information on the current virtual machine. # -# Returns: CurrentMachineParams -# # Since: 4.0 ## { 'command': 'query-current-machine', 'returns': 'CurrentMachineParams' } @@ -291,8 +290,6 @@ # # Return information about the target for this QEMU # -# Returns: QemuTargetInfo -# # Since: 1.2 ## { 'command': 'query-target', 'returns': 'QemuTargetInfo' } @@ -316,8 +313,6 @@ # # Query the guest UUID information. # -# Returns: The @UuidInfo for the guest -# # Since: 0.14 # # .. qmp-example:: @@ -385,7 +380,7 @@ # # Wake up guest from suspend. If the guest has wake-up from suspend # support enabled (wakeup-suspend-support flag from -# query-current-machine), wake-up guest from suspend if the guest is +# `query-current-machine`), wake-up guest from suspend if the guest is # in SUSPENDED state. Return an error otherwise. # # Since: 1.1 @@ -452,35 +447,6 @@ { 'command': 'inject-nmi' } ## -# @KvmInfo: -# -# Information about support for KVM acceleration -# -# @enabled: true if KVM acceleration is active -# -# @present: true if KVM acceleration is built into this executable -# -# Since: 0.14 -## -{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} } - -## -# @query-kvm: -# -# Return information about KVM acceleration -# -# Returns: @KvmInfo -# -# Since: 0.14 -# -# .. qmp-example:: -# -# -> { "execute": "query-kvm" } -# <- { "return": { "enabled": true, "present": true } } -## -{ 'command': 'query-kvm', 'returns': 'KvmInfo' } - -## # @NumaOptionsType: # # @node: NUMA nodes configuration @@ -599,7 +565,7 @@ # # List of CXL Fixed Memory Windows. # -# @cxl-fmw: List of CXLFixedMemoryWindowOptions +# @cxl-fmw: List of `CXLFixedMemoryWindowOptions` # # Since: 7.1 ## @@ -654,10 +620,10 @@ ## # @NumaCpuOptions: # -# Option "-numa cpu" overrides default cpu to node mapping. It -# accepts the same set of cpu properties as returned by -# query-hotpluggable-cpus[].props, where node-id could be used to -# override default node mapping. +# Option "-numa cpu" overrides default cpu to node mapping. It accepts +# the same set of cpu properties as returned by +# `query-hotpluggable-cpus[].props <query-hotpluggable-cpus>`, where +# node-id could be used to override default node mapping. # # Since: 2.10 ## @@ -671,7 +637,7 @@ # The memory hierarchy in the System Locality Latency and Bandwidth # Information Structure of HMAT (Heterogeneous Memory Attribute Table) # -# For more information about @HmatLBMemoryHierarchy, see chapter +# For more information about `HmatLBMemoryHierarchy`, see chapter # 5.2.27.4: Table 5-146: Field "Flags" of ACPI 6.3 spec. # # @memory: the structure represents the memory performance @@ -693,7 +659,7 @@ # Data type in the System Locality Latency and Bandwidth Information # Structure of HMAT (Heterogeneous Memory Attribute Table) # -# For more information about @HmatLBDataType, see chapter 5.2.27.4: +# For more information about `HmatLBDataType`, see chapter 5.2.27.4: # Table 5-146: Field "Data Type" of ACPI 6.3 spec. # # @access-latency: access latency (nanoseconds) @@ -720,7 +686,7 @@ # Set the system locality latency and bandwidth information between # Initiator and Target proximity Domains. # -# For more information about @NumaHmatLBOptions, see chapter 5.2.27.4: +# For more information about `NumaHmatLBOptions`, see chapter 5.2.27.4: # Table 5-146 of ACPI 6.3 spec. # # @initiator: the Initiator Proximity Domain. @@ -756,7 +722,7 @@ # Cache associativity in the Memory Side Cache Information Structure # of HMAT # -# For more information of @HmatCacheAssociativity, see chapter +# For more information of `HmatCacheAssociativity`, see chapter # 5.2.27.5: Table 5-147 of ACPI 6.3 spec. # # @none: None (no memory side cache in this proximity domain, or cache @@ -777,7 +743,7 @@ # Cache write policy in the Memory Side Cache Information Structure of # HMAT # -# For more information of @HmatCacheWritePolicy, see chapter 5.2.27.5: +# For more information of `HmatCacheWritePolicy`, see chapter 5.2.27.5: # Table 5-147: Field "Cache Attributes" of ACPI 6.3 spec. # # @none: None (no memory side cache in this proximity domain, or cache @@ -797,7 +763,7 @@ # # Set the memory side cache information for a given memory domain. # -# For more information of @NumaHmatCacheOptions, see chapter 5.2.27.5: +# For more information of `NumaHmatCacheOptions`, see chapter 5.2.27.5: # Table 5-147: Field "Cache Attributes" of ACPI 6.3 spec. # # @node-id: the memory proximity domain to which the memory belongs. @@ -932,8 +898,6 @@ # # Return information for all memory backends. # -# Returns: a list of @Memdev. -# # Since: 2.1 # # .. qmp-example:: @@ -976,7 +940,7 @@ # # The ids other than the node-id specify the position of the CPU # within the CPU topology (as defined by the machine property "smp", -# thus see also type @SMPConfiguration) +# thus see also type `SMPConfiguration`) # # @node-id: NUMA node ID the CPU belongs to # @@ -1004,7 +968,7 @@ # Since: 2.7 ## { 'struct': 'CpuInstanceProperties', - # Keep these in sync with the properties device_add accepts + # Keep these in sync with the properties `device_add` accepts 'data': { '*node-id': 'int', '*drawer-id': 'int', '*book-id': 'int', @@ -1020,19 +984,19 @@ ## # @HotpluggableCPU: # -# @type: CPU object type for usage with device_add command +# @type: CPU object type for usage with `device_add` command # # @props: list of properties to pass for hotplugging a CPU with -# device_add +# `device_add` # -# @vcpus-count: number of logical VCPU threads @HotpluggableCPU +# @vcpus-count: number of logical VCPU threads `HotpluggableCPU` # provides # # @qom-path: link to existing CPU object if CPU is present or omitted # if CPU is not present. # # .. note:: Management should be prepared to pass through additional -# properties with device_add. +# properties with `device_add`. # # Since: 2.7 ## @@ -1049,8 +1013,6 @@ # # TODO: Better documentation; currently there is none. # -# Returns: a list of HotpluggableCPU objects. -# # Since: 2.7 # # .. qmp-example:: @@ -1172,9 +1134,6 @@ # # Return information about the balloon device. # -# Returns: -# @BalloonInfo -# # Errors: # - If the balloon driver is enabled but not functional because # the KVM kernel module cannot support it, KVMMissingCap @@ -1196,7 +1155,7 @@ # @BALLOON_CHANGE: # # Emitted when the guest changes the actual BALLOON level. This value -# is equivalent to the @actual field return by the 'query-balloon' +# is equivalent to the @actual field return by the `query-balloon` # command # # @actual: the logical size of the VM in bytes. Formula used: @@ -1238,9 +1197,6 @@ # Return the hv-balloon driver data contained in the last received # "STATUS" message from the guest. # -# Returns: -# @HvBalloonInfo -# # Errors: # - If no hv-balloon device is present, guest memory status # reporting is not enabled or no guest memory status report @@ -1301,6 +1257,8 @@ # Return the amount of initially allocated and present hotpluggable # (if enabled) memory in bytes. # +# TODO: This line is a hack to separate the example from the body +# # .. qmp-example:: # # -> { "execute": "query-memory-size-summary" } @@ -1983,7 +1941,7 @@ # # The result of a CPU model baseline. # -# @model: the baselined CpuModelInfo. +# @model: the baselined `CpuModelInfo`. # # Since: 2.8 ## @@ -2032,28 +1990,28 @@ # # * QEMU version: CPU models may look different depending on the QEMU # version. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine-type: CPU model may look different depending on the # machine-type. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine options (including accelerator): in some architectures, # CPU models may look different depending on machine and accelerator # options. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * "-cpu" arguments and global properties: arguments to the -cpu # option and global properties may affect expansion of CPU models. -# Using query-cpu-model-expansion while using these is not advised. +# Using `query-cpu-model-expansion` while using these is not advised. # # Some architectures may not support comparing CPU models. s390x # supports comparing CPU models. # # @modela: description of the first CPU model to compare, referred to -# as "model A" in CpuModelCompareResult +# as "model A" in `CpuModelCompareResult` # # @modelb: description of the second CPU model to compare, referred to -# as "model B" in CpuModelCompareResult +# as "model B" in `CpuModelCompareResult` # -# Returns: a CpuModelCompareInfo describing how both CPU models +# Returns: a `CpuModelCompareInfo` describing how both CPU models # compare # # Errors: @@ -2086,17 +2044,17 @@ # # * QEMU version: CPU models may look different depending on the QEMU # version. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine-type: CPU model may look different depending on the # machine-type. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine options (including accelerator): in some architectures, # CPU models may look different depending on machine and accelerator # options. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * "-cpu" arguments and global properties: arguments to the -cpu # option and global properties may affect expansion of CPU models. -# Using query-cpu-model-expansion while using these is not advised. +# Using `query-cpu-model-expansion` while using these is not advised. # # Some architectures may not support baselining CPU models. s390x # supports baselining CPU models. @@ -2105,7 +2063,7 @@ # # @modelb: description of the second CPU model to baseline # -# Returns: a CpuModelBaselineInfo describing the baselined CPU model +# Returns: a `CpuModelBaselineInfo` describing the baselined CPU model # # Errors: # - if baselining CPU models is not supported by the target @@ -2125,7 +2083,7 @@ # # The result of a cpu model expansion. # -# @model: the expanded CpuModelInfo. +# @model: the expanded `CpuModelInfo`. # # @deprecated-props: an optional list of properties that are flagged as # deprecated by the CPU vendor. The list depends on the @@ -2154,17 +2112,17 @@ # # * QEMU version: CPU models may look different depending on the QEMU # version. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine-type: CPU model may look different depending on the # machine-type. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * machine options (including accelerator): in some architectures, # CPU models may look different depending on machine and accelerator # options. (Except for CPU models reported as "static" in -# query-cpu-definitions.) +# `query-cpu-definitions`.) # * "-cpu" arguments and global properties: arguments to the -cpu # option and global properties may affect expansion of CPU models. -# Using query-cpu-model-expansion while using these is not advised. +# Using `query-cpu-model-expansion` while using these is not advised. # # Some architectures may not support all expansion types. s390x # supports "full" and "static". Arm only supports "full". @@ -2173,7 +2131,7 @@ # # @type: expansion type, specifying how to expand the CPU model # -# Returns: a CpuModelExpansionInfo describing the expanded CPU model +# Returns: a `CpuModelExpansionInfo` describing the expanded CPU model # # Errors: # - if expanding CPU models is not supported @@ -2212,7 +2170,7 @@ # from running in the current host. (since 2.8) # # @typename: Type name that can be used as argument to -# @device-list-properties, to introspect properties configurable +# `device-list-properties`, to introspect properties configurable # using -cpu or -global. (since 2.9) # # @alias-of: Name of CPU model this model is an alias for. The target @@ -2256,8 +2214,6 @@ # # Return a list of supported virtual CPU definitions # -# Returns: a list of CpuDefinitionInfo -# # Since: 1.2 ## { 'command': 'query-cpu-definitions', 'returns': ['CpuDefinitionInfo'] } diff --git a/qapi/meson.build b/qapi/meson.build index 3b035ae..ca6b61a 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -57,6 +57,7 @@ qapi_all_modules = [ ] if have_system qapi_all_modules += [ + 'accelerator', 'acpi', 'audio', 'cryptodev', diff --git a/qapi/migration.json b/qapi/migration.json index 2d39a8f..e08a99b 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -3,7 +3,9 @@ # ## -# = Migration +# ********* +# Migration +# ********* ## { 'include': 'common.json' } @@ -193,14 +195,14 @@ # # Information about current migration process. # -# @status: @MigrationStatus describing the current migration status. +# @status: `MigrationStatus` describing the current migration status. # If this field is not returned, no migration process has been # initiated # -# @ram: @MigrationStats containing detailed migration status, only +# @ram: `MigrationStats` containing detailed migration status, only # returned if status is 'active' or 'completed'(since 1.2) # -# @xbzrle-cache: @XBZRLECacheStats containing detailed XBZRLE +# @xbzrle-cache: `XBZRLECacheStats` containing detailed XBZRLE # migration statistics, only returned if XBZRLE feature is on and # status is 'active' or 'completed' (since 1.2) # @@ -264,7 +266,7 @@ # @socket-address: Only used for tcp, to know what the real port is # (Since 4.0) # -# @vfio: @VfioStats containing detailed VFIO devices migration +# @vfio: `VfioStats` containing detailed VFIO devices migration # statistics, only returned if VFIO device is present, migration # is supported by all VFIO devices and status is 'active' or # 'completed' (since 5.2) @@ -275,7 +277,7 @@ # # @dirty-limit-throttle-time-per-round: Maximum throttle time (in # microseconds) of virtual CPUs each dirty ring full round, which -# shows how MigrationCapability dirty-limit affects the guest +# shows how `MigrationCapability` dirty-limit affects the guest # during live migration. (Since 8.1) # # @dirty-limit-ring-full-time: Estimated average dirty ring full time @@ -324,8 +326,6 @@ # is active there will be another json-object with RAM migration # status. # -# Returns: @MigrationInfo -# # Since: 0.14 # # .. qmp-example:: @@ -575,8 +575,6 @@ # # Return information about the current migration capabilities status # -# Returns: @MigrationCapabilityStatus -# # Since: 1.2 # # .. qmp-example:: @@ -629,7 +627,7 @@ # # @normal: the original form of migration. (since 8.2) # -# @cpr-reboot: The migrate command stops the VM and saves state to the +# @cpr-reboot: The `migrate` command stops the VM and saves state to the # URI. After quitting QEMU, the user resumes by running QEMU # -incoming. # @@ -679,7 +677,7 @@ # # New QEMU reads the CPR channel before opening a monitor, hence # the CPR channel cannot be specified in the list of channels for -# a migrate-incoming command. It may only be specified on the +# a `migrate-incoming` command. It may only be specified on the # command line. # # The main channel address cannot be a file type, and for an @@ -690,10 +688,10 @@ # memory-backend-epc is not supported. The VM must be started # with the '-machine aux-ram-share=on' option. # -# When using -incoming defer, you must issue the migrate command +# When using -incoming defer, you must issue the `migrate` command # to old QEMU before issuing any monitor commands to new QEMU. # However, new QEMU does not open and read the migration stream -# until you issue the migrate incoming command. +# until you issue the `migrate-incoming` command. # # (since 10.0) ## @@ -915,11 +913,11 @@ # @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration. # Defaults to 1. (Since 8.1) # -# @mode: Migration mode. See description in @MigMode. Default is +# @mode: Migration mode. See description in `MigMode`. Default is # 'normal'. (Since 8.2) # # @zero-page-detection: Whether and how to detect zero pages. -# See description in @ZeroPageDetection. Default is 'multifd'. +# See description in `ZeroPageDetection`. Default is 'multifd'. # (since 9.0) # # @direct-io: Open migration files with O_DIRECT when possible. This @@ -1096,11 +1094,11 @@ # @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration. # Defaults to 1. (Since 8.1) # -# @mode: Migration mode. See description in @MigMode. Default is +# @mode: Migration mode. See description in `MigMode`. Default is # 'normal'. (Since 8.2) # # @zero-page-detection: Whether and how to detect zero pages. -# See description in @ZeroPageDetection. Default is 'multifd'. +# See description in `ZeroPageDetection`. Default is 'multifd'. # (since 9.0) # # @direct-io: Open migration files with O_DIRECT when possible. This @@ -1112,8 +1110,8 @@ # @unstable: Members @x-checkpoint-delay and # @x-vcpu-dirty-limit-period are experimental. # -# TODO: either fuse back into MigrationParameters, or make -# MigrationParameters members mandatory +# TODO: either fuse back into `MigrationParameters`, or make +# `MigrationParameters` members mandatory # # Since: 2.4 ## @@ -1306,11 +1304,11 @@ # @vcpu-dirty-limit: Dirtyrate limit (MB/s) during live migration. # Defaults to 1. (Since 8.1) # -# @mode: Migration mode. See description in @MigMode. Default is +# @mode: Migration mode. See description in `MigMode`. Default is # 'normal'. (Since 8.2) # # @zero-page-detection: Whether and how to detect zero pages. -# See description in @ZeroPageDetection. Default is 'multifd'. +# See description in `ZeroPageDetection`. Default is 'multifd'. # (since 9.0) # # @direct-io: Open migration files with O_DIRECT when possible. This @@ -1362,8 +1360,6 @@ # # Return information about the current migration parameters # -# Returns: @MigrationParameters -# # Since: 2.4 # # .. qmp-example:: @@ -1402,7 +1398,7 @@ # # Emitted when a migration event happens # -# @status: @MigrationStatus describing the current migration status. +# @status: `MigrationStatus` describing the current migration status. # # Since: 2.4 # @@ -1523,8 +1519,8 @@ # The reason for a COLO exit. # # @none: failover has never happened. This state does not occur in -# the COLO_EXIT event, and is only visible in the result of -# query-colo-status. +# the `COLO_EXIT` event, and is only visible in the result of +# `query-colo-status`. # # @request: COLO exit is due to an external request. # @@ -1779,8 +1775,8 @@ # list connected to a destination interface endpoint. # # @exit-on-error: Exit on incoming migration failure. Default true. -# When set to false, the failure triggers a MIGRATION event, and -# error details could be retrieved with query-migrate. +# When set to false, the failure triggers a :qapi:event:`MIGRATION` +# event, and error details could be retrieved with `query-migrate`. # (since 9.1) # # Since: 2.3 @@ -1792,7 +1788,7 @@ # already exposed above libvirt. # # 2. QEMU must be started with -incoming defer to allow -# migrate-incoming to be used. +# `migrate-incoming` to be used. # # 3. The uri format is the same as for -incoming # @@ -1845,7 +1841,7 @@ # devices of the VM are not saved by this command. # # @filename: the file to save the state of the devices to as binary -# data. See xen-save-devices-state.txt for a description of the +# data. See `xen-save-devices-state`.txt for a description of the # binary format. # # @live: Optional argument to ask QEMU to treat this command as part @@ -1886,7 +1882,7 @@ # devices of the VM are not loaded by this command. # # @filename: the file to load the state of the devices from as binary -# data. See xen-save-devices-state.txt for a description of the +# data. See `xen-save-devices-state`.txt for a description of the # binary format. # # Since: 2.7 @@ -1926,7 +1922,7 @@ ## # @ReplicationStatus: # -# The result format for 'query-xen-replication-status'. +# The result format for `query-xen-replication-status`. # # @error: true if an error happened, false if replication is normal. # @@ -1944,7 +1940,7 @@ # # Query replication status while the vm is running. # -# Returns: A @ReplicationStatus object showing the status. +# TODO: This line is a hack to separate the example from the body # # .. qmp-example:: # @@ -1975,7 +1971,7 @@ ## # @COLOStatus: # -# The result format for 'query-colo-status'. +# The result format for `query-colo-status`. # # @mode: COLO running mode. If COLO is running, this field will # return 'primary' or 'secondary'. @@ -1998,7 +1994,7 @@ # # Query COLO status while the vm is running. # -# Returns: A @COLOStatus object showing the status. +# TODO: This line is a hack to separate the example from the body # # .. qmp-example:: # @@ -2099,7 +2095,7 @@ # @DirtyRateMeasureMode: # # Method used to measure dirty page rate. Differences between -# available methods are explained in @calc-dirty-rate. +# available methods are explained in `calc-dirty-rate`. # # @page-sampling: use page sampling # @@ -2167,7 +2163,7 @@ # @calc-dirty-rate: # # Start measuring dirty page rate of the VM. Results can be retrieved -# with @query-dirty-rate after measurements are completed. +# with `query-dirty-rate` after measurements are completed. # # Dirty page rate is the number of pages changed in a given time # period expressed in MiB/s. The following methods of calculation are @@ -2240,7 +2236,7 @@ ## # @query-dirty-rate: # -# Query results of the most recent invocation of @calc-dirty-rate. +# Query results of the most recent invocation of `calc-dirty-rate`. # # @calc-time-unit: time unit in which to report calculation time. # By default it is reported in seconds. (Since 8.2) @@ -2290,7 +2286,7 @@ # # Requires KVM with accelerator property "dirty-ring-size" set. A # virtual CPU's dirty page rate is a measure of its memory load. To -# observe dirty page rates, use @calc-dirty-rate. +# observe dirty page rates, use `calc-dirty-rate`. # # @cpu-index: index of a virtual CPU, default is all. # @@ -2315,8 +2311,8 @@ # Cancel the upper limit of dirty page rate for virtual CPUs. # # Cancel the dirty page limit for the vCPU which has been set with -# set-vcpu-dirty-limit command. Note that this command requires -# support from dirty ring, same as the "set-vcpu-dirty-limit". +# `set-vcpu-dirty-limit` command. Note that this command requires +# support from dirty ring, same as the `set-vcpu-dirty-limit`. # # @cpu-index: index of a virtual CPU, default is all. # @@ -2373,8 +2369,6 @@ # # @deprecated: This command is deprecated with no replacement yet. # -# Returns: @MigrationThreadInfo -# # Since: 7.2 ## { 'command': 'query-migrationthreads', @@ -2475,7 +2469,7 @@ # time it takes to load the snapshot. # # It is strongly recommended that @devices contain all writable block -# device nodes that can have changed since the original @snapshot-save +# device nodes that can have changed since the original `snapshot-save` # command execution. # # .. qmp-example:: diff --git a/qapi/misc-arm.json b/qapi/misc-arm.json index f534137..f921d74 100644 --- a/qapi/misc-arm.json +++ b/qapi/misc-arm.json @@ -30,14 +30,12 @@ ## # @query-gic-capabilities: # -# It will return a list of GICCapability objects that describe its +# It will return a list of `GICCapability` objects that describe its # capability bits. # # On non-ARM targets this command will report an error as the GIC # technology is not applicable. # -# Returns: a list of GICCapability objects. -# # Since: 2.6 # # .. qmp-example:: diff --git a/qapi/misc-i386.json b/qapi/misc-i386.json index 5fefa0a..c8c91a2 100644 --- a/qapi/misc-i386.json +++ b/qapi/misc-i386.json @@ -6,9 +6,9 @@ ## # @rtc-reset-reinjection: # -# This command will reset the RTC interrupt reinjection backlog. Can -# be used if another mechanism to synchronize guest time is in effect, -# for example QEMU guest agent's guest-set-time command. +# Reset the RTC interrupt reinjection backlog. Can be used if another +# mechanism to synchronize guest time is in effect, for example QEMU +# guest agent's guest-set-time command. # # Use of this command is only applicable for x86 machines with an RTC, # and on other machines will silently return without performing any @@ -26,7 +26,7 @@ ## # @SevState: # -# An enumeration of SEV state information used during @query-sev. +# An enumeration of SEV state information used during `query-sev`. # # @uninit: The guest is uninitialized. # @@ -132,8 +132,6 @@ # @enabled field is set to 'false' and the state of all other fields # is unspecified. # -# Returns: @SevInfo -# # Since: 2.12 # # .. qmp-example:: @@ -165,7 +163,7 @@ # 'sev-guest' confidential virtualization object. The launch # measurement for SEV-SNP guests is only available within the guest. # -# Returns: The @SevLaunchMeasureInfo for the guest +# Returns: The guest's SEV guest launch measurement info # # Errors: # - If the launch measurement is unavailable, either due to an @@ -214,8 +212,6 @@ # # This is only supported on AMD X86 platforms with KVM enabled. # -# Returns: SevCapability objects. -# # Errors: # - If SEV is not available on the platform, GenericError # @@ -233,8 +229,7 @@ ## # @sev-inject-launch-secret: # -# This command injects a secret blob into memory of a SEV/SEV-ES -# guest. +# Inject a secret blob into a SEV/SEV-ES guest's memory. # # This is only valid on x86 machines configured with KVM and the # 'sev-guest' confidential virtualization object. SEV-SNP guests do @@ -272,7 +267,7 @@ ## # @query-sev-attestation-report: # -# This command is used to get the SEV attestation report. +# Get the SEV attestation report. # # This is only valid on x86 machines configured with KVM and the # 'sev-guest' confidential virtualization object. The attestation @@ -281,13 +276,10 @@ # @mnonce: a random 16 bytes value encoded in base64 (it will be # included in report) # -# Returns: SevAttestationReport objects. -# # Errors: -# - This will return an error if the attestation report is -# unavailable, either due to an invalid guest configuration -# or if the guest has not reached the required SEV state, -# GenericError +# - If the attestation report is unavailable, either due to an +# invalid guest configuration or because the guest has not +# reached the required SEV state, GenericError # # Since: 6.1 # @@ -345,8 +337,6 @@ # # Return information about configured SGX capabilities of guest # -# Returns: @SgxInfo -# # Since: 6.2 # # .. qmp-example:: @@ -364,8 +354,6 @@ # # Return information about SGX capabilities of host # -# Returns: @SgxInfo -# # Since: 6.2 # # .. qmp-example:: diff --git a/qapi/misc.json b/qapi/misc.json index 4b9e601..28c641f 100644 --- a/qapi/misc.json +++ b/qapi/misc.json @@ -3,7 +3,9 @@ # ## -# = Miscellanea +# *********** +# Miscellanea +# *********** ## { 'include': 'common.json' } @@ -21,7 +23,7 @@ # "@dbus-display" or the name of a character device (e.g. from # -chardev id=XXXX) # -# @fdname: file descriptor name previously passed via 'getfd' command +# @fdname: file descriptor name previously passed via `getfd` command # # @skipauth: whether to skip authentication. Only applies to "vnc" # and "spice" protocols @@ -56,8 +58,6 @@ # # Return the name information of a guest. # -# Returns: @NameInfo of the guest -# # Since: 0.14 # # .. qmp-example:: @@ -107,7 +107,7 @@ # declared using the ``-object iothread`` command-line option. It # is always the main thread of the process. # -# Returns: a list of @IOThreadInfo for each iothread +# Returns: a list of info for each iothread # # Since: 2.0 # @@ -228,7 +228,7 @@ # Known limitations: # # * This command is stateless, this means that commands that depend -# on state information (such as getfd) might not work. +# on state information (such as `getfd`) might not work. # # * Commands that prompt the user for data don't currently work. # @@ -255,7 +255,7 @@ # .. note:: If @fdname already exists, the file descriptor assigned to # it will be closed and replaced by the received file descriptor. # -# The 'closefd' command can be used to explicitly close the file +# The `closefd` command can be used to explicitly close the file # descriptor when it is no longer needed. # # .. qmp-example:: @@ -282,7 +282,7 @@ # .. note:: If @fdname already exists, the file descriptor assigned to # it will be closed and replaced by the received file descriptor. # -# The 'closefd' command can be used to explicitly close the file +# The `closefd` command can be used to explicitly close the file # descriptor when it is no longer needed. # # .. qmp-example:: @@ -332,9 +332,6 @@ # # @opaque: A free-form string that can be used to describe the fd. # -# Returns: -# @AddfdInfo -# # Errors: # - If file descriptor was not received, GenericError # - If @fdset-id is a negative value, GenericError @@ -415,8 +412,6 @@ # # Return information describing all fd sets. # -# Returns: A list of @FdsetInfo -# # Since: 1.2 # # .. note:: The list of fd sets is shared by all monitor connections. @@ -480,7 +475,7 @@ # # @name: parameter name # -# @type: parameter @CommandLineParameterType +# @type: parameter `CommandLineParameterType` # # @help: human readable text string, not suitable for parsing. # @@ -502,7 +497,7 @@ # # @option: option name # -# @parameters: an array of @CommandLineParameterInfo +# @parameters: an array of `CommandLineParameterInfo` # # Since: 1.5 ## @@ -516,8 +511,7 @@ # # @option: option name # -# Returns: list of @CommandLineOptionInfo for all options (or for the -# given @option). +# Returns: list of objects for all options (or for the given @option). # # Errors: # - if the given @option doesn't exist diff --git a/qapi/net.json b/qapi/net.json index 97ea183..78bcc98 100644 --- a/qapi/net.json +++ b/qapi/net.json @@ -3,7 +3,9 @@ # ## -# = Net devices +# *********** +# Net devices +# *********** ## { 'include': 'sockets.json' } @@ -113,6 +115,119 @@ 'str': 'str' } } ## +# @NetDevPasstOptions: +# +# Unprivileged user-mode network connectivity using passt +# +# @path: Filename of the passt program to run (by default 'passt', and use PATH) +# +# @quiet: don't print informational messages (default, passed as '--quiet') +# +# @vhost-user: enable vhost-user +# +# @mtu: assign MTU via DHCP/NDP +# +# @address: IPv4 or IPv6 address +# +# @netmask: IPv4 mask +# +# @mac: source MAC address +# +# @gateway: IPv4 or IPv6 address as gateway +# +# @interface: interface for addresses and routes +# +# @outbound: bind to address as outbound source +# +# @outbound-if4: bind to outbound interface for IPv4 +# +# @outbound-if6: bind to outbound interface for IPv6 +# +# @dns: IPv4 or IPv6 address as DNS +# +# @search: search domains +# +# @fqdn: FQDN to configure client with +# +# @dhcp-dns: enable/disable DNS list in DHCP/DHCPv6/NDP +# +# @dhcp-search: enable/disable list in DHCP/DHCPv6/NDP +# +# @map-host-loopback: addresse to refer to host +# +# @map-guest-addr: addr to translate to guest's address +# +# @dns-forward: forward DNS queries sent to +# +# @dns-host: host nameserver to direct queries to +# +# @tcp: enable/disable TCP +# +# @udp: enable/disable UDP +# +# @icmp: enable/disable ICMP +# +# @dhcp: enable/disable DHCP +# +# @ndp: enable/disable NDP +# +# @dhcpv6: enable/disable DHCPv6 +# +# @ra: enable/disable route advertisements +# +# @freebind: bind to any address for forwarding +# +# @ipv4: enable/disable IPv4 +# +# @ipv6: enable/disable IPv6 +# +# @tcp-ports: TCP ports to forward +# +# @udp-ports: UDP ports to forward +# +# @param: parameter to pass to passt command +# +# Since: 10.1 +## +{ 'struct': 'NetDevPasstOptions', + 'data': { + '*path': 'str', + '*quiet': 'bool', + '*vhost-user': 'bool', + '*mtu': 'int', + '*address': 'str', + '*netmask': 'str', + '*mac': 'str', + '*gateway': 'str', + '*interface': 'str', + '*outbound': 'str', + '*outbound-if4': 'str', + '*outbound-if6': 'str', + '*dns': 'str', + '*search': ['String'], + '*fqdn': 'str', + '*dhcp-dns': 'bool', + '*dhcp-search': 'bool', + '*map-host-loopback': 'str', + '*map-guest-addr': 'str', + '*dns-forward': 'str', + '*dns-host': 'str', + '*tcp': 'bool', + '*udp': 'bool', + '*icmp': 'bool', + '*dhcp': 'bool', + '*ndp': 'bool', + '*dhcpv6': 'bool', + '*ra': 'bool', + '*freebind': 'bool', + '*ipv4': 'bool', + '*ipv6': 'bool', + '*tcp-ports': ['String'], + '*udp-ports': ['String'], + '*param': ['String'] }, + 'if': 'CONFIG_PASST' } + +## # @NetdevUserOptions: # # Use the user mode network stack which requires no administrator @@ -454,25 +569,34 @@ # (default: 0). # # @inhibit: Don't load a default XDP program, use one already loaded -# to the interface (default: false). Requires @sock-fds. +# to the interface (default: false). Requires @sock-fds or @map-path. # # @sock-fds: A colon (:) separated list of file descriptors for # already open but not bound AF_XDP sockets in the queue order. # One fd per queue. These descriptors should already be added -# into XDP socket map for corresponding queues. Requires -# @inhibit. +# into XDP socket map for corresponding queues. @sock-fds and +# @map-path are mutually exclusive. Requires @inhibit. +# +# @map-path: The path to a pinned xsk map to push file descriptors +# for bound AF_XDP sockets into. @map-path and @sock-fds are +# mutually exclusive. Requires @inhibit. (Since 10.1) +# +# @map-start-index: Use @map-path to insert xsk sockets starting from +# this index number (default: 0). Requires @map-path. (Since 10.1) # # Since: 8.2 ## { 'struct': 'NetdevAFXDPOptions', 'data': { - 'ifname': 'str', - '*mode': 'AFXDPMode', - '*force-copy': 'bool', - '*queues': 'int', - '*start-queue': 'int', - '*inhibit': 'bool', - '*sock-fds': 'str' }, + 'ifname': 'str', + '*mode': 'AFXDPMode', + '*force-copy': 'bool', + '*queues': 'int', + '*start-queue': 'int', + '*inhibit': 'bool', + '*sock-fds': 'str', + '*map-path': 'str', + '*map-start-index': 'int32' }, 'if': 'CONFIG_AF_XDP' } ## @@ -655,7 +779,7 @@ # this to zero disables this function. This member is mutually # exclusive with @reconnect. (default: 0) (Since: 9.2) # -# Only SocketAddress types 'unix', 'inet' and 'fd' are supported. +# Only `SocketAddress` types 'unix', 'inet' and 'fd' are supported. # # Features: # @@ -680,7 +804,7 @@ # # @local: local address # -# Only SocketAddress types 'unix', 'inet' and 'fd' are supported. +# Only `SocketAddress` types 'unix', 'inet' and 'fd' are supported. # # If remote address is present and it's a multicast address, local # address is optional. Otherwise local address is required and remote @@ -729,12 +853,15 @@ # # @af-xdp: since 8.2 # +# @passt: since 10.1 +# # Since: 2.7 ## { 'enum': 'NetClientDriver', 'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'stream', 'dgram', 'vde', 'bridge', 'hubport', 'netmap', 'vhost-user', 'vhost-vdpa', + { 'name': 'passt', 'if': 'CONFIG_PASST' }, { 'name': 'af-xdp', 'if': 'CONFIG_AF_XDP' }, { 'name': 'vmnet-host', 'if': 'CONFIG_VMNET' }, { 'name': 'vmnet-shared', 'if': 'CONFIG_VMNET' }, @@ -756,6 +883,8 @@ 'discriminator': 'type', 'data': { 'nic': 'NetLegacyNicOptions', + 'passt': { 'type': 'NetDevPasstOptions', + 'if': 'CONFIG_PASST' }, 'user': 'NetdevUserOptions', 'tap': 'NetdevTapOptions', 'l2tpv3': 'NetdevL2TPv3Options', @@ -845,7 +974,7 @@ # # @name: net client name # -# Returns: list of @RxFilterInfo for all NICs (or for the given NIC). +# Returns: list of info for all NICs (or for the given NIC). # # Errors: # - if the given @name doesn't exist @@ -890,7 +1019,7 @@ ## # @NIC_RX_FILTER_CHANGED: # -# Emitted once until the 'query-rx-filter' command is executed, the +# Emitted once until the `query-rx-filter` command is executed, the # first event will always be emitted # # @name: net client name diff --git a/qapi/pci.json b/qapi/pci.json index dc85a41..694c741 100644 --- a/qapi/pci.json +++ b/qapi/pci.json @@ -6,7 +6,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later ## -# = PCI +# *** +# PCI +# *** ## ## @@ -83,7 +85,7 @@ # # @bus: information about the bus the device resides on # -# @devices: a list of @PciDeviceInfo for each device on this bridge +# @devices: a list of `PciDeviceInfo` for each device on this bridge # # Since: 0.14 ## @@ -175,7 +177,7 @@ # # Return information about the PCI bus topology of the guest. # -# Returns: a list of @PciInfo for each PCI bus. Each bus is +# Returns: a list of info for each PCI bus. Each bus is # represented by a json-object, which has a key with a json-array # of all PCI devices attached to it. Each device is represented # by a json-object. diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index a8f6616..82f111b 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -1,7 +1,9 @@ # -*- Mode: Python -*- # vim: filetype=python ## -# = Introduction +# ************ +# Introduction +# ************ # # This manual describes the commands and events supported by the QEMU # Monitor Protocol (QMP). @@ -37,6 +39,7 @@ { 'include': 'run-state.json' } { 'include': 'crypto.json' } { 'include': 'job.json' } +{ 'include': 'accelerator.json' } { 'include': 'block.json' } { 'include': 'block-export.json' } { 'include': 'char.json' } diff --git a/qapi/qdev.json b/qapi/qdev.json index 32c7d10..e14a0c9 100644 --- a/qapi/qdev.json +++ b/qapi/qdev.json @@ -5,7 +5,9 @@ # See the COPYING file in the top-level directory. ## -# = Device infrastructure (qdev) +# **************************** +# Device infrastructure (qdev) +# **************************** ## { 'include': 'qom.json' } @@ -17,8 +19,7 @@ # # @typename: the type name of a device # -# Returns: a list of ObjectPropertyInfo describing a devices -# properties +# Returns: a list describing a devices properties # # .. note:: Objects can create properties at runtime, for example to # describe links between different devices and/or objects. These @@ -96,10 +97,10 @@ # from the guest. Hot removal is an operation that requires guest # cooperation. This command merely requests that the guest begin # the hot removal process. Completion of the device removal -# process is signaled with a DEVICE_DELETED event. Guest reset +# process is signaled with a `DEVICE_DELETED` event. Guest reset # will automatically complete removal for all devices. If a # guest-side error in the hot removal process is detected, the -# device will not be removed and a DEVICE_UNPLUG_GUEST_ERROR event +# device will not be removed and a `DEVICE_UNPLUG_GUEST_ERROR` event # is sent. Some errors cannot be detected. # # Since: 0.14 diff --git a/qapi/qom.json b/qapi/qom.json index b133b06..96d56df 100644 --- a/qapi/qom.json +++ b/qapi/qom.json @@ -10,7 +10,9 @@ { 'include': 'crypto.json' } ## -# = QEMU Object Model (QOM) +# *********************** +# QEMU Object Model (QOM) +# *********************** ## ## @@ -48,14 +50,12 @@ ## # @qom-list: # -# This command will list any properties of a object given a path in -# the object model. +# List properties of a object given a path in the object model. # -# @path: the path within the object model. See @qom-get for a +# @path: the path within the object model. See `qom-get` for a # description of this parameter. # -# Returns: a list of @ObjectPropertyInfo that describe the properties -# of the object. +# Returns: a list that describe the properties of the object. # # Since: 1.2 # @@ -76,8 +76,7 @@ ## # @qom-get: # -# This command will get a property from a object model path and return -# the value. +# Get a property value. # # @path: The path within the object model. There are two forms of # supported paths--absolute and partial paths. @@ -128,14 +127,14 @@ ## # @qom-set: # -# This command will set a property from a object model path. +# Set a property value. # -# @path: see @qom-get for a description of this parameter +# @path: see `qom-get` for a description of this parameter # # @property: the property name to set # # @value: a value who's type is appropriate for the property type. -# See @qom-get for a description of type mapping. +# See `qom-get` for a description of type mapping. # # Since: 1.2 # @@ -154,7 +153,7 @@ ## # @ObjectTypeInfo: # -# This structure describes a search result from @qom-list-types +# This structure describes a search result from `qom-list-types` # # @name: the type name found in the search # @@ -171,15 +170,14 @@ ## # @qom-list-types: # -# This command will return a list of types given search parameters +# Return a list of types given search parameters. # # @implements: if specified, only return types that implement this # type name # # @abstract: if true, include abstract types in the results # -# Returns: a list of @ObjectTypeInfo or an empty list if no results -# are found +# Returns: a list of types, or an empty list if no results are found # # Since: 1.1 ## @@ -195,11 +193,12 @@ # # @typename: the type name of an object # +# # .. note:: Objects can create properties at runtime, for example to # describe links between different devices and/or objects. These # properties are not included in the output of this command. # -# Returns: a list of ObjectPropertyInfo describing object properties +# Returns: a list describing object properties # # Since: 2.12 ## @@ -789,7 +788,7 @@ # # Properties for x-remote-object objects. # -# @fd: file descriptor name previously passed via 'getfd' command +# @fd: file descriptor name previously passed via `getfd` command # # @devid: the id of the device to be associated with the file # descriptor @@ -818,7 +817,7 @@ # # Properties for iommufd objects. # -# @fd: file descriptor name previously passed via 'getfd' command, +# @fd: file descriptor name previously passed via `getfd` command, # which represents a pre-opened /dev/iommu. This allows the # iommufd object to be shared across several subsystems (VFIO, # VDPA, ...), and the file descriptor to be shared with other @@ -933,6 +932,19 @@ 'if': 'CONFIG_POSIX' } ## +# @IgvmCfgProperties: +# +# Properties common to objects that handle IGVM files. +# +# @file: IGVM file to use to configure guest +# +# Since: 10.1 +## +{ 'struct': 'IgvmCfgProperties', + 'if': 'CONFIG_IGVM', + 'data': { 'file': 'str' } } + +## # @SevCommonProperties: # # Properties common to objects that are derivatives of sev-common. @@ -1142,6 +1154,8 @@ 'filter-redirector', 'filter-replay', 'filter-rewriter', + { 'name': 'igvm-cfg', + 'if': 'CONFIG_IGVM' }, 'input-barrier', { 'name': 'input-linux', 'if': 'CONFIG_LINUX' }, @@ -1218,6 +1232,8 @@ 'filter-redirector': 'FilterRedirectorProperties', 'filter-replay': 'NetfilterProperties', 'filter-rewriter': 'FilterRewriterProperties', + 'igvm-cfg': { 'type': 'IgvmCfgProperties', + 'if': 'CONFIG_IGVM' }, 'input-barrier': 'InputBarrierProperties', 'input-linux': { 'type': 'InputLinuxProperties', 'if': 'CONFIG_LINUX' }, @@ -1261,7 +1277,7 @@ # Create a QOM object. # # Errors: -# - Error if @qom-type is not a valid class name +# - If @qom-type is not a valid class name # # Since: 2.0 # @@ -1283,7 +1299,7 @@ # @id: the name of the QOM object to remove # # Errors: -# - Error if @id is not a valid id for a QOM object +# - If @id is not a valid id for a QOM object # # Since: 2.0 # diff --git a/qapi/replay.json b/qapi/replay.json index 35e0c4a..ccf84da 100644 --- a/qapi/replay.json +++ b/qapi/replay.json @@ -3,7 +3,9 @@ # ## -# = Record/replay +# ************* +# Record/replay +# ************* ## { 'include': 'common.json' } @@ -47,8 +49,8 @@ # @query-replay: # # Retrieve the record/replay information. It includes current -# instruction count which may be used for @replay-break and -# @replay-seek commands. +# instruction count which may be used for `replay-break` and +# `replay-seek` commands. # # Returns: record/replay information. # @@ -70,7 +72,7 @@ # breakpoint. When breakpoint is set, any prior one is removed. The # breakpoint may be set only in replay mode and only "in the future", # i.e. at instruction counts greater than the current one. The -# current instruction count can be observed with @query-replay. +# current instruction count can be observed with `query-replay`. # # @icount: instruction count to stop at # @@ -86,7 +88,7 @@ ## # @replay-delete-break: # -# Remove replay breakpoint which was set with @replay-break. The +# Remove replay breakpoint which was set with `replay-break`. The # command is ignored when there are no replay breakpoints. # # Since: 5.2 @@ -106,7 +108,7 @@ # snapshot and replays the execution to find the desired instruction. # When there is no preceding snapshot or the execution is not # replayed, then the command fails. Instruction count can be obtained -# with the @query-replay command. +# with the `query-replay` command. # # @icount: target instruction count # diff --git a/qapi/rocker.json b/qapi/rocker.json index 0c7ef1f..5d2dbd2 100644 --- a/qapi/rocker.json +++ b/qapi/rocker.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = Rocker switch device +# ******************** +# Rocker switch device +# ******************** ## ## @@ -28,8 +30,6 @@ # # @name: switch name # -# Returns: @Rocker information -# # Since: 2.4 # # .. qmp-example:: @@ -98,8 +98,6 @@ # # @name: port name # -# Returns: a list of @RockerPort information -# # Since: 2.4 # # .. qmp-example:: diff --git a/qapi/run-state.json b/qapi/run-state.json index fd09beb..54ba5c9 100644 --- a/qapi/run-state.json +++ b/qapi/run-state.json @@ -3,7 +3,9 @@ # ## -# = VM run state +# ************ +# VM run state +# ************ ## ## @@ -68,9 +70,9 @@ # # @host-error: An error prevents further use of guest # -# @host-qmp-quit: Reaction to the QMP command 'quit' +# @host-qmp-quit: Reaction to the QMP command `quit` # -# @host-qmp-system-reset: Reaction to the QMP command 'system_reset' +# @host-qmp-system-reset: Reaction to the QMP command `system_reset` # # @host-signal: Reaction to a signal, such as SIGINT # @@ -106,7 +108,7 @@ # # @running: true if all VCPUs are runnable, false if not runnable # -# @status: the virtual machine @RunState +# @status: the virtual machine `RunState` # # Since: 0.14 ## @@ -119,8 +121,6 @@ # # Query the run status of the VM # -# Returns: @StatusInfo reflecting the VM -# # Since: 0.14 # # .. qmp-example:: @@ -143,12 +143,12 @@ # hardware-specific action) rather than a host request (such as # sending QEMU a SIGINT). (since 2.10) # -# @reason: The @ShutdownCause which resulted in the SHUTDOWN. +# @reason: The `ShutdownCause` which resulted in the `SHUTDOWN`. # (since 4.0) # # .. note:: If the command-line option ``-no-shutdown`` has been -# specified, QEMU will not exit, and a STOP event will eventually -# follow the SHUTDOWN event. +# specified, QEMU will not exit, and a `STOP` event will eventually +# follow the `SHUTDOWN` event. # # Since: 0.12 # @@ -183,9 +183,9 @@ # @guest: If true, the reset was triggered by a guest request (such as # a guest-initiated ACPI reboot request or other hardware-specific # action) rather than a host request (such as the QMP command -# system_reset). (since 2.10) +# `system_reset`). (since 2.10) # -# @reason: The @ShutdownCause of the RESET. (since 4.0) +# @reason: The `ShutdownCause` of the `RESET`. (since 4.0) # # Since: 0.12 # @@ -247,7 +247,7 @@ # saved on disk, for example, S4 state, which is sometimes called # hibernate state # -# .. note:: QEMU shuts down (similar to event @SHUTDOWN) when entering +# .. note:: QEMU shuts down (similar to event `SHUTDOWN`) when entering # this state. # # Since: 1.2 @@ -281,8 +281,8 @@ # # @action: action that has been taken # -# .. note:: If action is "reset", "shutdown", or "pause" the WATCHDOG -# event is followed respectively by the RESET, SHUTDOWN, or STOP +# .. note:: If action is "reset", "shutdown", or "pause" the `WATCHDOG` +# event is followed respectively by the `RESET`, `SHUTDOWN`, or `STOP` # events. # # .. note:: This event is rate-limited. @@ -378,7 +378,7 @@ # # Set watchdog action. # -# @action: @WatchdogAction action taken when watchdog timer expires. +# @action: `WatchdogAction` action taken when watchdog timer expires. # # Since: 2.11 # @@ -396,13 +396,13 @@ # Set the actions that will be taken by the emulator in response to # guest events. # -# @reboot: @RebootAction action taken on guest reboot. +# @reboot: `RebootAction` action taken on guest reboot. # -# @shutdown: @ShutdownAction action taken on guest shutdown. +# @shutdown: `ShutdownAction` action taken on guest shutdown. # -# @panic: @PanicAction action taken on guest panic. +# @panic: `PanicAction` action taken on guest panic. # -# @watchdog: @WatchdogAction action taken when watchdog timer expires. +# @watchdog: `WatchdogAction` action taken when watchdog timer expires. # # Since: 6.0 # @@ -529,20 +529,20 @@ # # Hyper-V specific guest panic information (HV crash MSRs) # -# @arg1: for Windows, STOP code for the guest crash. For Linux, +# @arg1: for Windows, `STOP` code for the guest crash. For Linux, # an error code. # -# @arg2: for Windows, first argument of the STOP. For Linux, the +# @arg2: for Windows, first argument of the `STOP`. For Linux, the # guest OS ID, which has the kernel version in bits 16-47 and # 0x8100 in bits 48-63. # -# @arg3: for Windows, second argument of the STOP. For Linux, the +# @arg3: for Windows, second argument of the `STOP`. For Linux, the # program counter of the guest. # -# @arg4: for Windows, third argument of the STOP. For Linux, the +# @arg4: for Windows, third argument of the `STOP`. For Linux, the # RAX register (x86) or the stack pointer (aarch64) of the guest. # -# @arg5: for Windows, fourth argument of the STOP. For x86 Linux, the +# @arg5: for Windows, fourth argument of the `STOP`. For x86 Linux, the # stack pointer of the guest. # # Since: 2.9 @@ -630,11 +630,11 @@ # # Emitted when a memory failure occurs on host side. # -# @recipient: recipient is defined as @MemoryFailureRecipient. +# @recipient: recipient is defined as `MemoryFailureRecipient`. # # @action: action that has been taken. # -# @flags: flags for MemoryFailureAction. +# @flags: flags for `MemoryFailureAction`. # # Since: 5.2 # diff --git a/qapi/sockets.json b/qapi/sockets.json index f9f559d..82046b0 100644 --- a/qapi/sockets.json +++ b/qapi/sockets.json @@ -2,7 +2,9 @@ # vim: filetype=python ## -# = Socket data types +# ***************** +# Socket data types +# ***************** ## ## @@ -209,14 +211,14 @@ 'unix': 'UnixSocketAddressWrapper', 'vsock': 'VsockSocketAddressWrapper', 'fd': 'FdSocketAddressWrapper' } } -# Note: This type is deprecated in favor of SocketAddress. The -# difference between SocketAddressLegacy and SocketAddress is that the +# Note: This type is deprecated in favor of `SocketAddress`. The +# difference between `SocketAddressLegacy` and `SocketAddress` is that the # latter has fewer ``{}`` on the wire. ## # @SocketAddressType: # -# Available SocketAddress types +# Available `SocketAddress` types # # @inet: Internet address # diff --git a/qapi/stats.json b/qapi/stats.json index 8902ef9..151ac43 100644 --- a/qapi/stats.json +++ b/qapi/stats.json @@ -9,7 +9,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later ## -# = Statistics +# ********** +# Statistics +# ********** ## ## @@ -87,7 +89,7 @@ # @StatsRequest: # # Indicates a set of statistics that should be returned by -# query-stats. +# `query-stats`. # # @provider: provider for which to return statistics. # @@ -112,7 +114,7 @@ ## # @StatsFilter: # -# The arguments to the query-stats command; specifies a target for +# The arguments to the `query-stats` command; specifies a target for # which to request statistics and optionally the required subset of # information for that target. # @@ -183,10 +185,10 @@ # Return runtime-collected statistics for objects such as the VM or # its vCPUs. # -# The arguments are a StatsFilter and specify the provider and objects +# The arguments are a `StatsFilter` and specify the provider and objects # to return statistics about. # -# Returns: a list of StatsResult, one for each provider and object +# Returns: a list of statistics, one for each provider and object # (e.g., for each vCPU). # # Since: 7.1 @@ -203,7 +205,7 @@ # # @name: name of the statistic; each element of the schema is uniquely # identified by a target, a provider (both available in -# @StatsSchema) and the name. +# `StatsSchema`) and the name. # # @type: kind of statistic. # diff --git a/qapi/tpm.json b/qapi/tpm.json index a16a72e..3f2850a 100644 --- a/qapi/tpm.json +++ b/qapi/tpm.json @@ -3,7 +3,9 @@ # ## -# = TPM (trusted platform module) devices +# ************************************* +# TPM (trusted platform module) devices +# ************************************* ## ## @@ -27,8 +29,6 @@ # # Return a list of supported TPM models # -# Returns: a list of TpmModel -# # Since: 1.5 # # .. qmp-example:: @@ -58,8 +58,6 @@ # # Return a list of supported TPM types # -# Returns: a list of TpmType -# # Since: 1.5 # # .. qmp-example:: diff --git a/qapi/trace.json b/qapi/trace.json index eb5f63f..de369da 100644 --- a/qapi/trace.json +++ b/qapi/trace.json @@ -7,7 +7,9 @@ # See the COPYING file in the top-level directory. ## -# = Tracing +# ******* +# Tracing +# ******* ## ## @@ -47,7 +49,7 @@ # # @name: Event name pattern (case-sensitive glob). # -# Returns: a list of @TraceEventInfo for the matching events +# Returns: a list of info for the matching events # # Since: 2.2 # diff --git a/qapi/transaction.json b/qapi/transaction.json index 9d9e7af..4b4eb09 100644 --- a/qapi/transaction.json +++ b/qapi/transaction.json @@ -3,7 +3,9 @@ # ## -# = Transactions +# ************ +# Transactions +# ************ ## { 'include': 'block-core.json' } @@ -67,8 +69,8 @@ # # Features: # -# @deprecated: Member @drive-backup is deprecated. Use member -# @blockdev-backup instead. +# @deprecated: Member `drive-backup` is deprecated. Use member +# `blockdev-backup` instead. # # Since: 1.1 ## @@ -156,7 +158,7 @@ # @TransactionAction: # # A discriminated record of operations that can be performed with -# @transaction. +# `transaction`. # # @type: the operation to be performed # @@ -187,7 +189,7 @@ # # @completion-mode: Controls how jobs launched asynchronously by # Actions will complete or fail as a group. See -# @ActionCompletionMode for details. +# `ActionCompletionMode` for details. # # Since: 2.5 ## @@ -227,11 +229,11 @@ # in the transaction. When an I/O error occurs during deletion, the # user needs to fix it later with qemu-img or other command. # -# @actions: List of @TransactionAction; information needed for the +# @actions: List of `TransactionAction`; information needed for the # respective operations. # # @properties: structure of additional options to control the -# execution of the transaction. See @TransactionProperties for +# execution of the transaction. See `TransactionProperties` for # additional detail. # # Errors: diff --git a/qapi/uefi.json b/qapi/uefi.json index 6592183..a206c2e 100644 --- a/qapi/uefi.json +++ b/qapi/uefi.json @@ -3,7 +3,9 @@ # ## -# = UEFI Variable Store +# ******************* +# UEFI Variable Store +# ******************* # # The QEMU efi variable store implementation (hw/uefi/) uses this to # store non-volatile variables in json format on disk. diff --git a/qapi/ui.json b/qapi/ui.json index 514fa15..1b2f4a4 100644 --- a/qapi/ui.json +++ b/qapi/ui.json @@ -3,7 +3,9 @@ # ## -# = Remote desktop +# ************** +# Remote desktop +# ************** ## { 'include': 'common.json' } @@ -39,7 +41,7 @@ ## # @SetPasswordOptions: # -# Options for set_password. +# Options for `set_password`. # # @protocol: # - 'vnc' to modify the VNC server password @@ -63,7 +65,7 @@ ## # @SetPasswordOptionsVnc: # -# Options for set_password specific to the VNC protocol. +# Options for `set_password` specific to the VNC protocol. # # @display: The id of the display where the password should be # changed. Defaults to the first. @@ -94,7 +96,7 @@ ## # @ExpirePasswordOptions: # -# General options for expire_password. +# General options for `expire_password`. # # @protocol: # - 'vnc' to modify the VNC server expiration @@ -124,7 +126,7 @@ ## # @ExpirePasswordOptionsVnc: # -# Options for expire_password specific to the VNC protocol. +# Options for `expire_password` specific to the VNC protocol. # # @display: The id of the display where the expiration should be # changed. Defaults to the first. @@ -183,7 +185,7 @@ # the head can only be specified in conjunction with the device # ID. (Since 2.12) # -# @format: image format for screendump. (default: ppm) (Since 7.1) +# @format: image format for `screendump`. (default: ppm) (Since 7.1) # # Since: 0.14 # @@ -200,7 +202,8 @@ 'if': 'CONFIG_PIXMAN' } ## -# == Spice +# Spice +# ===== ## ## @@ -310,7 +313,7 @@ # unknown if spice server doesn't provide this information. # (since: 1.1) # -# @channels: a list of @SpiceChannel for each active spice channel +# @channels: a list of `SpiceChannel` for each active spice channel # # Since: 0.14 ## @@ -325,8 +328,6 @@ # # Return information about the current SPICE server # -# Returns: @SpiceInfo -# # Since: 0.14 # # .. qmp-example:: @@ -461,7 +462,8 @@ 'if': 'CONFIG_SPICE' } ## -# == VNC +# VNC +# === ## ## @@ -561,7 +563,7 @@ # - 'vencrypt+x509+sasl' if VEncrypt is used with x509 and SASL # auth # -# @clients: a list of @VncClientInfo of all currently connected +# @clients: a list of `VncClientInfo` of all currently connected # clients # # Since: 0.14 @@ -624,12 +626,12 @@ # # @id: vnc server name. # -# @server: A list of @VncBasincInfo describing all listening sockets. +# @server: A list of `VncBasicInfo` describing all listening sockets. # The list can be empty (in case the vnc server is disabled). It # also may have multiple entries: normal + websocket, possibly # also ipv4 + ipv6 in the future. # -# @clients: A list of @VncClientInfo of all currently connected +# @clients: A list of `VncClientInfo` of all currently connected # clients. The list can be empty, for obvious reasons. # # @auth: The current authentication type used by the non-websockets @@ -656,8 +658,6 @@ # # Return information about the current VNC server # -# Returns: @VncInfo -# # Since: 0.14 # # .. qmp-example:: @@ -687,8 +687,6 @@ # # Return a list of vnc servers. The list can be empty. # -# Returns: a list of @VncInfo2 -# # Since: 2.3 ## { 'command': 'query-vnc-servers', 'returns': ['VncInfo2'], @@ -794,7 +792,9 @@ 'if': 'CONFIG_VNC' } ## -# = Input +# ***** +# Input +# ***** ## ## @@ -822,7 +822,7 @@ # # Return information about each active mouse device # -# Returns: a list of @MouseInfo for each device +# Returns: a list of info for each device # # Since: 0.14 # @@ -852,7 +852,7 @@ # # An enumeration of key name. # -# This is used by the @send-key command. +# This is used by the `send-key` command. # # @unmapped: since 2.0 # @@ -1023,10 +1023,10 @@ # # Send keys to guest. # -# @keys: An array of @KeyValue elements. All @KeyValues in this array -# are simultaneously sent to the guest. A @KeyValue.number value -# is sent directly to the guest, while @KeyValue.qcode must be a -# valid @QKeyCode value +# @keys: An array of `KeyValue` elements. All @KeyValues in this array +# are simultaneously sent to the guest. A `KeyValue`.number value +# is sent directly to the guest, while `KeyValue`.qcode must be a +# valid `QKeyCode` value # # @hold-time: time to delay key up events, milliseconds. Defaults to # 100 @@ -1263,7 +1263,7 @@ # @head: head to send event(s) to, in case the display device supports # multiple scanouts. # -# @events: List of InputEvent union. +# @events: List of `InputEvent` union. # # Since: 2.6 # @@ -1335,13 +1335,20 @@ # @show-menubar: Display the main window menubar. Defaults to "on". # (Since 8.0) # +# @keep-aspect-ratio: Keep width/height aspect ratio of guest content when +# resizing host window. Defaults to "on". (Since 10.1) +# +# @scale: Set preferred scale of the display. Defaults to 1.0. (Since 10.1) +# # Since: 2.12 ## { 'struct' : 'DisplayGTK', - 'data' : { '*grab-on-hover' : 'bool', - '*zoom-to-fit' : 'bool', - '*show-tabs' : 'bool', - '*show-menubar' : 'bool' } } + 'data' : { '*grab-on-hover' : 'bool', + '*zoom-to-fit' : 'bool', + '*show-tabs' : 'bool', + '*show-menubar' : 'bool', + '*keep-aspect-ratio' : 'bool', + '*scale' : 'number' } } ## # @DisplayEGLHeadless: @@ -1367,7 +1374,7 @@ # first available node on the host. # # @p2p: Whether to use peer-to-peer connections (accepted through -# @add_client). +# `add_client`). # # @audiodev: Use the specified DBus audiodev to export audio. # @@ -1526,7 +1533,7 @@ # # Display (user interface) options. # -# @type: Which DisplayType QEMU should use. +# @type: Which `DisplayType` QEMU should use. # # @full-screen: Start user interface in fullscreen mode # (default: off). @@ -1564,8 +1571,6 @@ # # Return information about display configuration # -# Returns: @DisplayOptions -# # Since: 3.1 ## { 'command': 'query-display-options', diff --git a/qapi/vfio.json b/qapi/vfio.json index b53b7ca..a1a9c5b 100644 --- a/qapi/vfio.json +++ b/qapi/vfio.json @@ -3,7 +3,9 @@ # ## -# = VFIO devices +# ************ +# VFIO devices +# ************ ## ## diff --git a/qapi/virtio.json b/qapi/virtio.json index 73df718..9d652fe 100644 --- a/qapi/virtio.json +++ b/qapi/virtio.json @@ -3,7 +3,9 @@ # ## -# = Virtio devices +# ************** +# Virtio devices +# ************** ## ## @@ -135,7 +137,7 @@ # @num-vqs: VirtIODevice virtqueue count. This is the number of # active virtqueues being used by the VirtIODevice. # -# @status: VirtIODevice configuration status (VirtioDeviceStatus) +# @status: VirtIODevice configuration status (`VirtioDeviceStatus`) # # @isr: VirtIODevice ISR # @@ -199,7 +201,7 @@ # # @unstable: This command is meant for debugging. # -# Returns: VirtioStatus of the virtio device +# Returns: Status of the virtio device # # Since: 7.2 # @@ -563,7 +565,7 @@ # # @unstable: This command is meant for debugging. # -# Returns: VirtQueueStatus of the VirtQueue +# Returns: Status of the queue # # .. note:: last_avail_idx will not be displayed in the case where the # selected VirtIODevice has a running vhost device and the @@ -577,7 +579,7 @@ # .. qmp-example:: # :annotated: # -# Get VirtQueueStatus for virtio-vsock (vhost-vsock running) +# Get `VirtQueueStatus` for virtio-vsock (vhost-vsock running) # :: # # -> { "execute": "x-query-virtio-queue-status", @@ -604,7 +606,7 @@ # .. qmp-example:: # :annotated: # -# Get VirtQueueStatus for virtio-serial (no vhost) +# Get `VirtQueueStatus` for virtio-serial (no vhost) # :: # # -> { "execute": "x-query-virtio-queue-status", @@ -698,7 +700,7 @@ # # @unstable: This command is meant for debugging. # -# Returns: VirtVhostQueueStatus of the vhost_virtqueue +# Returns: Status of the vhost_virtqueue # # Since: 7.2 # @@ -816,7 +818,7 @@ # # @index: Index of the element in the queue # -# @descs: List of descriptors (VirtioRingDesc) +# @descs: List of descriptors (`VirtioRingDesc`) # # @avail: VRingAvail info # @@ -847,8 +849,6 @@ # # @unstable: This command is meant for debugging. # -# Returns: VirtioQueueElement information -# # Since: 7.2 # # .. qmp-example:: @@ -964,16 +964,30 @@ 'data': { 'iothread': 'str', '*vqs': ['uint16'] } } ## +# @VirtIOGPUOutput: +# +# Describes configuration of a VirtIO GPU output. +# +# @name: the name of the output +# +# Since: 10.1 +## + +{ 'struct': 'VirtIOGPUOutput', + 'data': { 'name': 'str' } } + +## # @DummyVirtioForceArrays: # # Not used by QMP; hack to let us use IOThreadVirtQueueMappingList -# internally +# and VirtIOGPUOutputList internally # # Since: 9.0 ## { 'struct': 'DummyVirtioForceArrays', - 'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'] } } + 'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'], + 'unused-virtio-gpu-output': ['VirtIOGPUOutput'] } } ## # @GranuleMode: diff --git a/qapi/yank.json b/qapi/yank.json index 30f46c9..f3cd5c1 100644 --- a/qapi/yank.json +++ b/qapi/yank.json @@ -3,13 +3,15 @@ # ## -# = Yank feature +# ************ +# Yank feature +# ************ ## ## # @YankInstanceType: # -# An enumeration of yank instance types. See @YankInstance for more +# An enumeration of yank instance types. See `YankInstance` for more # information. # # Since: 6.0 @@ -20,7 +22,7 @@ ## # @YankInstanceBlockNode: # -# Specifies which block graph node to yank. See @YankInstance for +# Specifies which block graph node to yank. See `YankInstance` for # more information. # # @node-name: the name of the block graph node @@ -33,7 +35,7 @@ ## # @YankInstanceChardev: # -# Specifies which character device to yank. See @YankInstance for +# Specifies which character device to yank. See `YankInstance` for # more information. # # @id: the chardev's ID @@ -46,7 +48,7 @@ ## # @YankInstance: # -# A yank instance can be yanked with the @yank qmp command to recover +# A yank instance can be yanked with the `yank` qmp command to recover # from a hanging QEMU. # # @type: yank instance type @@ -57,9 +59,9 @@ # nbd server without attempting to reconnect. # - socket chardev: Yanking it will shut down the connected socket. # - migration: Yanking it will shut down all migration connections. -# Unlike @migrate_cancel, it will not notify the migration process, +# Unlike `migrate_cancel`, it will not notify the migration process, # so migration will go into @failed state, instead of @cancelled -# state. @yank should be used to recover from hangs. +# state. `yank` should be used to recover from hangs. # # Since: 6.0 ## @@ -74,7 +76,7 @@ # @yank: # # Try to recover from hanging QEMU by yanking the specified instances. -# See @YankInstance for more information. +# See `YankInstance` for more information. # # @instances: the instances to be yanked # @@ -100,9 +102,9 @@ ## # @query-yank: # -# Query yank instances. See @YankInstance for more information. +# Query yank instances. See `YankInstance` for more information. # -# Returns: list of @YankInstance +# TODO: This line is a hack to separate the example from the body # # .. qmp-example:: # diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index c9dd70a..2c5a8a2 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -84,9 +84,9 @@ SRST ERST DEF("snapshot", img_snapshot, - "snapshot [--object objectdef] [--image-opts] [-U] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") + "snapshot [--object objectdef] [-f fmt | --image-opts] [-U] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") SRST -.. option:: snapshot [--object OBJECTDEF] [--image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME +.. option:: snapshot [--object OBJECTDEF] [-f FMT | --image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME ERST DEF("rebase", img_rebase, @@ -60,7 +60,8 @@ typedef struct img_cmd_t { const char *name; - int (*handler)(int argc, char **argv); + int (*handler)(const struct img_cmd_t *ccmd, int argc, char **argv); + const char *description; } img_cmd_t; enum { @@ -72,7 +73,6 @@ enum { OPTION_FLUSH_INTERVAL = 261, OPTION_NO_DRAIN = 262, OPTION_TARGET_IMAGE_OPTS = 263, - OPTION_SIZE = 264, OPTION_PREALLOCATION = 265, OPTION_SHRINK = 266, OPTION_SALVAGE = 267, @@ -96,13 +96,15 @@ typedef enum OutputFormat { /* Default to cache=writeback as data integrity is not important for qemu-img */ #define BDRV_DEFAULT_CACHE "writeback" -static void format_print(void *opaque, const char *name) +static G_NORETURN +void tryhelp(const char *argv0) { - printf(" %s", name); + error_printf("Try '%s --help' for more information\n", argv0); + exit(EXIT_FAILURE); } -static G_NORETURN G_GNUC_PRINTF(1, 2) -void error_exit(const char *fmt, ...) +static G_NORETURN G_GNUC_PRINTF(2, 3) +void error_exit(const char *argv0, const char *fmt, ...) { va_list ap; @@ -110,128 +112,43 @@ void error_exit(const char *fmt, ...) error_vreport(fmt, ap); va_end(ap); - error_printf("Try 'qemu-img --help' for more information\n"); - exit(EXIT_FAILURE); -} - -static G_NORETURN -void missing_argument(const char *option) -{ - error_exit("missing argument for option '%s'", option); + tryhelp(argv0); } +/* + * Print --help output for a command and exit. + * @syntax and @description are multi-line with trailing EOL + * (to allow easy extending of the text) + * @syntax has each subsequent line indented by 8 chars. + * @description is indented by 2 chars for argument on each own line, + * and with 5 chars for argument description (like -h arg below). + */ static G_NORETURN -void unrecognized_option(const char *option) +void cmd_help(const img_cmd_t *ccmd, + const char *syntax, const char *arguments) { - error_exit("unrecognized option '%s'", option); + printf( +"Usage:\n" +" %s %s %s\n" +"%s.\n" +"\n" +"Arguments:\n" +" -h, --help\n" +" print this help and exit\n" +"%s\n", + "qemu-img", ccmd->name, syntax, ccmd->description, arguments); + exit(EXIT_SUCCESS); } -/* Please keep in synch with docs/tools/qemu-img.rst */ -static G_NORETURN -void help(void) +static OutputFormat parse_output_format(const char *argv0, const char *arg) { - const char *help_msg = - QEMU_IMG_VERSION - "usage: qemu-img [standard options] command [command options]\n" - "QEMU disk image utility\n" - "\n" - " '-h', '--help' display this help and exit\n" - " '-V', '--version' output version information and exit\n" - " '-T', '--trace' [[enable=]<pattern>][,events=<file>][,file=<file>]\n" - " specify tracing options\n" - "\n" - "Command syntax:\n" -#define DEF(option, callback, arg_string) \ - " " arg_string "\n" -#include "qemu-img-cmds.h" -#undef DEF - "\n" - "Command parameters:\n" - " 'filename' is a disk image filename\n" - " 'objectdef' is a QEMU user creatable object definition. See the qemu(1)\n" - " manual page for a description of the object properties. The most common\n" - " object type is a 'secret', which is used to supply passwords and/or\n" - " encryption keys.\n" - " 'fmt' is the disk image format. It is guessed automatically in most cases\n" - " 'cache' is the cache mode used to write the output disk image, the valid\n" - " options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n" - " 'directsync' and 'unsafe' (default for convert)\n" - " 'src_cache' is the cache mode used to read input disk images, the valid\n" - " options are the same as for the 'cache' option\n" - " 'size' is the disk image size in bytes. Optional suffixes\n" - " 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' (gigabyte, 1024M),\n" - " 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' (exabyte, 1024P) are\n" - " supported. 'b' is ignored.\n" - " 'output_filename' is the destination disk image filename\n" - " 'output_fmt' is the destination format\n" - " 'options' is a comma separated list of format specific options in a\n" - " name=value format. Use -o help for an overview of the options supported by\n" - " the used format\n" - " 'snapshot_param' is param used for internal snapshot, format\n" - " is 'snapshot.id=[ID],snapshot.name=[NAME]', or\n" - " '[ID_OR_NAME]'\n" - " '-c' indicates that target image must be compressed (qcow format only)\n" - " '-u' allows unsafe backing chains. For rebasing, it is assumed that old and\n" - " new backing file match exactly. The image doesn't need a working\n" - " backing file before rebasing in this case (useful for renaming the\n" - " backing file). For image creation, allow creating without attempting\n" - " to open the backing file.\n" - " '-h' with or without a command shows this help and lists the supported formats\n" - " '-p' show progress of command (only certain commands)\n" - " '-q' use Quiet mode - do not print any output (except errors)\n" - " '-S' indicates the consecutive number of bytes (defaults to 4k) that must\n" - " contain only zeros for qemu-img to create a sparse image during\n" - " conversion. If the number of bytes is 0, the source will not be scanned for\n" - " unallocated or zero sectors, and the destination image will always be\n" - " fully allocated\n" - " '--output' takes the format in which the output must be done (human or json)\n" - " '-n' skips the target volume creation (useful if the volume is created\n" - " prior to running qemu-img)\n" - "\n" - "Parameters to bitmap subcommand:\n" - " 'bitmap' is the name of the bitmap to manipulate, through one or more\n" - " actions from '--add', '--remove', '--clear', '--enable', '--disable',\n" - " or '--merge source'\n" - " '-g granularity' sets the granularity for '--add' actions\n" - " '-b source' and '-F src_fmt' tell '--merge' actions to find the source\n" - " bitmaps from an alternative file\n" - "\n" - "Parameters to check subcommand:\n" - " '-r' tries to repair any inconsistencies that are found during the check.\n" - " '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all\n" - " kinds of errors, with a higher risk of choosing the wrong fix or\n" - " hiding corruption that has already occurred.\n" - "\n" - "Parameters to convert subcommand:\n" - " '--bitmaps' copies all top-level persistent bitmaps to destination\n" - " '-m' specifies how many coroutines work in parallel during the convert\n" - " process (defaults to 8)\n" - " '-W' allow to write to the target out of order rather than sequential\n" - "\n" - "Parameters to snapshot subcommand:\n" - " 'snapshot' is the name of the snapshot to create, apply or delete\n" - " '-a' applies a snapshot (revert disk to saved state)\n" - " '-c' creates a snapshot\n" - " '-d' deletes a snapshot\n" - " '-l' lists all snapshots in the given image\n" - "\n" - "Parameters to compare subcommand:\n" - " '-f' first image format\n" - " '-F' second image format\n" - " '-s' run in Strict mode - fail on different image size or sector allocation\n" - "\n" - "Parameters to dd subcommand:\n" - " 'bs=BYTES' read and write up to BYTES bytes at a time " - "(default: 512)\n" - " 'count=N' copy only N input blocks\n" - " 'if=FILE' read from FILE\n" - " 'of=FILE' write to FILE\n" - " 'skip=N' skip N bs-sized blocks at the start of input\n"; - - printf("%s\nSupported formats:", help_msg); - bdrv_iterate_format(format_print, NULL, false); - printf("\n\n" QEMU_HELP_BOTTOM "\n"); - exit(EXIT_SUCCESS); + if (!strcmp(arg, "json")) { + return OFORMAT_JSON; + } else if (!strcmp(arg, "human")) { + return OFORMAT_HUMAN; + } else { + error_exit(argv0, "--output expects 'human' or 'json', not '%s'", arg); + } } /* @@ -481,18 +398,16 @@ static int add_old_style_options(const char *fmt, QemuOpts *opts, return 0; } -static int64_t cvtnum_full(const char *name, const char *value, int64_t min, - int64_t max) +static int64_t cvtnum_full(const char *name, const char *value, + bool is_size, int64_t min, int64_t max) { int err; uint64_t res; - err = qemu_strtosz(value, NULL, &res); + err = is_size ? qemu_strtosz(value, NULL, &res) : + qemu_strtou64(value, NULL, 0, &res); if (err < 0 && err != -ERANGE) { - error_report("Invalid %s specified. You may use " - "k, M, G, T, P or E suffixes for", name); - error_report("kilobytes, megabytes, gigabytes, terabytes, " - "petabytes and exabytes."); + error_report("Invalid %s specified: '%s'", name, value); return err; } if (err == -ERANGE || res > max || res < min) { @@ -503,15 +418,15 @@ static int64_t cvtnum_full(const char *name, const char *value, int64_t min, return res; } -static int64_t cvtnum(const char *name, const char *value) +static int64_t cvtnum(const char *name, const char *value, bool is_size) { - return cvtnum_full(name, value, 0, INT64_MAX); + return cvtnum_full(name, value, is_size, 0, INT64_MAX); } -static int img_create(int argc, char **argv) +static int img_create(const img_cmd_t *ccmd, int argc, char **argv) { int c; - uint64_t img_size = -1; + int64_t img_size = -1; const char *fmt = "raw"; const char *base_fmt = NULL; const char *filename; @@ -524,29 +439,46 @@ static int img_create(int argc, char **argv) for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"format", required_argument, 0, 'f'}, + {"options", required_argument, 0, 'o'}, + {"backing", required_argument, 0, 'b'}, + {"backing-format", required_argument, 0, 'B'}, /* was -F in 10.0 */ + {"backing-unsafe", no_argument, 0, 'u'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":F:b:f:ho:qu", + c = getopt_long(argc, argv, "hf:o:b:F:B:uq", long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - break; - case 'F': - base_fmt = optarg; - break; - case 'b': - base_filename = optarg; + cmd_help(ccmd, "[-f FMT] [-o FMT_OPTS]\n" +" [-b BACKING_FILE [-B BACKING_FMT]] [-u]\n" +" [-q] [--object OBJDEF] FILE [SIZE]\n" +, +" -f, --format FMT\n" +" specifies the format of the new image (default: raw)\n" +" -o, --options FMT_OPTS\n" +" format-specific options (specify '-o help' for help)\n" +" -b, --backing BACKING_FILE\n" +" create target image to be a CoW on top of BACKING_FILE\n" +" -B, --backing-format BACKING_FMT (was -F in <= 10.0)\n" +" specifies the format of BACKING_FILE (default: probing is used)\n" +" -u, --backing-unsafe\n" +" do not fail if BACKING_FILE can not be read\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file to create (will be overritten if already exists)\n" +" SIZE[bKMGTPE]\n" +" image size with optional multiplier suffix (powers of 1024)\n" +" (required unless BACKING_FILE is specified)\n" +); break; case 'f': fmt = optarg; @@ -556,15 +488,24 @@ static int img_create(int argc, char **argv) goto fail; } break; - case 'q': - quiet = true; + case 'b': + base_filename = optarg; + break; + case 'F': /* <=10.0 */ + case 'B': + base_fmt = optarg; break; case 'u': flags |= BDRV_O_NO_BACKING; break; + case 'q': + quiet = true; + break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; + default: + tryhelp(argv[0]); } } @@ -576,22 +517,19 @@ static int img_create(int argc, char **argv) } if (optind >= argc) { - error_exit("Expecting image file name"); + error_exit(argv[0], "Expecting image file name"); } optind++; /* Get image size, if specified */ if (optind < argc) { - int64_t sval; - - sval = cvtnum("image size", argv[optind++]); - if (sval < 0) { + img_size = cvtnum("image size", argv[optind++], true); + if (img_size < 0) { goto fail; } - img_size = (uint64_t)sval; } if (optind != argc) { - error_exit("Unexpected argument: %s", argv[optind]); + error_exit(argv[0], "Unexpected argument: %s", argv[optind]); } bdrv_img_create(filename, fmt, base_filename, base_fmt, @@ -716,11 +654,11 @@ static int collect_image_check(BlockDriverState *bs, * 3 - Check completed, image has leaked clusters, but is good otherwise * 63 - Checks are not supported by the image format */ -static int img_check(int argc, char **argv) +static int img_check(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret; OutputFormat output_format = OFORMAT_HUMAN; - const char *filename, *fmt, *output, *cache; + const char *filename, *fmt, *cache; BlockBackend *blk; BlockDriverState *bs; int fix = 0; @@ -732,7 +670,6 @@ static int img_check(int argc, char **argv) bool force_share = false; fmt = NULL; - output = NULL; cache = BDRV_DEFAULT_CACHE; for(;;) { @@ -740,31 +677,57 @@ static int img_check(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"format", required_argument, 0, 'f'}, + {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 'T'}, {"repair", required_argument, 0, 'r'}, + {"force-share", no_argument, 0, 'U'}, {"output", required_argument, 0, OPTION_OUTPUT}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, - {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:r:T:qU", + c = getopt_long(argc, argv, "hf:T:r:Uq", long_options, &option_index); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, "[-f FMT | --image-opts] [-T CACHE_MODE] [-r leaks|all]\n" +" [-U] [--output human|json] [-q] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specifies the format of the image explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -T, --cache CACHE_MODE\n" /* why not -t ? */ +" cache mode (default: " BDRV_DEFAULT_CACHE ")\n" +" -r, --repair leaks|all\n" +" repair errors of the given category in the image (image will be\n" +" opened in read-write mode, incompatible with -U|--force-share)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --output human|json\n" +" output format (default: human)\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or an option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; case 'f': fmt = optarg; break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case 'T': + cache = optarg; + break; case 'r': flags |= BDRV_O_RDWR; @@ -773,44 +736,32 @@ static int img_check(int argc, char **argv) } else if (!strcmp(optarg, "all")) { fix = BDRV_FIX_LEAKS | BDRV_FIX_ERRORS; } else { - error_exit("Unknown option value for -r " - "(expecting 'leaks' or 'all'): %s", optarg); + error_exit(argv[0], + "--repair (-r) expects 'leaks' or 'all', not '%s'", + optarg); } break; - case OPTION_OUTPUT: - output = optarg; + case 'U': + force_share = true; break; - case 'T': - cache = optarg; + case OPTION_OUTPUT: + output_format = parse_output_format(argv[0], optarg); break; case 'q': quiet = true; break; - case 'U': - force_share = true; - break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - ret = bdrv_parse_cache_mode(cache, &flags, &writethrough); if (ret < 0) { error_report("Invalid source cache option: %s", cache); @@ -948,7 +899,7 @@ static void run_block_job(BlockJob *job, Error **errp) } } -static int img_commit(int argc, char **argv) +static int img_commit(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret, flags; const char *filename, *fmt, *cache, *base; @@ -968,38 +919,73 @@ static int img_commit(int argc, char **argv) for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 't'}, + {"drop", no_argument, 0, 'd'}, + {"base", required_argument, 0, 'b'}, + {"rate-limit", required_argument, 0, 'r'}, + {"progress", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:ht:b:dpqr:", + c = getopt_long(argc, argv, "hf:t:db:r:pq", long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, "[-f FMT | --image-opts] [-t CACHE_MODE] [-b BASE_IMG]\n" +" [-d] [-r RATE] [-q] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specify FILE image format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -t, --cache CACHE_MODE image cache mode (default: " BDRV_DEFAULT_CACHE ")\n" +" -d, --drop\n" +" skip emptying FILE on completion\n" +" -b, --base BASE_IMG\n" +" image in the backing chain to commit change to\n" +" (default: immediate backing file; implies --drop)\n" +" -r, --rate-limit RATE\n" +" I/O rate limit, in bytes per second\n" +" -p, --progress\n" +" display progress information\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or an option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; case 'f': fmt = optarg; break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; case 't': cache = optarg; break; + case 'd': + drop = true; + break; case 'b': base = optarg; /* -b implies -d */ drop = true; break; - case 'd': - drop = true; + case 'r': + rate_limit = cvtnum("rate limit", optarg, true); + if (rate_limit < 0) { + return 1; + } break; case 'p': progress = true; @@ -1007,18 +993,11 @@ static int img_commit(int argc, char **argv) case 'q': quiet = true; break; - case 'r': - rate_limit = cvtnum("rate limit", optarg); - if (rate_limit < 0) { - return 1; - } - break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } @@ -1028,7 +1007,7 @@ static int img_commit(int argc, char **argv) } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; @@ -1355,7 +1334,7 @@ static int check_empty_sectors(BlockBackend *blk, int64_t offset, * 1 - Images differ * >1 - Error occurred */ -static int img_compare(int argc, char **argv) +static int img_compare(const img_cmd_t *ccmd, int argc, char **argv) { const char *fmt1 = NULL, *fmt2 = NULL, *cache, *filename1, *filename2; BlockBackend *blk1, *blk2; @@ -1380,25 +1359,51 @@ static int img_compare(int argc, char **argv) for (;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"a-format", required_argument, 0, 'f'}, + {"b-format", required_argument, 0, 'F'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"strict", no_argument, 0, 's'}, + {"cache", required_argument, 0, 'T'}, {"force-share", no_argument, 0, 'U'}, + {"progress", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:T:pqsU", + c = getopt_long(argc, argv, "hf:F:sT:Upq", long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[[-f FMT] [-F FMT] | --image-opts] [-s] [-T CACHE]\n" +" [-U] [-p] [-q] [--object OBJDEF] FILE1 FILE2\n" +, +" -f, --a-format FMT\n" +" specify FILE1 image format explicitly (default: probing is used)\n" +" -F, --b-format FMT\n" +" specify FILE2 image format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE1 and FILE2 as option strings (key=value,..), not file names\n" +" (incompatible with -f|--a-format and -F|--b-format)\n" +" -s, --strict\n" +" strict mode, also check if sizes are equal\n" +" -T, --cache CACHE_MODE\n" +" images caching mode (default: " BDRV_DEFAULT_CACHE ")\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" -p, --progress\n" +" display progress information\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE1, FILE2\n" +" names of the image files, or option strings (key=value,..)\n" +" with --image-opts, to compare\n" +); break; case 'f': fmt1 = optarg; @@ -1406,39 +1411,29 @@ static int img_compare(int argc, char **argv) case 'F': fmt2 = optarg; break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case 's': + strict = true; + break; case 'T': cache = optarg; break; + case 'U': + force_share = true; + break; case 'p': progress = true; break; case 'q': quiet = true; break; - case 's': - strict = true; - break; - case 'U': - force_share = true; - break; case OPTION_OBJECT: - { - Error *local_err = NULL; - - if (!user_creatable_add_from_str(optarg, &local_err)) { - if (local_err) { - error_report_err(local_err); - exit(2); - } else { - /* Help was printed */ - exit(EXIT_SUCCESS); - } - } - break; - } - case OPTION_IMAGE_OPTS: - image_opts = true; + user_creatable_process_cmdline(optarg); break; + default: + tryhelp(argv[0]); } } @@ -1449,7 +1444,7 @@ static int img_compare(int argc, char **argv) if (optind != argc - 2) { - error_exit("Expecting two image file names"); + error_exit(argv[0], "Expecting two image file names"); } filename1 = argv[optind++]; filename2 = argv[optind++]; @@ -2231,7 +2226,7 @@ static void set_rate_limit(BlockBackend *blk, int64_t rate_limit) blk_set_io_limits(blk, &cfg); } -static int img_convert(int argc, char **argv) +static int img_convert(const img_cmd_t *ccmd, int argc, char **argv) { int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE; const char *fmt = NULL, *out_fmt = NULL, *cache = "unsafe", @@ -2267,53 +2262,122 @@ static int img_convert(int argc, char **argv) for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"source-format", required_argument, 0, 'f'}, + /* + * XXX: historic --image-opts acts on source file only, + * it seems better to have it affect both source and target, + * and have separate --source-image-opts for source, + * but this might break existing setups. + */ {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"force-share", no_argument, 0, 'U'}, - {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, - {"salvage", no_argument, 0, OPTION_SALVAGE}, - {"target-is-zero", no_argument, 0, OPTION_TARGET_IS_ZERO}, + {"source-cache", required_argument, 0, 'T'}, + {"snapshot", required_argument, 0, 'l'}, {"bitmaps", no_argument, 0, OPTION_BITMAPS}, {"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN}, + {"salvage", no_argument, 0, OPTION_SALVAGE}, + {"target-format", required_argument, 0, 'O'}, + {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, + {"target-format-options", required_argument, 0, 'o'}, + {"target-cache", required_argument, 0, 't'}, + {"backing", required_argument, 0, 'b'}, + {"backing-format", required_argument, 0, 'F'}, + {"sparse-size", required_argument, 0, 'S'}, + {"no-create", no_argument, 0, 'n'}, + {"target-is-zero", no_argument, 0, OPTION_TARGET_IS_ZERO}, + {"force-share", no_argument, 0, 'U'}, + {"rate-limit", required_argument, 0, 'r'}, + {"parallel", required_argument, 0, 'm'}, + {"oob-writes", no_argument, 0, 'W'}, + {"copy-range-offloading", no_argument, 0, 'C'}, + {"progress", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:", + c = getopt_long(argc, argv, "hf:O:b:B:CcF:o:l:S:pt:T:nm:WUr:q", long_options, NULL); if (c == -1) { break; } - switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; + switch (c) { case 'h': - help(); + cmd_help(ccmd, "[-f SRC_FMT | --image-opts] [-T SRC_CACHE]\n" +" [-l SNAPSHOT] [--bitmaps [--skip-broken-bitmaps]] [--salvage]\n" +" [-O TGT_FMT | --target-image-opts] [-o TGT_FMT_OPTS] [-t TGT_CACHE]\n" +" [-b BACKING_FILE [-F BACKING_FMT]] [-S SPARSE_SIZE]\n" +" [-n] [--target-is-zero] [-c]\n" +" [-U] [-r RATE] [-m NUM_PARALLEL] [-W] [-C] [-p] [-q] [--object OBJDEF]\n" +" SRC_FILE [SRC_FILE2...] TGT_FILE\n" +, +" -f, --source-format SRC_FMT\n" +" specify format of all SRC_FILEs explicitly (default: probing is used)\n" +" --image-opts\n" +" treat each SRC_FILE as an option string (key=value,...), not a file name\n" +" (incompatible with -f|--source-format)\n" +" -T, --source-cache SRC_CACHE\n" +" source image(s) cache mode (" BDRV_DEFAULT_CACHE ")\n" +" -l, --snapshot SNAPSHOT\n" +" specify source snapshot\n" +" --bitmaps\n" +" also copy any persistent bitmaps present in source\n" +" --skip-broken-bitmaps\n" +" skip (do not error out) any broken bitmaps\n" +" --salvage\n" +" ignore errors on input (convert unreadable areas to zeros)\n" +" -O, --target-format TGT_FMT\n" +" specify TGT_FILE image format (default: raw)\n" +" --target-image-opts\n" +" treat TGT_FILE as an option string (key=value,...), not a file name\n" +" (incompatible with -O|--target-format)\n" +" -o, --target-format-options TGT_FMT_OPTS\n" +" TGT_FMT-specific options\n" +" -t, --target-cache TGT_CACHE\n" +" cache mode when opening output image (default: unsafe)\n" +" -b, --backing BACKING_FILE (was -B in <= 10.0)\n" +" create target image to be a CoW on top of BACKING_FILE\n" +" -F, --backing-format BACKING_FMT\n" /* -B used for -b in <=10.0 */ +" specify BACKING_FILE image format explicitly (default: probing is used)\n" +" -S, --sparse-size SPARSE_SIZE[bkKMGTPE]\n" +" specify number of consecutive zero bytes to treat as a gap on output\n" +" (rounded down to nearest 512 bytes), with optional multiplier suffix\n" +" -n, --no-create\n" +" omit target volume creation (e.g. on rbd)\n" +" --target-is-zero\n" +" indicates that the target volume is pre-zeroed\n" +" -c, --compress\n" +" create compressed output image (qcow and qcow2 formats only)\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" -r, --rate-limit RATE\n" +" I/O rate limit, in bytes per second\n" +" -m, --parallel NUM_PARALLEL\n" +" specify parallelism (default: 8)\n" +" -C, --copy-range-offloading\n" +" try to use copy offloading\n" +" -W, --oob-writes\n" +" enable out-of-order writes to improve performance\n" +" -p, --progress\n" +" display progress information\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" SRC_FILE...\n" +" one or more source image file names,\n" +" or option strings (key=value,..) with --source-image-opts\n" +" TGT_FILE\n" +" target (output) image file name,\n" +" or option string (key=value,..) with --target-image-opts\n" +); break; case 'f': fmt = optarg; break; - case 'O': - out_fmt = optarg; - break; - case 'B': - out_baseimg = optarg; - break; - case 'C': - s.copy_range = true; - break; - case 'c': - s.compressed = true; - break; - case 'F': - backing_fmt = optarg; + case OPTION_IMAGE_OPTS: + image_opts = true; break; - case 'o': - if (accumulate_options(&options, optarg) < 0) { - goto fail_getopt; - } + case 'T': + src_cache = optarg; break; case 'l': if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) { @@ -2328,11 +2392,41 @@ static int img_convert(int argc, char **argv) snapshot_name = optarg; } break; + case OPTION_BITMAPS: + bitmaps = true; + break; + case OPTION_SKIP_BROKEN: + skip_broken = true; + break; + case OPTION_SALVAGE: + s.salvage = true; + break; + case 'O': + out_fmt = optarg; + break; + case OPTION_TARGET_IMAGE_OPTS: + tgt_image_opts = true; + break; + case 'o': + if (accumulate_options(&options, optarg) < 0) { + goto fail_getopt; + } + break; + case 't': + cache = optarg; + break; + case 'B': /* <=10.0 */ + case 'b': + out_baseimg = optarg; + break; + case 'F': /* can't use -B as it used as -b in <=10.0 */ + backing_fmt = optarg; + break; case 'S': { int64_t sval; - sval = cvtnum("buffer size for sparse output", optarg); + sval = cvtnum("buffer size for sparse output", optarg, true); if (sval < 0) { goto fail_getopt; } else if (!QEMU_IS_ALIGNED(sval, BDRV_SECTOR_SIZE) || @@ -2348,67 +2442,53 @@ static int img_convert(int argc, char **argv) explict_min_sparse = true; break; } - case 'p': - progress = true; - break; - case 't': - cache = optarg; - break; - case 'T': - src_cache = optarg; - break; - case 'q': - s.quiet = true; - break; case 'n': skip_create = true; break; - case 'm': - if (qemu_strtol(optarg, NULL, 0, &s.num_coroutines) || - s.num_coroutines < 1 || s.num_coroutines > MAX_COROUTINES) { - error_report("Invalid number of coroutines. Allowed number of" - " coroutines is between 1 and %d", MAX_COROUTINES); - goto fail_getopt; - } + case OPTION_TARGET_IS_ZERO: + /* + * The user asserting that the target is blank has the + * same effect as the target driver supporting zero + * initialisation. + */ + s.has_zero_init = true; break; - case 'W': - s.wr_in_order = false; + case 'c': + s.compressed = true; break; case 'U': force_share = true; break; case 'r': - rate_limit = cvtnum("rate limit", optarg); + rate_limit = cvtnum("rate limit", optarg, true); if (rate_limit < 0) { goto fail_getopt; } break; - case OPTION_OBJECT: - user_creatable_process_cmdline(optarg); - break; - case OPTION_IMAGE_OPTS: - image_opts = true; + case 'm': + s.num_coroutines = cvtnum_full("number of coroutines", optarg, + false, 1, MAX_COROUTINES); + if (s.num_coroutines < 0) { + goto fail_getopt; + } break; - case OPTION_SALVAGE: - s.salvage = true; + case 'W': + s.wr_in_order = false; break; - case OPTION_TARGET_IMAGE_OPTS: - tgt_image_opts = true; + case 'C': + s.copy_range = true; break; - case OPTION_TARGET_IS_ZERO: - /* - * The user asserting that the target is blank has the - * same effect as the target driver supporting zero - * initialisation. - */ - s.has_zero_init = true; + case 'p': + progress = true; break; - case OPTION_BITMAPS: - bitmaps = true; + case 'q': + s.quiet = true; break; - case OPTION_SKIP_BROKEN: - skip_broken = true; + case OPTION_OBJECT: + user_creatable_process_cmdline(optarg); break; + default: + tryhelp(argv[0]); } } @@ -2999,79 +3079,82 @@ err: return NULL; } -static int img_info(int argc, char **argv) +static int img_info(const img_cmd_t *ccmd, int argc, char **argv) { int c; OutputFormat output_format = OFORMAT_HUMAN; bool chain = false; - const char *filename, *fmt, *output; + const char *filename, *fmt; BlockGraphInfoList *list; bool image_opts = false; bool force_share = false; fmt = NULL; - output = NULL; for(;;) { - int option_index = 0; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"format", required_argument, 0, 'f'}, - {"output", required_argument, 0, OPTION_OUTPUT}, - {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, - {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, {"force-share", no_argument, 0, 'U'}, + {"output", required_argument, 0, OPTION_OUTPUT}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:hU", - long_options, &option_index); + c = getopt_long(argc, argv, "hf:U", long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, "[-f FMT | --image-opts] [--backing-chain] [-U]\n" +" [--output human|json] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specify FILE image format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" --backing-chain\n" +" display information about the backing chain for copy-on-write overlays\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --output human|json\n" +" specify output format (default: human)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; case 'f': fmt = optarg; break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case OPTION_BACKING_CHAIN: + chain = true; + break; case 'U': force_share = true; break; case OPTION_OUTPUT: - output = optarg; - break; - case OPTION_BACKING_CHAIN: - chain = true; + output_format = parse_output_format(argv[0], optarg); break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - list = collect_image_info_list(image_opts, filename, fmt, chain, force_share); if (!list) { @@ -3224,13 +3307,13 @@ static inline bool entry_mergeable(const MapEntry *curr, const MapEntry *next) return true; } -static int img_map(int argc, char **argv) +static int img_map(const img_cmd_t *ccmd, int argc, char **argv) { int c; OutputFormat output_format = OFORMAT_HUMAN; BlockBackend *blk; BlockDriverState *bs; - const char *filename, *fmt, *output; + const char *filename, *fmt; int64_t length; MapEntry curr = { .length = 0 }, next; int ret = 0; @@ -3240,78 +3323,85 @@ static int img_map(int argc, char **argv) int64_t max_length = -1; fmt = NULL; - output = NULL; for (;;) { - int option_index = 0; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"format", required_argument, 0, 'f'}, - {"output", required_argument, 0, OPTION_OUTPUT}, - {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"force-share", no_argument, 0, 'U'}, {"start-offset", required_argument, 0, 's'}, {"max-length", required_argument, 0, 'l'}, + {"force-share", no_argument, 0, 'U'}, + {"output", required_argument, 0, OPTION_OUTPUT}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:s:l:hU", - long_options, &option_index); + c = getopt_long(argc, argv, "hf:s:l:U", + long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, "[-f FMT | --image-opts]\n" +" [--start-offset OFFSET] [--max-length LENGTH]\n" +" [--output human|json] [-U] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specify FILE image format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -s, --start-offset OFFSET\n" +" start at the given OFFSET in the image, not at the beginning\n" +" -l, --max-length LENGTH\n" +" process at most LENGTH bytes instead of up to the end of the image\n" +" --output human|json\n" +" specify output format name (default: human)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" the image file name, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; case 'f': fmt = optarg; break; - case 'U': - force_share = true; - break; - case OPTION_OUTPUT: - output = optarg; + case OPTION_IMAGE_OPTS: + image_opts = true; break; case 's': - start_offset = cvtnum("start offset", optarg); + start_offset = cvtnum("start offset", optarg, true); if (start_offset < 0) { return 1; } break; case 'l': - max_length = cvtnum("max length", optarg); + max_length = cvtnum("max length", optarg, true); if (max_length < 0) { return 1; } break; + case OPTION_OUTPUT: + output_format = parse_output_format(argv[0], optarg); + break; + case 'U': + force_share = true; + break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - blk = img_open(image_opts, filename, fmt, 0, false, false, force_share); if (!blk) { return 1; @@ -3368,18 +3458,19 @@ out: return ret < 0; } -#define SNAPSHOT_LIST 1 -#define SNAPSHOT_CREATE 2 -#define SNAPSHOT_APPLY 3 -#define SNAPSHOT_DELETE 4 +/* the same as options */ +#define SNAPSHOT_LIST 'l' +#define SNAPSHOT_CREATE 'c' +#define SNAPSHOT_APPLY 'a' +#define SNAPSHOT_DELETE 'd' -static int img_snapshot(int argc, char **argv) +static int img_snapshot(const img_cmd_t *ccmd, int argc, char **argv) { BlockBackend *blk; BlockDriverState *bs; QEMUSnapshotInfo sn; - char *filename, *snapshot_name = NULL; - int c, ret = 0, bdrv_oflags; + char *filename, *fmt = NULL, *snapshot_name = NULL; + int c, ret = 0; int action = 0; bool quiet = false; Error *err = NULL; @@ -3387,86 +3478,100 @@ static int img_snapshot(int argc, char **argv) bool force_share = false; int64_t rt; - bdrv_oflags = BDRV_O_RDWR; /* Parse commandline parameters */ for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"list", no_argument, 0, SNAPSHOT_LIST}, + {"apply", required_argument, 0, SNAPSHOT_APPLY}, + {"create", required_argument, 0, SNAPSHOT_CREATE}, + {"delete", required_argument, 0, SNAPSHOT_DELETE}, {"force-share", no_argument, 0, 'U'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":la:c:d:hqU", + c = getopt_long(argc, argv, "hf:la:c:d:Uq", long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - return 0; - case 'l': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_LIST; - bdrv_oflags &= ~BDRV_O_RDWR; /* no need for RW */ + cmd_help(ccmd, "[-f FMT | --image-opts] [-l | -a|-c|-d SNAPSHOT]\n" +" [-U] [-q] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specify FILE format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -l, --list\n" +" list snapshots in FILE (default action if no -l|-c|-a|-d is given)\n" +" -c, --create SNAPSHOT\n" +" create named snapshot\n" +" -a, --apply SNAPSHOT\n" +" apply named snapshot to the base\n" +" -d, --delete SNAPSHOT\n" +" delete named snapshot\n" +" (only one of -l|-c|-a|-d can be specified)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts) to operate on\n" +); break; - case 'a': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_APPLY; - snapshot_name = optarg; + case 'f': + fmt = optarg; break; - case 'c': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_CREATE; - snapshot_name = optarg; + case OPTION_IMAGE_OPTS: + image_opts = true; break; - case 'd': + case SNAPSHOT_LIST: + case SNAPSHOT_APPLY: + case SNAPSHOT_CREATE: + case SNAPSHOT_DELETE: if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); + error_exit(argv[0], "Cannot mix '-l', '-a', '-c', '-d'"); return 0; } - action = SNAPSHOT_DELETE; + action = c; snapshot_name = optarg; break; - case 'q': - quiet = true; - break; case 'U': force_share = true; break; + case 'q': + quiet = true; + break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; + if (!action) { + action = SNAPSHOT_LIST; + } + /* Open the image */ - blk = img_open(image_opts, filename, NULL, bdrv_oflags, false, quiet, - force_share); + blk = img_open(image_opts, filename, fmt, + action == SNAPSHOT_LIST ? 0 : BDRV_O_RDWR, + false, quiet, force_share); if (!blk) { return 1; } @@ -3533,7 +3638,7 @@ static int img_snapshot(int argc, char **argv) return 0; } -static int img_rebase(int argc, char **argv) +static int img_rebase(const img_cmd_t *ccmd, int argc, char **argv) { BlockBackend *blk = NULL, *blk_old_backing = NULL, *blk_new_backing = NULL; uint8_t *buf_old = NULL; @@ -3564,45 +3669,89 @@ static int img_rebase(int argc, char **argv) for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"force-share", no_argument, 0, 'U'}, + {"cache", required_argument, 0, 't'}, {"compress", no_argument, 0, 'c'}, + {"backing", required_argument, 0, 'b'}, + {"backing-format", required_argument, 0, 'B'}, + {"backing-cache", required_argument, 0, 'T'}, + {"backing-unsafe", no_argument, 0, 'u'}, + {"force-share", no_argument, 0, 'U'}, + {"progress", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:b:upt:T:qUc", + c = getopt_long(argc, argv, "hf:t:cb:F:B:T:uUpq", long_options, NULL); if (c == -1) { break; } - switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; + switch (c) { case 'h': - help(); + cmd_help(ccmd, "[-f FMT | --image-opts] [-t CACHE]\n" +" [-b BACKING_FILE [-B BACKING_FMT] [-T BACKING_CACHE]] [-u]\n" +" [-c] [-U] [-p] [-q] [--object OBJDEF] FILE\n" +, +" -f, --format FMT\n" +" specify FILE format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -t, --cache CACHE\n" +" cache mode for FILE (default: " BDRV_DEFAULT_CACHE ")\n" +" -b, --backing BACKING_FILE|\"\"\n" +" rebase onto this file (specify empty name for no backing file)\n" +" -B, --backing-format BACKING_FMT (was -F in <=10.0)\n" +" specify format for BACKING_FILE explicitly (default: probing is used)\n" +" -T, --backing-cache CACHE\n" +" BACKING_FILE cache mode (default: " BDRV_DEFAULT_CACHE ")\n" +" -u, --backing-unsafe\n" +" do not fail if BACKING_FILE can not be read\n" +" -c, --compress\n" +" compress image (when image supports this)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" -p, --progress\n" +" display progress information\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); return 0; case 'f': fmt = optarg; break; - case 'F': - out_basefmt = optarg; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case 't': + cache = optarg; break; case 'b': out_baseimg = optarg; break; + case 'F': /* <=10.0 */ + case 'B': + out_basefmt = optarg; + break; case 'u': unsafe = 1; break; + case 'c': + compress = true; + break; + case 'U': + force_share = true; + break; case 'p': progress = 1; break; - case 't': - cache = optarg; - break; case 'T': src_cache = optarg; break; @@ -3612,15 +3761,8 @@ static int img_rebase(int argc, char **argv) case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; - case 'U': - force_share = true; - break; - case 'c': - compress = true; - break; + default: + tryhelp(argv[0]); } } @@ -3629,10 +3771,11 @@ static int img_rebase(int argc, char **argv) } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } if (!unsafe && !out_baseimg) { - error_exit("Must specify backing file (-b) or use unsafe mode (-u)"); + error_exit(argv[0], + "Must specify backing file (-b) or use unsafe mode (-u)"); } filename = argv[optind++]; @@ -4026,11 +4169,11 @@ out: return 0; } -static int img_resize(int argc, char **argv) +static int img_resize(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; int c, ret, relative; - const char *filename, *fmt, *size; + const char *filename = NULL, *fmt = NULL, *size = NULL; int64_t n, total_size, current_size; bool quiet = false; BlockBackend *blk = NULL; @@ -4053,50 +4196,52 @@ static int img_resize(int argc, char **argv) bool image_opts = false; bool shrink = false; - /* Remove size from argv manually so that negative numbers are not treated - * as options by getopt. */ - if (argc < 3) { - error_exit("Not enough arguments"); - return 1; - } - - size = argv[--argc]; - /* Parse getopt arguments */ - fmt = NULL; for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"preallocation", required_argument, 0, OPTION_PREALLOCATION}, {"shrink", no_argument, 0, OPTION_SHRINK}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:hq", + c = getopt_long(argc, argv, "-hf:q", long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - break; + cmd_help(ccmd, "[-f FMT | --image-opts] [--preallocation PREALLOC] [--shrink]\n" +" [-q] [--object OBJDEF] FILE [+-]SIZE[bkKMGTPE]\n" +, +" -f, --format FMT\n" +" specify FILE format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,...), not a file name\n" +" (incompatible with -f|--format)\n" +" --shrink\n" +" allow operation when the new size is smaller than the original\n" +" --preallocation PREALLOC\n" +" specify FMT-specific preallocation type for the new areas\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +" [+-]SIZE[bkKMGTPE]\n" +" new image size or amount by which to shrink (-)/grow (+),\n" +" with optional multiplier suffix (powers of 1024, default is bytes)\n" +); + return 0; case 'f': fmt = optarg; break; - case 'q': - quiet = true; - break; - case OPTION_OBJECT: - user_creatable_process_cmdline(optarg); - break; case OPTION_IMAGE_OPTS: image_opts = true; break; @@ -4111,12 +4256,43 @@ static int img_resize(int argc, char **argv) case OPTION_SHRINK: shrink = true; break; + case 'q': + quiet = true; + break; + case OPTION_OBJECT: + user_creatable_process_cmdline(optarg); + break; + case 1: /* a non-optional argument */ + if (!filename) { + filename = optarg; + /* see if we have -size (number) next to filename */ + if (optind < argc) { + size = argv[optind]; + if (size[0] == '-' && size[1] >= '0' && size[1] <= '9') { + ++optind; + } else { + size = NULL; + } + } + } else if (!size) { + size = optarg; + } else { + error_exit(argv[0], "Extra argument(s) in command line"); + } + break; + default: + tryhelp(argv[0]); } } - if (optind != argc - 1) { - error_exit("Expecting image file name and size"); + if (!filename && optind < argc) { + filename = argv[optind++]; + } + if (!size && optind < argc) { + size = argv[optind++]; + } + if (!filename || !size || optind < argc) { + error_exit(argv[0], "Expecting image file name and size"); } - filename = argv[optind++]; /* Choose grow, shrink, or absolute resize mode */ switch (size[0]) { @@ -4239,7 +4415,7 @@ static int print_amend_option_help(const char *format) return 0; } -static int img_amend(int argc, char **argv) +static int img_amend(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; int c, ret = 0; @@ -4259,26 +4435,48 @@ static int img_amend(int argc, char **argv) for (;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"options", required_argument, 0, 'o'}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 't'}, {"force", no_argument, 0, OPTION_FORCE}, + {"progress", no_argument, 0, 'p'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":ho:f:t:pq", + c = getopt_long(argc, argv, "ho:f:t:pq", long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, "-o FMT_OPTS [-f FMT | --image-opts]\n" +" [-t CACHE] [--force] [-p] [-q] [--object OBJDEF] FILE\n" +, +" -o, --options FMT_OPTS\n" +" FMT-specfic format options (required)\n" +" -f, --format FMT\n" +" specify FILE format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -t, --cache CACHE\n" +" cache mode for FILE (default: " BDRV_DEFAULT_CACHE ")\n" +" --force\n" +" allow certain unsafe operations\n" +" -p, --progres\n" +" show operation progress\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; case 'o': if (accumulate_options(&options, optarg) < 0) { @@ -4289,9 +4487,15 @@ static int img_amend(int argc, char **argv) case 'f': fmt = optarg; break; + case OPTION_IMAGE_OPTS: + image_opts = true; + break; case 't': cache = optarg; break; + case OPTION_FORCE: + force = true; + break; case 'p': progress = true; break; @@ -4301,17 +4505,13 @@ static int img_amend(int argc, char **argv) case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; - case OPTION_FORCE: - force = true; - break; + default: + tryhelp(argv[0]); } } if (!options) { - error_exit("Must specify options (-o)"); + error_exit(argv[0], "Must specify options (-o)"); } if (quiet) { @@ -4507,7 +4707,7 @@ static void bench_cb(void *opaque, int ret) } } -static int img_bench(int argc, char **argv) +static int img_bench(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret = 0; const char *fmt = NULL, *filename; @@ -4517,9 +4717,9 @@ static int img_bench(int argc, char **argv) int count = 75000; int depth = 64; int64_t offset = 0; - size_t bufsize = 4096; + ssize_t bufsize = 4096; int pattern = 0; - size_t step = 0; + ssize_t step = 0; int flush_interval = 0; bool drain_on_flush = true; int64_t image_size; @@ -4535,53 +4735,105 @@ static int img_bench(int argc, char **argv) for (;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"flush-interval", required_argument, 0, OPTION_FLUSH_INTERVAL}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 't'}, + {"count", required_argument, 0, 'c'}, + {"depth", required_argument, 0, 'd'}, + {"offset", required_argument, 0, 'o'}, + {"buffer-size", required_argument, 0, 's'}, + {"step-size", required_argument, 0, 'S'}, + {"write", no_argument, 0, 'w'}, {"pattern", required_argument, 0, OPTION_PATTERN}, + {"flush-interval", required_argument, 0, OPTION_FLUSH_INTERVAL}, {"no-drain", no_argument, 0, OPTION_NO_DRAIN}, + {"aio", required_argument, 0, 'i'}, + {"native", no_argument, 0, 'n'}, {"force-share", no_argument, 0, 'U'}, + {"quiet", no_argument, 0, 'q'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hc:d:f:ni:o:qs:S:t:wU", long_options, - NULL); + c = getopt_long(argc, argv, "hf:t:c:d:o:s:S:wi:nUq", + long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); + case 'h': + cmd_help(ccmd, "[-f FMT | --image-opts] [-t CACHE]\n" +" [-c COUNT] [-d DEPTH] [-o OFFSET] [-s BUFFER_SIZE] [-S STEP_SIZE]\n" +" [-w [--pattern PATTERN] [--flush-interval INTERVAL [--no-drain]]]\n" +" [-i AIO] [-n] [-U] [-q] FILE\n" +, +" -f, --format FMT\n" +" specify FILE format explicitly\n" +" --image-opts\n" +" indicates that FILE is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -t, --cache CACHE\n" +" cache mode for FILE (default: " BDRV_DEFAULT_CACHE ")\n" +" -c, --count COUNT\n" +" number of I/O requests to perform\n" +" -d, --depth DEPTH\n" +" number of requests to perform in parallel\n" +" -o, --offset OFFSET\n" +" start first request at this OFFSET\n" +" -s, --buffer-size BUFFER_SIZE[bkKMGTPE]\n" +" size of each I/O request, with optional multiplier suffix\n" +" (powers of 1024, default is 4K)\n" +" -S, --step-size STEP_SIZE[bkKMGTPE]\n" +" each next request offset increment, with optional multiplier suffix\n" +" (powers of 1024, default is the same as BUFFER_SIZE)\n" +" -w, --write\n" +" perform write test (default is read)\n" +" --pattern PATTERN\n" +" write this pattern byte instead of zero\n" +" --flush-interval FLUSH_INTERVAL\n" +" issue flush after this number of requests\n" +" --no-drain\n" +" do not wait when flushing pending requests\n" +" -i, --aio AIO\n" +" async-io backend (threads, native, io_uring)\n" +" -n, --native\n" +" use native AIO backend if possible\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" -q, --quiet\n" +" quiet mode (produce only error messages if any)\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +); break; - case '?': - unrecognized_option(argv[optind - 1]); + case 'f': + fmt = optarg; break; - case 'h': - help(); + case OPTION_IMAGE_OPTS: + image_opts = true; + break; + case 't': + ret = bdrv_parse_cache_mode(optarg, &flags, &writethrough); + if (ret < 0) { + error_report("Invalid cache mode"); + ret = -1; + goto out; + } break; case 'c': - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) { - error_report("Invalid request count specified"); + count = cvtnum_full("request count", optarg, false, 1, INT_MAX); + if (count < 0) { return 1; } - count = res; break; - } case 'd': - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) <= 0 || res > INT_MAX) { - error_report("Invalid queue depth specified"); + depth = cvtnum_full("queue depth", optarg, false, 1, INT_MAX); + if (depth < 0) { return 1; } - depth = res; - break; - } - case 'f': - fmt = optarg; break; case 'n': flags |= BDRV_O_NATIVE_AIO; @@ -4595,89 +4847,59 @@ static int img_bench(int argc, char **argv) } break; case 'o': - { - offset = cvtnum("offset", optarg); + offset = cvtnum("offset", optarg, true); if (offset < 0) { return 1; } break; - } - break; - case 'q': - quiet = true; - break; case 's': - { - int64_t sval; - - sval = cvtnum_full("buffer size", optarg, 0, INT_MAX); - if (sval < 0) { + bufsize = cvtnum_full("buffer size", optarg, true, 1, INT_MAX); + if (bufsize < 0) { return 1; } - - bufsize = sval; break; - } case 'S': - { - int64_t sval; - - sval = cvtnum_full("step_size", optarg, 0, INT_MAX); - if (sval < 0) { + step = cvtnum_full("step size", optarg, true, 0, INT_MAX); + if (step < 0) { return 1; } - - step = sval; - break; - } - case 't': - ret = bdrv_parse_cache_mode(optarg, &flags, &writethrough); - if (ret < 0) { - error_report("Invalid cache mode"); - ret = -1; - goto out; - } break; case 'w': flags |= BDRV_O_RDWR; is_write = true; break; - case 'U': - force_share = true; - break; case OPTION_PATTERN: - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > 0xff) { - error_report("Invalid pattern byte specified"); + pattern = cvtnum_full("pattern byte", optarg, false, 0, 0xff); + if (pattern < 0) { return 1; } - pattern = res; break; - } case OPTION_FLUSH_INTERVAL: - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) { - error_report("Invalid flush interval specified"); + flush_interval = cvtnum_full("flush interval", optarg, + false, 0, INT_MAX); + if (flush_interval < 0) { return 1; } - flush_interval = res; break; - } case OPTION_NO_DRAIN: drain_on_flush = false; break; - case OPTION_IMAGE_OPTS: - image_opts = true; + case 'U': + force_share = true; break; + case 'q': + quiet = true; + break; + case OPTION_OBJECT: + user_creatable_process_cmdline(optarg); + break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[argc - 1]; @@ -4777,7 +4999,7 @@ typedef struct ImgBitmapAction { QSIMPLEQ_ENTRY(ImgBitmapAction) next; } ImgBitmapAction; -static int img_bitmap(int argc, char **argv) +static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; int c, ret = 1; @@ -4799,48 +5021,69 @@ static int img_bitmap(int argc, char **argv) for (;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"add", no_argument, 0, OPTION_ADD}, + {"granularity", required_argument, 0, 'g'}, {"remove", no_argument, 0, OPTION_REMOVE}, {"clear", no_argument, 0, OPTION_CLEAR}, {"enable", no_argument, 0, OPTION_ENABLE}, {"disable", no_argument, 0, OPTION_DISABLE}, {"merge", required_argument, 0, OPTION_MERGE}, - {"granularity", required_argument, 0, 'g'}, {"source-file", required_argument, 0, 'b'}, {"source-format", required_argument, 0, 'F'}, + {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":b:f:F:g:h", long_options, NULL); + c = getopt_long(argc, argv, "hf:g:b:F:", + long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - break; - case 'b': - src_filename = optarg; + cmd_help(ccmd, "[-f FMT | --image-opts]\n" +" ( --add [-g SIZE] | --remove | --clear | --enable | --disable |\n" +" --merge SOURCE [-b SRC_FILE [-F SRC_FMT]] )..\n" +" [--object OBJDEF] FILE BITMAP\n" +, +" -f, --format FMT\n" +" specify FILE format explicitly (default: probing is used)\n" +" --image-opts\n" +" treat FILE as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" --add\n" +" creates BITMAP in FILE, enables to record future edits\n" +" -g, --granularity SIZE[bKMGTPE]\n" +" sets non-default granularity for the bitmap being added,\n" +" with optional multiplier suffix (in powers of 1024)\n" +" --remove\n" +" removes BITMAP from FILE\n" +" --clear\n" +" clears BITMAP in FILE\n" +" --enable, --disable\n" +" starts and stops recording future edits to BITMAP in FILE\n" +" --merge SOURCE\n" +" merges contents of the SOURCE bitmap into BITMAP in FILE\n" +" -b, --source-file SRC_FILE\n" +" select alternative source file for --merge\n" +" -F, --source-format SRC_FMT\n" +" specify format for SRC_FILE explicitly\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" FILE\n" +" name of the image file, or option string (key=value,..)\n" +" with --image-opts, to operate on\n" +" BITMAP\n" +" name of the bitmap to add, remove, clear, enable, disable or merge to\n" +); break; case 'f': fmt = optarg; break; - case 'F': - src_fmt = optarg; - break; - case 'g': - granularity = cvtnum("granularity", optarg); - if (granularity < 0) { - return 1; - } + case OPTION_IMAGE_OPTS: + image_opts = true; break; case OPTION_ADD: act = g_new0(ImgBitmapAction, 1); @@ -4848,6 +5091,12 @@ static int img_bitmap(int argc, char **argv) QSIMPLEQ_INSERT_TAIL(&actions, act, next); add = true; break; + case 'g': + granularity = cvtnum("granularity", optarg, true); + if (granularity < 0) { + return 1; + } + break; case OPTION_REMOVE: act = g_new0(ImgBitmapAction, 1); act->act = BITMAP_REMOVE; @@ -4875,12 +5124,17 @@ static int img_bitmap(int argc, char **argv) QSIMPLEQ_INSERT_TAIL(&actions, act, next); merge = true; break; + case 'b': + src_filename = optarg; + break; + case 'F': + src_fmt = optarg; + break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } @@ -5023,7 +5277,7 @@ static int img_dd_bs(const char *arg, { int64_t res; - res = cvtnum_full("bs", arg, 1, INT_MAX); + res = cvtnum_full("bs", arg, true, 1, INT_MAX); if (res < 0) { return 1; @@ -5037,7 +5291,7 @@ static int img_dd_count(const char *arg, struct DdIo *in, struct DdIo *out, struct DdInfo *dd) { - dd->count = cvtnum("count", arg); + dd->count = cvtnum("count", arg, true); if (dd->count < 0) { return 1; @@ -5068,7 +5322,7 @@ static int img_dd_skip(const char *arg, struct DdIo *in, struct DdIo *out, struct DdInfo *dd) { - in->offset = cvtnum("skip", arg); + in->offset = cvtnum("skip", arg, true); if (in->offset < 0) { return 1; @@ -5077,7 +5331,7 @@ static int img_dd_skip(const char *arg, return 0; } -static int img_dd(int argc, char **argv) +static int img_dd(const img_cmd_t *ccmd, int argc, char **argv) { int ret = 0; char *arg = NULL; @@ -5121,31 +5375,54 @@ static int img_dd(int argc, char **argv) }; const struct option long_options[] = { { "help", no_argument, 0, 'h'}, - { "object", required_argument, 0, OPTION_OBJECT}, + { "format", required_argument, 0, 'f'}, { "image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + { "output-format", required_argument, 0, 'O'}, { "force-share", no_argument, 0, 'U'}, + { "object", required_argument, 0, OPTION_OBJECT}, { 0, 0, 0, 0 } }; - while ((c = getopt_long(argc, argv, ":hf:O:U", long_options, NULL))) { + while ((c = getopt_long(argc, argv, "hf:O:U", long_options, NULL))) { if (c == EOF) { break; } switch (c) { - case 'O': - out_fmt = optarg; + case 'h': + cmd_help(ccmd, "[-f FMT|--image-opts] [-O OUTPUT_FMT] [-U]\n" +" [--object OBJDEF] [bs=BLOCK_SIZE] [count=BLOCKS] if=INPUT of=OUTPUT\n" +, +" -f, --format FMT\n" +" specify format for INPUT explicitly (default: probing is used)\n" +" --image-opts\n" +" treat INPUT as an option string (key=value,..), not a file name\n" +" (incompatible with -f|--format)\n" +" -O, --output-format OUTPUT_FMT\n" +" format of the OUTPUT (default: raw)\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" bs=BLOCK_SIZE[bKMGTP]\n" +" size of the I/O block, with optional multiplier suffix (powers of 1024)\n" +" (default: 512)\n" +" count=COUNT\n" +" number of blocks to convert (default whole INPUT)\n" +" if=INPUT\n" +" name of the file, or option string (key=value,..)\n" +" with --image-opts, to use for input\n" +" of=OUTPUT\n" +" output file name to create (will be overridden if alrady exists)\n" +); break; case 'f': fmt = optarg; break; - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); + case OPTION_IMAGE_OPTS: + image_opts = true; break; - case 'h': - help(); + case 'O': + out_fmt = optarg; break; case 'U': force_share = true; @@ -5153,9 +5430,8 @@ static int img_dd(int argc, char **argv) case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; + default: + tryhelp(argv[0]); } } @@ -5345,17 +5621,8 @@ static void dump_json_block_measure_info(BlockMeasureInfo *info) g_string_free(str, true); } -static int img_measure(int argc, char **argv) +static int img_measure(const img_cmd_t *ccmd, int argc, char **argv) { - static const struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"object", required_argument, 0, OPTION_OBJECT}, - {"output", required_argument, 0, OPTION_OUTPUT}, - {"size", required_argument, 0, OPTION_SIZE}, - {"force-share", no_argument, 0, 'U'}, - {0, 0, 0, 0} - }; OutputFormat output_format = OFORMAT_HUMAN; BlockBackend *in_blk = NULL; BlockDriver *drv; @@ -5370,29 +5637,67 @@ static int img_measure(int argc, char **argv) QemuOpts *sn_opts = NULL; QemuOptsList *create_opts = NULL; bool image_opts = false; - uint64_t img_size = UINT64_MAX; + int64_t img_size = -1; BlockMeasureInfo *info = NULL; Error *local_err = NULL; int ret = 1; int c; - while ((c = getopt_long(argc, argv, "hf:O:o:l:U", + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"source-format", required_argument, 0, 'f'}, /* img_convert */ + {"format", required_argument, 0, 'f'}, + {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"source-image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, /* img_convert */ + {"snapshot", required_argument, 0, 'l'}, + {"target-format", required_argument, 0, 'O'}, + {"target-format-options", required_argument, 0, 'o'}, /* img_convert */ + {"options", required_argument, 0, 'o'}, + {"force-share", no_argument, 0, 'U'}, + {"output", required_argument, 0, OPTION_OUTPUT}, + {"object", required_argument, 0, OPTION_OBJECT}, + {"size", required_argument, 0, 's'}, + {0, 0, 0, 0} + }; + + while ((c = getopt_long(argc, argv, "hf:l:O:o:Us:", long_options, NULL)) != -1) { switch (c) { - case '?': case 'h': - help(); + cmd_help(ccmd, "[-f FMT|--image-opts] [-l SNAPSHOT]\n" +" [-O TARGET_FMT] [-o TARGET_FMT_OPTS] [--output human|json]\n" +" [--object OBJDEF] (--size SIZE | FILE)\n" +, +" -f, --format\n" +" specify format of FILE explicitly (default: probing is used)\n" +" --image-opts\n" +" indicates that FILE is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -l, --snapshot SNAPSHOT\n" +" use this snapshot in FILE as source\n" +" -O, --target-format TARGET_FMT\n" +" desired target/output image format (default: raw)\n" +" -o TARGET_FMT_OPTS\n" +" options specific to TARGET_FMT\n" +" --output human|json\n" +" output format (default: human)\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" --object OBJDEF\n" +" defines QEMU user-creatable object\n" +" -s, --size SIZE[bKMGTPE]\n" +" measure file size for given image size,\n" +" with optional multiplier suffix (powers of 1024)\n" +" FILE\n" +" measure file size required to convert from FILE (either a file name\n" +" or an option string (key=value,..) with --image-options)\n" +); break; case 'f': fmt = optarg; break; - case 'O': - out_fmt = optarg; - break; - case 'o': - if (accumulate_options(&options, optarg) < 0) { - goto out; - } + case OPTION_IMAGE_OPTS: + image_opts = true; break; case 'l': if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) { @@ -5407,37 +5712,31 @@ static int img_measure(int argc, char **argv) snapshot_name = optarg; } break; + case 'O': + out_fmt = optarg; + break; + case 'o': + if (accumulate_options(&options, optarg) < 0) { + goto out; + } + break; case 'U': force_share = true; break; + case OPTION_OUTPUT: + output_format = parse_output_format(argv[0], optarg); + break; case OPTION_OBJECT: user_creatable_process_cmdline(optarg); break; - case OPTION_IMAGE_OPTS: - image_opts = true; - break; - case OPTION_OUTPUT: - if (!strcmp(optarg, "json")) { - output_format = OFORMAT_JSON; - } else if (!strcmp(optarg, "human")) { - output_format = OFORMAT_HUMAN; - } else { - error_report("--output must be used with human or json " - "as argument."); + case 's': + img_size = cvtnum("image size", optarg, true); + if (img_size < 0) { goto out; } break; - case OPTION_SIZE: - { - int64_t sval; - - sval = cvtnum("image size", optarg); - if (sval < 0) { - goto out; - } - img_size = (uint64_t)sval; - } - break; + default: + tryhelp(argv[0]); } } @@ -5452,11 +5751,11 @@ static int img_measure(int argc, char **argv) error_report("--image-opts, -f, and -l require a filename argument."); goto out; } - if (filename && img_size != UINT64_MAX) { + if (filename && img_size != -1) { error_report("--size N cannot be used together with a filename."); goto out; } - if (!filename && img_size == UINT64_MAX) { + if (!filename && img_size == -1) { error_report("Either --size N or one filename must be specified."); goto out; } @@ -5504,7 +5803,7 @@ static int img_measure(int argc, char **argv) goto out; } } - if (img_size != UINT64_MAX) { + if (img_size != -1) { qemu_opt_set_number(opts, BLOCK_OPT_SIZE, img_size, &error_abort); } @@ -5538,13 +5837,49 @@ out: } static const img_cmd_t img_cmds[] = { -#define DEF(option, callback, arg_string) \ - { option, callback }, -#include "qemu-img-cmds.h" -#undef DEF + { "amend", img_amend, + "Update format-specific options of the image" }, + { "bench", img_bench, + "Run a simple image benchmark" }, + { "bitmap", img_bitmap, + "Perform modifications of the persistent bitmap in the image" }, + { "check", img_check, + "Check basic image integrity" }, + { "commit", img_commit, + "Commit image to its backing file" }, + { "compare", img_compare, + "Check if two images have the same contents" }, + { "convert", img_convert, + "Copy one or more images to another with optional format conversion" }, + { "create", img_create, + "Create and format a new image file" }, + { "dd", img_dd, + "Copy input to output with optional format conversion" }, + { "info", img_info, + "Display information about the image" }, + { "map", img_map, + "Dump image metadata" }, + { "measure", img_measure, + "Calculate the file size required for a new image" }, + { "rebase", img_rebase, + "Change the backing file of the image" }, + { "resize", img_resize, + "Resize the image" }, + { "snapshot", img_snapshot, + "List or manipulate snapshots in the image" }, { NULL, NULL, }, }; +static void format_print(void *opaque, const char *name) +{ + int *np = opaque; + if (*np + strlen(name) > 75) { + printf("\n "); + *np = 1; + } + *np += printf(" %s", name); +} + int main(int argc, char **argv) { const img_cmd_t *cmd; @@ -5572,23 +5907,39 @@ int main(int argc, char **argv) module_call_init(MODULE_INIT_QOM); bdrv_init(); - if (argc < 2) { - error_exit("Not enough arguments"); - } qemu_add_opts(&qemu_source_opts); qemu_add_opts(&qemu_trace_opts); - while ((c = getopt_long(argc, argv, "+:hVT:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "+hVT:", long_options, NULL)) != -1) { switch (c) { - case ':': - missing_argument(argv[optind - 1]); - return 0; - case '?': - unrecognized_option(argv[optind - 1]); - return 0; case 'h': - help(); + printf( +QEMU_IMG_VERSION +"QEMU disk image utility. Usage:\n" +"\n" +" qemu-img [standard options] COMMAND [--help | command options]\n" +"\n" +"Standard options:\n" +" -h, --help\n" +" display this help and exit\n" +" -V, --version\n" +" display version info and exit\n" +" -T,--trace TRACE\n" +" specify tracing options:\n" +" [[enable=]<pattern>][,events=<file>][,file=<file>]\n" +"\n" +"Recognized commands (run qemu-img COMMAND --help for command-specific help):\n\n"); + for (cmd = img_cmds; cmd->name != NULL; cmd++) { + printf(" %s - %s\n", cmd->name, cmd->description); + } + printf("\nSupported image formats:\n"); + c = 99; /* force a newline */ + bdrv_iterate_format(format_print, &c, false); + if (c) { + printf("\n"); + } + printf("\n" QEMU_HELP_BOTTOM "\n"); return 0; case 'V': printf(QEMU_IMG_VERSION); @@ -5596,18 +5947,16 @@ int main(int argc, char **argv) case 'T': trace_opt_parse(optarg); break; + default: + tryhelp(argv[0]); } } - cmdname = argv[optind]; - - /* reset getopt_long scanning */ - argc -= optind; - if (argc < 1) { - return 0; + if (optind >= argc) { + error_exit(argv[0], "Not enough arguments"); } - argv += optind; - qemu_reset_optind(); + + cmdname = argv[optind]; if (!trace_init_backends()) { exit(1); @@ -5618,10 +5967,16 @@ int main(int argc, char **argv) /* find the command */ for (cmd = img_cmds; cmd->name != NULL; cmd++) { if (!strcmp(cmdname, cmd->name)) { - return cmd->handler(argc, argv); + g_autofree char *argv0 = g_strdup_printf("%s %s", argv[0], cmdname); + /* reset options and getopt processing (incl return order) */ + argv += optind; + argc -= optind; + qemu_reset_optind(); + argv[0] = argv0; + return cmd->handler(cmd, argc, argv); } } /* not found */ - error_exit("Command not found: %s", cmdname); + error_exit(argv[0], "Command not found: %s", cmdname); } diff --git a/qemu-options.hx b/qemu-options.hx index 1f862b1..ab23f14 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -38,6 +38,7 @@ DEF("machine", HAS_ARG, QEMU_OPTION_machine, \ " nvdimm=on|off controls NVDIMM support (default=off)\n" " memory-encryption=@var{} memory encryption object to use (default=none)\n" " hmat=on|off controls ACPI HMAT support (default=off)\n" + " spcr=on|off controls ACPI SPCR support (default=on)\n" #ifdef CONFIG_POSIX " aux-ram-share=on|off allocate auxiliary guest RAM as shared (default: off)\n" #endif @@ -105,6 +106,10 @@ SRST Enables or disables ACPI Heterogeneous Memory Attribute Table (HMAT) support. The default is off. + ``spcr=on|off`` + Enables or disables ACPI Serial Port Console Redirection Table + (SPCR) support. The default is on. + ``aux-ram-share=on|off`` Allocate auxiliary guest RAM as an anonymous file that is shareable with an external process. This option applies to @@ -2281,6 +2286,8 @@ DEF("spice", HAS_ARG, QEMU_OPTION_spice, " [,streaming-video=[off|all|filter]][,disable-copy-paste=on|off]\n" " [,disable-agent-file-xfer=on|off][,agent-mouse=[on|off]]\n" " [,playback-compression=[on|off]][,seamless-migration=[on|off]]\n" + " [,video-codec=<codec>\n" + " [,max-refresh-rate=rate\n" " [,gl=[on|off]][,rendernode=<file>]\n" " enable spice\n" " at least one of {port, tls-port} is mandatory\n", @@ -2369,6 +2376,17 @@ SRST ``seamless-migration=[on|off]`` Enable/disable spice seamless migration. Default is off. + ``video-codec=<codec>`` + Provide the preferred codec the Spice server should use with the + Gstreamer encoder. This option is only relevant when gl=on is + specified. If no codec is provided, then the codec gstreamer:h264 + would be used as default. And, for the case where gl=off, the + default codec to be used is determined by the Spice server. + + ``max-refresh-rate=rate`` + Provide the maximum refresh rate (or FPS) at which the encoding + requests should be sent to the Spice server. Default would be 30. + ``gl=[on|off]`` Enable/disable OpenGL context. Default is off. @@ -2796,6 +2814,26 @@ DEFHEADING() DEFHEADING(Network options:) DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, +#ifdef CONFIG_PASST + "-netdev passt,id=str[,path=file][,quiet=on|off][,vhost-user=on|off]\n" + "[,mtu=mtu][,address=addr][,netmask=mask][,mac=addr][,gateway=addr]\n" + " [,interface=name][,outbound=address][,outbound-if4=name]\n" + " [,outbound-if6=name][,dns=addr][,search=list][,fqdn=name]\n" + " [,dhcp-dns=on|off][,dhcp-search=on|off][,map-host-loopback=addr]\n" + " [,map-guest-addr=addr][,dns-forward=addr][,dns-host=addr]\n" + " [,tcp=on|off][,udp=on|off][,icmp=on|off][,dhcp=on|off]\n" + " [,ndp=on|off][,dhcpv6=on|off][,ra=on|off][,freebind=on|off]\n" + " [,ipv4=on|off][,ipv6=on|off][,tcp-ports=spec][,udp-ports=spec]\n" + " [,param=list]\n" + " configure a passt network backend with ID 'str'\n" + " if 'path' is not provided 'passt' will be started according to PATH\n" + " by default, informational message of passt are not displayed (quiet=on)\n" + " to display this message, use 'quiet=off'\n" + " by default, passt will be started in socket-based mode, to enable vhost-mode,\n" + " use 'vhost-user=on'\n" + " for details on other options, refer to passt(1)\n" + " 'param' allows to pass any option defined by passt(1)\n" +#endif #ifdef CONFIG_SLIRP "-netdev user,id=str[,ipv4=on|off][,net=addr[/mask]][,host=addr]\n" " [,ipv6=on|off][,ipv6-net=addr[/int]][,ipv6-host=addr]\n" @@ -2909,6 +2947,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, #ifdef CONFIG_AF_XDP "-netdev af-xdp,id=str,ifname=name[,mode=native|skb][,force-copy=on|off]\n" " [,queues=n][,start-queue=m][,inhibit=on|off][,sock-fds=x:y:...:z]\n" + " [,map-path=/path/to/socket/map][,map-start-index=i]\n" " attach to the existing network interface 'name' with AF_XDP socket\n" " use 'mode=MODE' to specify an XDP program attach mode\n" " use 'force-copy=on|off' to force XDP copy mode even if device supports zero-copy (default: off)\n" @@ -2916,6 +2955,8 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, " with inhibit=on,\n" " use 'sock-fds' to provide file descriptors for already open AF_XDP sockets\n" " added to a socket map in XDP program. One socket per queue.\n" + " use 'map-path' to provide the socket map location to populate AF_XDP sockets with,\n" + " and use 'map-start-index' to specify the starting index for the map (default: 0) (Since 10.1)\n" " use 'queues=n' to specify how many queues of a multiqueue interface should be used\n" " use 'start-queue=m' to specify the first queue that should be used\n" #endif @@ -2952,6 +2993,9 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, " configure a hub port on the hub with ID 'n'\n", QEMU_ARCH_ALL) DEF("nic", HAS_ARG, QEMU_OPTION_nic, "-nic [tap|bridge|" +#ifdef CONFIG_PASST + "passt|" +#endif #ifdef CONFIG_SLIRP "user|" #endif @@ -2984,6 +3028,9 @@ DEF("net", HAS_ARG, QEMU_OPTION_net, " configure or create an on-board (or machine default) NIC and\n" " connect it to hub 0 (please use -nic unless you need a hub)\n" "-net [" +#ifdef CONFIG_PASST + "passt|" +#endif #ifdef CONFIG_SLIRP "user|" #endif @@ -3005,7 +3052,7 @@ DEF("net", HAS_ARG, QEMU_OPTION_net, " old way to initialize a host network interface\n" " (use the -netdev option if possible instead)\n", QEMU_ARCH_ALL) SRST -``-nic [tap|bridge|user|l2tpv3|vde|netmap|af-xdp|vhost-user|socket][,...][,mac=macaddr][,model=mn]`` +``-nic [tap|passt|bridge|user|l2tpv3|vde|netmap|af-xdp|vhost-user|socket][,...][,mac=macaddr][,model=mn]`` This option is a shortcut for configuring both the on-board (default) guest NIC hardware and the host network backend in one go. The host backend options are the same as with the corresponding @@ -3027,6 +3074,129 @@ SRST network backend) which is activated if no other networking options are provided. +``-netdev passt,id=str[,option][,...]`` + Configure a passt network backend which requires no administrator + privilege to run. Valid options are: + + ``id=id`` + Assign symbolic name for use in monitor commands. + + ``path=file`` + Filename of the passt program to run. If it is not provided, + passt command will be started with the help of the PATH environment + variable. + + ``quiet=on|off`` + By default, ``quiet=on`` to disable informational message from + passt. ``quiet=on`` is passed as ``--quiet`` to passt. + + ``vhost-user=on|off`` + By default, ``vhost-user=off`` and QEMU uses the stream network + backend to communicate with passt. If ``vhost-user=on``, passt is + started with ``--vhost-user`` and QEMU uses the vhost-user network + backend to communicate with passt. + + ``@mtu`` + Assign MTU via DHCP/NDP + + ``address`` + IPv4 or IPv6 address + + ``netmask`` + IPv4 mask + + ``mac`` + source MAC address + + ``gateway`` + IPv4 or IPv6 address as gateway + + ``interface`` + Interface for addresses and routes + + ``outbound`` + Bind to address as outbound source + + ``outbound-if4`` + Bind to outbound interface for IPv4 + + ``outbound-if6`` + Bind to outbound interface for IPv6 + + ``dns`` + IPv4 or IPv6 address as DNS + + ``search`` + Search domains + + ``fqdn`` + FQDN to configure client with + + ``dhcp-dns`` + Enable/disable DNS list in DHCP/DHCPv6/NDP + + ``dhcp-search`` + Enable/disable list in DHCP/DHCPv6/NDP + + ``map-host-loopback`` + Addresse to refer to host + + ``map-guest-addr`` + Addr to translate to guest's address + + ``dns-forward`` + Forward DNS queries sent to + + ``dns-host`` + Host nameserver to direct queries to + + ``tcp`` + Enable/disable TCP + + ``udp`` + Enable/disable UDP + + ``icmp`` + Enable/disable ICMP + + ``dhcp`` + Enable/disable DHCP + + ``ndp`` + Enable/disable NDP + + ``dhcpv6`` + Enable/disable DHCPv6 + + ``ra`` + Enable/disable route advertisements + + ``freebind`` + Bind to any address for forwarding + + ``ipv4`` + Enable/disable IPv4 + + ``ipv6`` + Enable/disable IPv6 + + ``tcp-ports`` + TCP ports to forward + + ``udp-ports`` + UDP ports to forward + + ``param=string`` + ``string`` will be passed to passt has a command line parameter, + we can have multiple occurences of the ``param`` parameter to + pass multiple parameters to passt. + + For instance, to pass ``--trace --log=trace.log``: + + .. parsed-literal:: + + |qemu_system| -nic passt,param=--trace,param=--log=trace.log + ``-netdev user,id=id[,option][,option][,...]`` Configure user mode host network backend which requires no administrator privilege to run. Valid options are: @@ -3610,7 +3780,7 @@ SRST # launch QEMU instance |qemu_system| linux.img -nic vde,sock=/tmp/myswitch -``-netdev af-xdp,id=str,ifname=name[,mode=native|skb][,force-copy=on|off][,queues=n][,start-queue=m][,inhibit=on|off][,sock-fds=x:y:...:z]`` +``-netdev af-xdp,id=str,ifname=name[,mode=native|skb][,force-copy=on|off][,queues=n][,start-queue=m][,inhibit=on|off][,sock-fds=x:y:...:z][,map-path=/path/to/socket/map][,map-start-index=i]`` Configure AF_XDP backend to connect to a network interface 'name' using AF_XDP socket. A specific program attach mode for a default XDP program can be forced with 'mode', defaults to best-effort, @@ -3650,7 +3820,8 @@ SRST -netdev af-xdp,id=n1,ifname=eth0,queues=1,start-queue=1 XDP program can also be loaded externally. In this case 'inhibit' option - should be set to 'on' and 'sock-fds' provided with file descriptors for + should be set to 'on'. Either 'sock-fds' or 'map-path' can be used with + 'inhibit' enabled. 'sock-fds' can be provided with file descriptors for already open but not bound XDP sockets already added to a socket map for corresponding queues. One socket per queue. @@ -3659,6 +3830,21 @@ SRST |qemu_system| linux.img -device virtio-net-pci,netdev=n1 \\ -netdev af-xdp,id=n1,ifname=eth0,queues=3,inhibit=on,sock-fds=15:16:17 + For the 'inhibit' option set to 'on' used together with 'map-path' it is + expected that the XDP program with the socket map is already loaded on + the networking device and the map pinned into BPF file system. The path + to the pinned map is then passed to QEMU which then creates the file + descriptors and inserts them into the existing socket map. + + .. parsed-literal:: + + |qemu_system| linux.img -device virtio-net-pci,netdev=n1 \\ + -netdev af-xdp,id=n1,ifname=eth0,queues=2,inhibit=on,map-path=/sys/fs/bpf/xsks_map + + Additionally, 'map-start-index' can be used to specify the start offset + for insertion into the socket map. The combination of 'map-path' and + 'sock-fds' together is not supported. + ``-netdev vhost-user,chardev=id[,vhostforce=on|off][,queues=n]`` Establish a vhost-user netdev, backed by a chardev id. The chardev should be a unix domain socket backed one. The vhost-user uses a @@ -3711,7 +3897,7 @@ SRST Use ``-net nic,model=help`` for a list of available devices for your target. -``-net user|tap|bridge|socket|l2tpv3|vde[,...][,name=name]`` +``-net user|passt|tap|bridge|socket|l2tpv3|vde[,...][,name=name]`` Configure a host network backend (with the options corresponding to the same ``-netdev`` option) and connect it to the emulated hub 0 (the default hub). Use name to specify the name of the hub port. @@ -5992,6 +6178,34 @@ SRST -machine ...,memory-encryption=sev0 \\ ..... + ``-object igvm-cfg,file=file`` + Create an IGVM configuration object that defines the initial state + of the guest using a file in that conforms to the Independent Guest + Virtual Machine (IGVM) file format. + + This is currently only supported by ``-machine q35`` and + ``-machine pc``. + + The ``file`` parameter is used to specify the IGVM file to load. + When provided, the IGVM file is used to populate the initial + memory of the virtual machine and, depending on the platform, can + define the initial processor state, memory map and parameters. + + The IGVM file is expected to contain the firmware for the virtual + machine, therefore an ``igvm-cfg`` object cannot be provided along + with other ways of specifying firmware, such as the ``-bios`` + parameter on x86 machines. + + e.g to launch a machine providing the firmware in an IGVM file + + .. parsed-literal:: + + # |qemu_system_x86| \\ + ...... \\ + -object igvm-cfg,id=igvm0,file=bios.igvm \\ + -machine ...,igvm-cfg=igvm0 \\ + ..... + ``-object authz-simple,id=id,identity=string`` Create an authorization object that will control access to network services. diff --git a/rust/qemu-api-macros/meson.build b/rust/qemu-api-macros/meson.build index 8610ce1..2152bcb 100644 --- a/rust/qemu-api-macros/meson.build +++ b/rust/qemu-api-macros/meson.build @@ -17,3 +17,6 @@ _qemu_api_macros_rs = rust.proc_macro( qemu_api_macros = declare_dependency( link_with: _qemu_api_macros_rs, ) + +rust.test('rust-qemu-api-macros-tests', _qemu_api_macros_rs, + suite: ['unit', 'rust']) diff --git a/rust/qemu-api-macros/src/bits.rs b/rust/qemu-api-macros/src/bits.rs index 5ba8475..a80a3b9 100644 --- a/rust/qemu-api-macros/src/bits.rs +++ b/rust/qemu-api-macros/src/bits.rs @@ -6,8 +6,7 @@ use proc_macro2::{ Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree, TokenTree as TT, }; - -use crate::utils::MacroError; +use syn::Error; pub struct BitsConstInternal { typ: TokenTree, @@ -36,27 +35,21 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator<Item = TokenTree>, out: &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { let next = match tok { TT::Group(ref g) => { if g.delimiter() != Delimiter::Parenthesis && g.delimiter() != Delimiter::None { - return Err(MacroError::Message("expected parenthesis".into(), g.span())); + return Err(Error::new(g.span(), "expected parenthesis")); } let mut stream = g.stream().into_iter(); let Some(first_tok) = stream.next() else { - return Err(MacroError::Message( - "expected operand, found ')'".into(), - g.span(), - )); + return Err(Error::new(g.span(), "expected operand, found ')'")); }; let mut output = TokenStream::new(); // start from the lowest precedence let next = self.parse_or(first_tok, &mut stream, &mut output)?; if let Some(tok) = next { - return Err(MacroError::Message( - format!("unexpected token {tok}"), - tok.span(), - )); + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); } out.extend(Some(paren(output))); it.next() @@ -74,20 +67,17 @@ impl BitsConstInternal { } TT::Punct(ref p) => { if p.as_char() != '!' { - return Err(MacroError::Message("expected operand".into(), p.span())); + return Err(Error::new(p.span(), "expected operand")); } let Some(rhs_tok) = it.next() else { - return Err(MacroError::Message( - "expected operand at end of input".into(), - p.span(), - )); + return Err(Error::new(p.span(), "expected operand at end of input")); }; let next = self.parse_primary(rhs_tok, it, out)?; out.extend([punct('.'), ident("invert"), paren(TokenStream::new())]); next } _ => { - return Err(MacroError::Message("unexpected literal".into(), tok.span())); + return Err(Error::new(tok.span(), "unexpected literal")); } }; Ok(next) @@ -99,7 +89,7 @@ impl BitsConstInternal { TokenTree, &mut dyn Iterator<Item = TokenTree>, &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError>, + ) -> Result<Option<TokenTree>, Error>, >( &self, tok: TokenTree, @@ -108,7 +98,7 @@ impl BitsConstInternal { ch: char, f: F, method: &'static str, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { let mut next = f(self, tok, it, out)?; while next.is_some() { let op = next.as_ref().unwrap(); @@ -118,10 +108,7 @@ impl BitsConstInternal { } let Some(rhs_tok) = it.next() else { - return Err(MacroError::Message( - "expected operand at end of input".into(), - p.span(), - )); + return Err(Error::new(p.span(), "expected operand at end of input")); }; let mut rhs = TokenStream::new(); next = f(self, rhs_tok, it, &mut rhs)?; @@ -136,7 +123,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator<Item = TokenTree>, out: &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { self.parse_binop(tok, it, out, '-', Self::parse_primary, "difference") } @@ -146,7 +133,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator<Item = TokenTree>, out: &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { self.parse_binop(tok, it, out, '&', Self::parse_sub, "intersection") } @@ -156,7 +143,7 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator<Item = TokenTree>, out: &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { self.parse_binop(tok, it, out, '^', Self::parse_and, "symmetric_difference") } @@ -166,13 +153,13 @@ impl BitsConstInternal { tok: TokenTree, it: &mut dyn Iterator<Item = TokenTree>, out: &mut TokenStream, - ) -> Result<Option<TokenTree>, MacroError> { + ) -> Result<Option<TokenTree>, Error> { self.parse_binop(tok, it, out, '|', Self::parse_xor, "union") } pub fn parse( it: &mut dyn Iterator<Item = TokenTree>, - ) -> Result<proc_macro2::TokenStream, MacroError> { + ) -> Result<proc_macro2::TokenStream, Error> { let mut pos = Span::call_site(); let mut typ = proc_macro2::TokenStream::new(); @@ -198,15 +185,15 @@ impl BitsConstInternal { }; let Some(tok) = next else { - return Err(MacroError::Message( - "expected expression, do not call this macro directly".into(), + return Err(Error::new( pos, + "expected expression, do not call this macro directly", )); }; let TT::Group(ref _group) = tok else { - return Err(MacroError::Message( - "expected parenthesis, do not call this macro directly".into(), + return Err(Error::new( tok.span(), + "expected parenthesis, do not call this macro directly", )); }; let mut out = TokenStream::new(); @@ -219,10 +206,7 @@ impl BitsConstInternal { // A parenthesized expression is a single production of the grammar, // so the input must have reached the last token. if let Some(tok) = next { - return Err(MacroError::Message( - format!("unexpected token {tok}"), - tok.span(), - )); + return Err(Error::new(tok.span(), format!("unexpected token {tok}"))); } Ok(out) } diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index c18bb4e..b525d89 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -6,83 +6,82 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, + DeriveInput, Error, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Variant, }; - -mod utils; -use utils::MacroError; - mod bits; use bits::BitsConstInternal; +#[cfg(test)] +mod tests; + fn get_fields<'a>( input: &'a DeriveInput, msg: &str, -) -> Result<&'a Punctuated<Field, Comma>, MacroError> { +) -> Result<&'a Punctuated<Field, Comma>, Error> { let Data::Struct(ref s) = &input.data else { - return Err(MacroError::Message( - format!("Struct required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Struct required for {msg}"), )); }; let Fields::Named(ref fs) = &s.fields else { - return Err(MacroError::Message( - format!("Named fields required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Named fields required for {msg}"), )); }; Ok(&fs.named) } -fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { +fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, Error> { let Data::Struct(ref s) = &input.data else { - return Err(MacroError::Message( - format!("Struct required for {msg}"), + return Err(Error::new( input.ident.span(), + format!("Struct required for {msg}"), )); }; let Fields::Unnamed(FieldsUnnamed { ref unnamed, .. }) = &s.fields else { - return Err(MacroError::Message( - format!("Tuple struct required for {msg}"), + return Err(Error::new( s.fields.span(), + format!("Tuple struct required for {msg}"), )); }; if unnamed.len() != 1 { - return Err(MacroError::Message( - format!("A single field is required for {msg}"), + return Err(Error::new( s.fields.span(), + format!("A single field is required for {msg}"), )); } Ok(&unnamed[0]) } -fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { +fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { let expected = parse_quote! { #[repr(C)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(MacroError::Message( - format!("#[repr(C)] required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(C)] required for {msg}"), )) } } -fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { +fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), Error> { let expected = parse_quote! { #[repr(transparent)] }; if input.attrs.iter().any(|attr| attr == &expected) { Ok(()) } else { - Err(MacroError::Message( - format!("#[repr(transparent)] required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(transparent)] required for {msg}"), )) } } -fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { +fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { is_c_repr(&input, "#[derive(Object)]")?; let name = &input.ident; @@ -103,12 +102,13 @@ fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream #[proc_macro_derive(Object)] pub fn derive_object(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_object_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_object_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } -fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { +fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { is_transparent_repr(&input, "#[derive(Wrapper)]")?; let name = &input.ident; @@ -149,13 +149,14 @@ fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream #[proc_macro_derive(Wrapper)] pub fn derive_opaque(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_opaque_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_opaque_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[allow(non_snake_case)] -fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, MacroError> { +fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, Error> { let repr = input.attrs.iter().find(|attr| attr.path().is_ident("repr")); if let Some(repr) = repr { let nested = repr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?; @@ -170,23 +171,23 @@ fn get_repr_uN(input: &DeriveInput, msg: &str) -> Result<Path, MacroError> { } } - Err(MacroError::Message( - format!("#[repr(u8/u16/u32/u64) required for {msg}"), + Err(Error::new( input.ident.span(), + format!("#[repr(u8/u16/u32/u64) required for {msg}"), )) } -fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, MacroError> { +fn get_variants(input: &DeriveInput) -> Result<&Punctuated<Variant, Comma>, Error> { let Data::Enum(ref e) = &input.data else { - return Err(MacroError::Message( - "Cannot derive TryInto for union or struct.".to_string(), + return Err(Error::new( input.ident.span(), + "Cannot derive TryInto for union or struct.", )); }; if let Some(v) = e.variants.iter().find(|v| v.fields != Fields::Unit) { - return Err(MacroError::Message( - "Cannot derive TryInto for enum with non-unit variants.".to_string(), + return Err(Error::new( v.fields.span(), + "Cannot derive TryInto for enum with non-unit variants.", )); } Ok(&e.variants) @@ -197,11 +198,11 @@ fn derive_tryinto_body( name: &Ident, variants: &Punctuated<Variant, Comma>, repr: &Path, -) -> Result<proc_macro2::TokenStream, MacroError> { +) -> Result<proc_macro2::TokenStream, Error> { let discriminants: Vec<&Ident> = variants.iter().map(|f| &f.ident).collect(); Ok(quote! { - #(const #discriminants: #repr = #name::#discriminants as #repr;)*; + #(const #discriminants: #repr = #name::#discriminants as #repr;)* match value { #(#discriminants => core::result::Result::Ok(#name::#discriminants),)* _ => core::result::Result::Err(value), @@ -210,7 +211,7 @@ fn derive_tryinto_body( } #[rustfmt::skip::macros(quote)] -fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { +fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> { let repr = get_repr_uN(&input, "#[derive(TryInto)]")?; let name = &input.ident; let body = derive_tryinto_body(name, get_variants(&input)?, &repr)?; @@ -229,7 +230,7 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStrea #body }) { Ok(x) => x, - Err(_) => panic!(#errmsg) + Err(_) => panic!(#errmsg), } } } @@ -247,9 +248,10 @@ fn derive_tryinto_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStrea #[proc_macro_derive(TryInto)] pub fn derive_tryinto(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = derive_tryinto_or_error(input).unwrap_or_else(Into::into); - TokenStream::from(expanded) + derive_tryinto_or_error(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } #[proc_macro] @@ -257,6 +259,7 @@ pub fn bits_const_internal(ts: TokenStream) -> TokenStream { let ts = proc_macro2::TokenStream::from(ts); let mut it = ts.into_iter(); - let expanded = BitsConstInternal::parse(&mut it).unwrap_or_else(Into::into); - TokenStream::from(expanded) + BitsConstInternal::parse(&mut it) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } diff --git a/rust/qemu-api-macros/src/tests.rs b/rust/qemu-api-macros/src/tests.rs new file mode 100644 index 0000000..d6dcd62 --- /dev/null +++ b/rust/qemu-api-macros/src/tests.rs @@ -0,0 +1,137 @@ +// Copyright 2025, Linaro Limited +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> +// SPDX-License-Identifier: GPL-2.0-or-later + +use quote::quote; + +use super::*; + +macro_rules! derive_compile_fail { + ($derive_fn:ident, $input:expr, $error_msg:expr) => {{ + let input: proc_macro2::TokenStream = $input; + let error_msg: &str = $error_msg; + let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input); + let err = result.unwrap_err().into_compile_error(); + assert_eq!( + err.to_string(), + quote! { ::core::compile_error! { #error_msg } }.to_string() + ); + }}; +} + +macro_rules! derive_compile { + ($derive_fn:ident, $input:expr, $($expected:tt)*) => {{ + let input: proc_macro2::TokenStream = $input; + let expected: proc_macro2::TokenStream = $($expected)*; + let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> = + $derive_fn; + + let input: syn::DeriveInput = syn::parse2(input).unwrap(); + let result = derive_fn(input).unwrap(); + assert_eq!(result.to_string(), expected.to_string()); + }}; +} + +#[test] +fn test_derive_object() { + derive_compile_fail!( + derive_object_or_error, + quote! { + #[derive(Object)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(C)] required for #[derive(Object)]" + ); + derive_compile!( + derive_object_or_error, + quote! { + #[derive(Object)] + #[repr(C)] + struct Foo { + _unused: [u8; 0], + } + }, + quote! { + ::qemu_api::assert_field_type!( + Foo, + _unused, + ::qemu_api::qom::ParentField<<Foo as ::qemu_api::qom::ObjectImpl>::ParentType> + ); + ::qemu_api::module_init! { + MODULE_INIT_QOM => unsafe { + ::qemu_api::bindings::type_register_static(&<Foo as ::qemu_api::qom::ObjectImpl>::TYPE_INFO); + } + } + } + ); +} + +#[test] +fn test_derive_tryinto() { + derive_compile_fail!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + struct Foo { + _unused: [u8; 0], + } + }, + "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]" + ); + derive_compile!( + derive_tryinto_or_error, + quote! { + #[derive(TryInto)] + #[repr(u8)] + enum Foo { + First = 0, + Second, + } + }, + quote! { + impl Foo { + #[allow(dead_code)] + pub const fn into_bits(self) -> u8 { + self as u8 + } + + #[allow(dead_code)] + pub const fn from_bits(value: u8) -> Self { + match ({ + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + }) { + Ok(x) => x, + Err(_) => panic!("invalid value for Foo"), + } + } + } + + impl core::convert::TryFrom<u8> for Foo { + type Error = u8; + + #[allow(ambiguous_associated_items)] + fn try_from(value: u8) -> Result<Self, u8> { + const First: u8 = Foo::First as u8; + const Second: u8 = Foo::Second as u8; + match value { + First => core::result::Result::Ok(Foo::First), + Second => core::result::Result::Ok(Foo::Second), + _ => core::result::Result::Err(value), + } + } + } + } + ); +} diff --git a/rust/qemu-api-macros/src/utils.rs b/rust/qemu-api-macros/src/utils.rs deleted file mode 100644 index 02c91ae..0000000 --- a/rust/qemu-api-macros/src/utils.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Procedural macro utilities. -// Author(s): Paolo Bonzini <pbonzini@redhat.com> -// SPDX-License-Identifier: GPL-2.0-or-later - -use proc_macro2::Span; -use quote::quote_spanned; - -pub enum MacroError { - Message(String, Span), - ParseError(syn::Error), -} - -impl From<syn::Error> for MacroError { - fn from(err: syn::Error) -> Self { - MacroError::ParseError(err) - } -} - -impl From<MacroError> for proc_macro2::TokenStream { - fn from(err: MacroError) -> Self { - match err { - MacroError::Message(msg, span) => quote_spanned! { span => compile_error!(#msg); }, - MacroError::ParseError(err) => err.into_compile_error(), - } - } -} diff --git a/rust/qemu-api/build.rs b/rust/qemu-api/build.rs index 7849486..29d0945 100644 --- a/rust/qemu-api/build.rs +++ b/rust/qemu-api/build.rs @@ -9,12 +9,14 @@ use std::os::windows::fs::symlink_file; use std::{env, fs::remove_file, io::Result, path::Path}; fn main() -> Result<()> { - // Placing bindings.inc.rs in the source directory is supported - // but not documented or encouraged. - let path = env::var("MESON_BUILD_ROOT") - .unwrap_or_else(|_| format!("{}/src", env!("CARGO_MANIFEST_DIR"))); + let file = if let Ok(root) = env::var("MESON_BUILD_ROOT") { + format!("{root}/rust/qemu-api/bindings.inc.rs") + } else { + // Placing bindings.inc.rs in the source directory is supported + // but not documented or encouraged. + format!("{}/src/bindings.inc.rs", env!("CARGO_MANIFEST_DIR")) + }; - let file = format!("{path}/rust/qemu-api/bindings.inc.rs"); let file = Path::new(&file); if !Path::new(&file).exists() { panic!(concat!( diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index 057de4b..b8104de 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -6,6 +6,7 @@ non_camel_case_types, non_snake_case, non_upper_case_globals, + unnecessary_transmutes, unsafe_op_in_unsafe_fn, clippy::pedantic, clippy::restriction, @@ -13,7 +14,8 @@ clippy::missing_const_for_fn, clippy::ptr_offset_with_cast, clippy::useless_transmute, - clippy::missing_safety_doc + clippy::missing_safety_doc, + clippy::too_many_arguments )] //! `bindgen`-generated declarations. diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs index d6c3d6c..a441b8c 100644 --- a/rust/qemu-api/src/log.rs +++ b/rust/qemu-api/src/log.rs @@ -3,6 +3,13 @@ //! Bindings for QEMU's logging infrastructure +use std::{ + io::{self, Write}, + ptr::NonNull, +}; + +use crate::{bindings, errno}; + #[repr(u32)] /// Represents specific error categories within QEMU's logging system. /// @@ -11,11 +18,82 @@ pub enum Log { /// Log invalid access caused by the guest. /// Corresponds to `LOG_GUEST_ERROR` in the C implementation. - GuestError = crate::bindings::LOG_GUEST_ERROR, + GuestError = bindings::LOG_GUEST_ERROR, /// Log guest access of unimplemented functionality. /// Corresponds to `LOG_UNIMP` in the C implementation. - Unimp = crate::bindings::LOG_UNIMP, + Unimp = bindings::LOG_UNIMP, +} + +/// A RAII guard for QEMU's logging infrastructure. Creating the guard +/// locks the log file, and dropping it (letting it go out of scope) unlocks +/// the file. +/// +/// As long as the guard lives, it can be written to using [`std::io::Write`]. +/// +/// The locking is recursive, therefore owning a guard does not prevent +/// using [`log_mask_ln!()`](crate::log_mask_ln). +pub struct LogGuard(NonNull<bindings::FILE>); + +impl LogGuard { + /// Return a RAII guard that writes to QEMU's logging infrastructure. + /// The log file is locked while the guard exists, ensuring that there + /// is no tearing of the messages. + /// + /// Return `None` if the log file is closed and could not be opened. + /// Do *not* use `unwrap()` on the result; failure can be handled simply + /// by not logging anything. + /// + /// # Examples + /// + /// ``` + /// # use qemu_api::log::LogGuard; + /// # use std::io::Write; + /// if let Some(mut log) = LogGuard::new() { + /// writeln!(log, "test"); + /// } + /// ``` + pub fn new() -> Option<Self> { + let f = unsafe { bindings::qemu_log_trylock() }.cast(); + NonNull::new(f).map(Self) + } + + /// Writes a formatted string into the log, returning any error encountered. + /// + /// This method is primarily used by the + /// [`log_mask_ln!()`](crate::log_mask_ln) macro, and it is rare for it + /// to be called explicitly. It is public because it is the only way to + /// examine the error, which `log_mask_ln!()` ignores + /// + /// Unlike `log_mask_ln!()`, it does *not* append a newline at the end. + pub fn log_fmt(args: std::fmt::Arguments) -> io::Result<()> { + if let Some(mut log) = Self::new() { + log.write_fmt(args)?; + } + Ok(()) + } +} + +impl Write for LogGuard { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + let ret = unsafe { + bindings::rust_fwrite(bytes.as_ptr().cast(), 1, bytes.len(), self.0.as_ptr()) + }; + errno::into_io_result(ret) + } + + fn flush(&mut self) -> io::Result<()> { + // Do nothing, dropping the guard takes care of flushing + Ok(()) + } +} + +impl Drop for LogGuard { + fn drop(&mut self) { + unsafe { + bindings::qemu_log_unlock(self.0.as_ptr()); + } + } } /// A macro to log messages conditionally based on a provided mask. @@ -24,6 +102,8 @@ pub enum Log { /// log level and, if so, formats and logs the message. It is the Rust /// counterpart of the `qemu_log_mask()` macro in the C implementation. /// +/// Errors from writing to the log are ignored. +/// /// # Parameters /// /// - `$mask`: A log level mask. This should be a variant of the `Log` enum. @@ -62,12 +142,8 @@ macro_rules! log_mask_ln { if unsafe { (::qemu_api::bindings::qemu_loglevel & ($mask as std::os::raw::c_int)) != 0 } { - let formatted_string = format!("{}\n", format_args!($fmt $($args)*)); - let c_string = std::ffi::CString::new(formatted_string).unwrap(); - - unsafe { - ::qemu_api::bindings::qemu_log(c_string.as_ptr()); - } + _ = ::qemu_api::log::LogGuard::log_fmt( + format_args!("{}\n", format_args!($fmt $($args)*))); } }}; } diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 73e0770..e850468 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -130,6 +130,7 @@ meson_options_help() { printf "%s\n" ' hv-balloon hv-balloon driver (requires Glib 2.68+ GTree API)' printf "%s\n" ' hvf HVF acceleration support' printf "%s\n" ' iconv Font glyph conversion support' + printf "%s\n" ' igvm IGVM file support' printf "%s\n" ' jack JACK sound support' printf "%s\n" ' keyring Linux keyring support' printf "%s\n" ' kvm KVM acceleration support' @@ -162,6 +163,7 @@ meson_options_help() { printf "%s\n" ' oss OSS sound support' printf "%s\n" ' pa PulseAudio sound support' printf "%s\n" ' parallels parallels image format support' + printf "%s\n" ' passt passt network backend support' printf "%s\n" ' pipewire PipeWire sound support' printf "%s\n" ' pixman pixman support' printf "%s\n" ' plugins TCG plugins via shared library loading' @@ -346,6 +348,8 @@ _meson_option_parse() { --iasl=*) quote_sh "-Diasl=$2" ;; --enable-iconv) printf "%s" -Diconv=enabled ;; --disable-iconv) printf "%s" -Diconv=disabled ;; + --enable-igvm) printf "%s" -Digvm=enabled ;; + --disable-igvm) printf "%s" -Digvm=disabled ;; --includedir=*) quote_sh "-Dincludedir=$2" ;; --enable-install-blobs) printf "%s" -Dinstall_blobs=true ;; --disable-install-blobs) printf "%s" -Dinstall_blobs=false ;; @@ -422,6 +426,8 @@ _meson_option_parse() { --disable-pa) printf "%s" -Dpa=disabled ;; --enable-parallels) printf "%s" -Dparallels=enabled ;; --disable-parallels) printf "%s" -Dparallels=disabled ;; + --enable-passt) printf "%s" -Dpasst=enabled ;; + --disable-passt) printf "%s" -Dpasst=disabled ;; --enable-pipewire) printf "%s" -Dpipewire=enabled ;; --disable-pipewire) printf "%s" -Dpipewire=disabled ;; --enable-pixman) printf "%s" -Dpixman=enabled ;; diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 949d9e8..2529edf 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -584,10 +584,6 @@ class QAPISchemaParser: doc.append_line(text) line = self.get_doc_indented(doc) no_more_args = True - elif line.startswith('='): - raise QAPIParseError( - self, - "unexpected '=' markup in definition documentation") else: # plain paragraph doc.ensure_untagged_section(self.info) @@ -597,22 +593,15 @@ class QAPISchemaParser: # Free-form documentation doc = QAPIDoc(info) doc.ensure_untagged_section(self.info) - first = True while line is not None: if match := self._match_at_name_colon(line): raise QAPIParseError( self, "'@%s:' not allowed in free-form documentation" % match.group(1)) - if line.startswith('='): - if not first: - raise QAPIParseError( - self, - "'=' heading must come first in a comment block") doc.append_line(line) self.accept(False) line = self.get_doc_line() - first = False self.accept() doc.end() @@ -815,6 +804,43 @@ class QAPIDoc: % feature.name) self.features[feature.name].connect(feature) + def ensure_returns(self, info: QAPISourceInfo) -> None: + + def _insert_near_kind( + kind: QAPIDoc.Kind, + new_sect: QAPIDoc.Section, + after: bool = False, + ) -> bool: + for idx, sect in enumerate(reversed(self.all_sections)): + if sect.kind == kind: + pos = len(self.all_sections) - idx - 1 + if after: + pos += 1 + self.all_sections.insert(pos, new_sect) + return True + return False + + if any(s.kind == QAPIDoc.Kind.RETURNS for s in self.all_sections): + return + + # Stub "Returns" section for undocumented returns value + stub = QAPIDoc.Section(info, QAPIDoc.Kind.RETURNS) + + if any(_insert_near_kind(kind, stub, after) for kind, after in ( + # 1. If arguments, right after those. + (QAPIDoc.Kind.MEMBER, True), + # 2. Elif errors, right *before* those. + (QAPIDoc.Kind.ERRORS, False), + # 3. Elif features, right *before* those. + (QAPIDoc.Kind.FEATURE, False), + )): + return + + # Otherwise, it should go right after the intro. The intro + # is always the first section and is always present (even + # when empty), so we can insert directly at index=1 blindly. + self.all_sections.insert(1, stub) + def check_expr(self, expr: QAPIExpression) -> None: if 'command' in expr: if self.returns and 'returns' not in expr: diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index cbe3b5a..3abddea 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -1062,6 +1062,9 @@ class QAPISchemaCommand(QAPISchemaDefinition): if self.arg_type and self.arg_type.is_implicit(): self.arg_type.connect_doc(doc) + if self.ret_type and self.info: + doc.ensure_returns(self.info) + def visit(self, visitor: QAPISchemaVisitor) -> None: super().visit(visitor) visitor.visit_command( diff --git a/storage-daemon/qapi/qapi-schema.json b/storage-daemon/qapi/qapi-schema.json index 0427594..478e7a9 100644 --- a/storage-daemon/qapi/qapi-schema.json +++ b/storage-daemon/qapi/qapi-schema.json @@ -14,7 +14,9 @@ # storage daemon. ## -# = Introduction +# ************ +# Introduction +# ************ # # This manual describes the commands and events supported by the QEMU # storage daemon QMP. @@ -51,7 +53,9 @@ { 'include': '../../qapi/job.json' } ## -# = Block devices +# ************* +# Block devices +# ************* ## { 'include': '../../qapi/block-core.json' } { 'include': '../../qapi/block-export.json' } diff --git a/system/cpus.c b/system/cpus.c index 8e6da2e..2567235 100644 --- a/system/cpus.c +++ b/system/cpus.c @@ -31,7 +31,7 @@ #include "qapi/qapi-events-run-state.h" #include "qapi/qmp/qerror.h" #include "exec/gdbstub.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/hw_accel.h" #include "exec/cpu-common.h" #include "qemu/thread.h" diff --git a/system/memory.c b/system/memory.c index e8d9b15..5646547 100644 --- a/system/memory.c +++ b/system/memory.c @@ -22,6 +22,7 @@ #include "qemu/error-report.h" #include "qemu/main-loop.h" #include "qemu/qemu-print.h" +#include "qemu/target-info.h" #include "qom/object.h" #include "trace.h" #include "system/ram_addr.h" @@ -29,6 +30,7 @@ #include "system/runstate.h" #include "system/tcg.h" #include "qemu/accel.h" +#include "accel/accel-ops.h" #include "hw/boards.h" #include "migration/vmstate.h" #include "system/address-spaces.h" diff --git a/system/qtest.c b/system/qtest.c index 301b03b..fa42c9f 100644 --- a/system/qtest.c +++ b/system/qtest.c @@ -29,6 +29,7 @@ #include "qemu/error-report.h" #include "qemu/module.h" #include "qemu/cutils.h" +#include "qemu/target-info.h" #include "qom/object_interfaces.h" #define MAX_IRQ 256 diff --git a/system/runstate.c b/system/runstate.c index 38900c9..6178b00 100644 --- a/system/runstate.c +++ b/system/runstate.c @@ -306,18 +306,6 @@ struct VMChangeStateEntry { static QTAILQ_HEAD(, VMChangeStateEntry) vm_change_state_head = QTAILQ_HEAD_INITIALIZER(vm_change_state_head); -/** - * qemu_add_vm_change_state_handler_prio: - * @cb: the callback to invoke - * @opaque: user data passed to the callback - * @priority: low priorities execute first when the vm runs and the reverse is - * true when the vm stops - * - * Register a callback function that is invoked when the vm starts or stops - * running. - * - * Returns: an entry to be freed using qemu_del_vm_change_state_handler() - */ VMChangeStateEntry *qemu_add_vm_change_state_handler_prio( VMChangeStateHandler *cb, void *opaque, int priority) { @@ -325,24 +313,6 @@ VMChangeStateEntry *qemu_add_vm_change_state_handler_prio( opaque, priority); } -/** - * qemu_add_vm_change_state_handler_prio_full: - * @cb: the main callback to invoke - * @prepare_cb: a callback to invoke before the main callback - * @cb_ret: the main callback to invoke with return value - * @opaque: user data passed to the callbacks - * @priority: low priorities execute first when the vm runs and the reverse is - * true when the vm stops - * - * Register a main callback function and an optional prepare callback function - * that are invoked when the vm starts or stops running. The main callback and - * the prepare callback are called in two separate phases: First all prepare - * callbacks are called and only then all main callbacks are called. As its - * name suggests, the prepare callback can be used to do some preparatory work - * before invoking the main callback. - * - * Returns: an entry to be freed using qemu_del_vm_change_state_handler() - */ VMChangeStateEntry * qemu_add_vm_change_state_handler_prio_full(VMChangeStateHandler *cb, VMChangeStateHandler *prepare_cb, @@ -437,6 +407,7 @@ static ShutdownCause reset_requested; static ShutdownCause shutdown_requested; static int shutdown_exit_code = EXIT_SUCCESS; static int shutdown_signal; +static bool force_shutdown; static pid_t shutdown_pid; static int powerdown_requested; static int debug_requested; @@ -457,6 +428,11 @@ ShutdownCause qemu_shutdown_requested_get(void) return shutdown_requested; } +bool qemu_force_shutdown_requested(void) +{ + return force_shutdown; +} + ShutdownCause qemu_reset_requested_get(void) { return reset_requested; @@ -805,6 +781,7 @@ void qemu_system_killed(int signal, pid_t pid) * we are in a signal handler. */ shutdown_requested = SHUTDOWN_CAUSE_HOST_SIGNAL; + force_shutdown = true; qemu_notify_event(); } @@ -820,6 +797,9 @@ void qemu_system_shutdown_request(ShutdownCause reason) trace_qemu_system_shutdown_request(reason); replay_shutdown_request(reason); shutdown_requested = reason; + if (reason == SHUTDOWN_CAUSE_HOST_QMP_QUIT) { + force_shutdown = true; + } qemu_notify_event(); } diff --git a/system/tpm.c b/system/tpm.c index 8df0f6e..903b29c 100644 --- a/system/tpm.c +++ b/system/tpm.c @@ -21,6 +21,7 @@ #include "system/tpm.h" #include "qemu/config-file.h" #include "qemu/error-report.h" +#include "qemu/help_option.h" static QLIST_HEAD(, TPMBackend) tpm_backends = QLIST_HEAD_INITIALIZER(tpm_backends); @@ -179,9 +180,9 @@ int tpm_config_parse(QemuOptsList *opts_list, const char *optstr) { QemuOpts *opts; - if (!strcmp(optstr, "help")) { + if (is_help_option(optstr)) { tpm_display_backend_drivers(); - return -1; + exit(EXIT_SUCCESS); } opts = qemu_opts_parse_noisily(opts_list, optstr, true); if (!opts) { diff --git a/target-info-stub.c b/target-info-stub.c index fecc0e7..ca0caa3 100644 --- a/target-info-stub.c +++ b/target-info-stub.c @@ -14,9 +14,11 @@ static const TargetInfo target_info_stub = { .target_name = TARGET_NAME, + .target_arch = SYS_EMU_TARGET__MAX, .long_bits = TARGET_LONG_BITS, .cpu_type = CPU_RESOLVING_TYPE, .machine_typename = TYPE_MACHINE, + .endianness = TARGET_BIG_ENDIAN ? ENDIAN_MODE_BIG : ENDIAN_MODE_LITTLE, }; const TargetInfo *target_info(void) diff --git a/target-info.c b/target-info.c index 16fdca7..3110ab3 100644 --- a/target-info.c +++ b/target-info.c @@ -8,7 +8,9 @@ #include "qemu/osdep.h" #include "qemu/target-info.h" +#include "qemu/target-info-qapi.h" #include "qemu/target-info-impl.h" +#include "qapi/error.h" const char *target_name(void) { @@ -20,6 +22,17 @@ unsigned target_long_bits(void) return target_info()->long_bits; } +SysEmuTarget target_arch(void) +{ + SysEmuTarget arch = target_info()->target_arch; + + if (arch == SYS_EMU_TARGET__MAX) { + arch = qapi_enum_parse(&SysEmuTarget_lookup, target_name(), -1, + &error_abort); + } + return arch; +} + const char *target_cpu_type(void) { return target_info()->cpu_type; @@ -29,3 +42,13 @@ const char *target_machine_typename(void) { return target_info()->machine_typename; } + +EndianMode target_endian_mode(void) +{ + return target_info()->endianness; +} + +bool target_big_endian(void) +{ + return target_endian_mode() == ENDIAN_MODE_BIG; +} diff --git a/target/arm/arm-qmp-cmds.c b/target/arm/arm-qmp-cmds.c index cefd235..d292c97 100644 --- a/target/arm/arm-qmp-cmds.c +++ b/target/arm/arm-qmp-cmds.c @@ -21,6 +21,7 @@ */ #include "qemu/osdep.h" +#include "qemu/target-info.h" #include "hw/boards.h" #include "kvm_arm.h" #include "qapi/error.h" @@ -241,7 +242,7 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) CpuDefinitionInfoList *cpu_list = NULL; GSList *list; - list = object_class_get_list(TYPE_ARM_CPU, false); + list = object_class_get_list(target_cpu_type(), false); g_slist_foreach(list, arm_cpu_add_definition, &cpu_list); g_slist_free(list); diff --git a/target/arm/cpu.c b/target/arm/cpu.c index 08c43f6..e2b2337 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -23,7 +23,6 @@ #include "qemu/timer.h" #include "qemu/log.h" #include "exec/page-vary.h" -#include "exec/tswap.h" #include "target/arm/idau.h" #include "qemu/module.h" #include "qapi/error.h" diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 0d35e95..da7d8dc 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -28,6 +28,7 @@ #include "system/hvf.h" #include "hvf/hvf-i386.h" #include "kvm/kvm_i386.h" +#include "kvm/tdx.h" #include "sev.h" #include "qapi/error.h" #include "qemu/error-report.h" @@ -44,6 +45,7 @@ #include "hw/boards.h" #include "hw/i386/sgx-epc.h" #endif +#include "system/qtest.h" #include "tcg/tcg-cpu.h" #include "disas/capstone.h" @@ -66,6 +68,7 @@ struct CPUID2CacheDescriptorInfo { /* * Known CPUID 2 cache descriptors. + * TLB, prefetch and sectored cache related descriptors are not included. * From Intel SDM Volume 2A, CPUID instruction */ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { @@ -87,18 +90,29 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 2, .line_size = 64, }, [0x21] = { .level = 2, .type = UNIFIED_CACHE, .size = 256 * KiB, .associativity = 8, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x22, 0x23 are not included - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x22, 0x23 are not included + */ [0x24] = { .level = 2, .type = UNIFIED_CACHE, .size = 1 * MiB, .associativity = 16, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x25, 0x20 are not included - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x25, 0x29 are not included + */ [0x2C] = { .level = 1, .type = DATA_CACHE, .size = 32 * KiB, .associativity = 8, .line_size = 64, }, [0x30] = { .level = 1, .type = INSTRUCTION_CACHE, .size = 32 * KiB, .associativity = 8, .line_size = 64, }, + /* + * Newer Intel CPUs (having the cores without L3, e.g., Intel MTL, ARL) + * use CPUID 0x4 leaf to describe cache topology, by encoding CPUID 0x2 + * leaf with 0xFF. For older CPUs (without 0x4 leaf), it's also valid + * to just ignore L3's code if there's no L3. + * + * This already covers all the cases in QEMU, so code 0x40 is not + * included. + */ [0x41] = { .level = 2, .type = UNIFIED_CACHE, .size = 128 * KiB, .associativity = 4, .line_size = 32, }, [0x42] = { .level = 2, .type = UNIFIED_CACHE, .size = 256 * KiB, @@ -115,7 +129,18 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 8, .line_size = 64, }, [0x48] = { .level = 2, .type = UNIFIED_CACHE, .size = 3 * MiB, .associativity = 12, .line_size = 64, }, - /* Descriptor 0x49 depends on CPU family/model, so it is not included */ + /* + * Descriptor 0x49 has 2 cases: + * - 2nd-level cache: 4 MByte, 16-way set associative, 64 byte line size. + * - 3rd-level cache: 4MB, 16-way set associative, 64-byte line size + * (Intel Xeon processor MP, Family 0FH, Model 06H). + * + * When it represents L3, then it depends on CPU family/model. Fortunately, + * the legacy cache/CPU models don't have such special L3. So, just add it + * to represent the general L2 case. + */ + [0x49] = { .level = 2, .type = UNIFIED_CACHE, .size = 4 * MiB, + .associativity = 16, .line_size = 64, }, [0x4A] = { .level = 3, .type = UNIFIED_CACHE, .size = 6 * MiB, .associativity = 12, .line_size = 64, }, [0x4B] = { .level = 3, .type = UNIFIED_CACHE, .size = 8 * MiB, @@ -136,9 +161,10 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { .associativity = 4, .line_size = 64, }, [0x78] = { .level = 2, .type = UNIFIED_CACHE, .size = 1 * MiB, .associativity = 4, .line_size = 64, }, - /* lines per sector is not supported cpuid2_cache_descriptor(), - * so descriptors 0x79, 0x7A, 0x7B, 0x7C are not included. - */ + /* + * lines per sector is not supported cpuid2_cache_descriptor(), + * so descriptors 0x79, 0x7A, 0x7B, 0x7C are not included. + */ [0x7D] = { .level = 2, .type = UNIFIED_CACHE, .size = 2 * MiB, .associativity = 8, .line_size = 64, }, [0x7F] = { .level = 2, .type = UNIFIED_CACHE, .size = 512 * KiB, @@ -199,7 +225,7 @@ struct CPUID2CacheDescriptorInfo cpuid2_cache_descriptors[] = { * Return a CPUID 2 cache descriptor for a given cache. * If no known descriptor is found, return CACHE_DESCRIPTOR_UNAVAILABLE */ -static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache) +static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache, bool *unmacthed) { int i; @@ -216,9 +242,46 @@ static uint8_t cpuid2_cache_descriptor(CPUCacheInfo *cache) } } + *unmacthed |= true; return CACHE_DESCRIPTOR_UNAVAILABLE; } +static const CPUCaches legacy_intel_cpuid2_cache_info; + +/* Encode cache info for CPUID[2] */ +static void encode_cache_cpuid2(X86CPU *cpu, + const CPUCaches *caches, + uint32_t *eax, uint32_t *ebx, + uint32_t *ecx, uint32_t *edx) +{ + CPUX86State *env = &cpu->env; + int l1d, l1i, l2, l3; + bool unmatched = false; + + *eax = 1; /* Number of CPUID[EAX=2] calls required */ + *ebx = *ecx = *edx = 0; + + l1d = cpuid2_cache_descriptor(caches->l1d_cache, &unmatched); + l1i = cpuid2_cache_descriptor(caches->l1i_cache, &unmatched); + l2 = cpuid2_cache_descriptor(caches->l2_cache, &unmatched); + l3 = cpuid2_cache_descriptor(caches->l3_cache, &unmatched); + + if (!cpu->consistent_cache || + (env->cpuid_min_level < 0x4 && !unmatched)) { + /* + * Though SDM defines code 0x40 for cases with no L2 or L3. It's + * also valid to just ignore l3's code if there's no l2. + */ + if (cpu->enable_l3_cache) { + *ecx = l3; + } + *edx = (l1d << 16) | (l1i << 8) | l2; + } else { + *ecx = 0; + *edx = CACHE_DESCRIPTOR_UNAVAILABLE; + } +} + /* CPUID Leaf 4 constants: */ /* EAX: */ @@ -286,11 +349,17 @@ static void encode_cache_cpuid4(CPUCacheInfo *cache, assert(cache->size == cache->line_size * cache->associativity * cache->partitions * cache->sets); + /* + * The following fields have bit-width limitations, so consider the + * maximum values to avoid overflow: + * Bits 25-14: maximum 4095. + * Bits 31-26: maximum 63. + */ *eax = CACHE_TYPE(cache->type) | CACHE_LEVEL(cache->level) | (cache->self_init ? CACHE_SELF_INIT_LEVEL : 0) | - (max_core_ids_in_package(topo_info) << 26) | - (max_thread_ids_for_cache(topo_info, cache->share_level) << 14); + (MIN(max_core_ids_in_package(topo_info), 63) << 26) | + (MIN(max_thread_ids_for_cache(topo_info, cache->share_level), 4095) << 14); assert(cache->line_size > 0); assert(cache->partitions > 0); @@ -430,7 +499,6 @@ static void encode_topo_cpuid1f(CPUX86State *env, uint32_t count, static uint32_t encode_cache_cpuid80000005(CPUCacheInfo *cache) { assert(cache->size % 1024 == 0); - assert(cache->lines_per_tag > 0); assert(cache->associativity > 0); assert(cache->line_size > 0); return ((cache->size / 1024) << 24) | (cache->associativity << 16) | @@ -439,8 +507,8 @@ static uint32_t encode_cache_cpuid80000005(CPUCacheInfo *cache) #define ASSOC_FULL 0xFF -/* AMD associativity encoding used on CPUID Leaf 0x80000006: */ -#define AMD_ENC_ASSOC(a) (a <= 1 ? a : \ +/* x86 associativity encoding used on CPUID Leaf 0x80000006: */ +#define X86_ENC_ASSOC(a) (a <= 1 ? a : \ a == 2 ? 0x2 : \ a == 4 ? 0x4 : \ a == 8 ? 0x6 : \ @@ -463,19 +531,18 @@ static void encode_cache_cpuid80000006(CPUCacheInfo *l2, { assert(l2->size % 1024 == 0); assert(l2->associativity > 0); - assert(l2->lines_per_tag > 0); assert(l2->line_size > 0); *ecx = ((l2->size / 1024) << 16) | - (AMD_ENC_ASSOC(l2->associativity) << 12) | + (X86_ENC_ASSOC(l2->associativity) << 12) | (l2->lines_per_tag << 8) | (l2->line_size); + /* For Intel, EDX is reserved. */ if (l3) { assert(l3->size % (512 * 1024) == 0); assert(l3->associativity > 0); - assert(l3->lines_per_tag > 0); assert(l3->line_size > 0); *edx = ((l3->size / (512 * 1024)) << 18) | - (AMD_ENC_ASSOC(l3->associativity) << 12) | + (X86_ENC_ASSOC(l3->associativity) << 12) | (l3->lines_per_tag << 8) | (l3->line_size); } else { *edx = 0; @@ -493,7 +560,8 @@ static void encode_cache_cpuid8000001d(CPUCacheInfo *cache, *eax = CACHE_TYPE(cache->type) | CACHE_LEVEL(cache->level) | (cache->self_init ? CACHE_SELF_INIT_LEVEL : 0); - *eax |= max_thread_ids_for_cache(topo_info, cache->share_level) << 14; + /* Bits 25:14 - NumSharingCache: maximum 4095. */ + *eax |= MIN(max_thread_ids_for_cache(topo_info, cache->share_level), 4095) << 14; assert(cache->line_size > 0); assert(cache->partitions > 0); @@ -573,117 +641,172 @@ static void encode_topo_cpuid8000001e(X86CPU *cpu, X86CPUTopoInfo *topo_info, * These are legacy cache values. If there is a need to change any * of these values please use builtin_x86_defs */ - -/* L1 data cache: */ -static CPUCacheInfo legacy_l1d_cache = { - .type = DATA_CACHE, - .level = 1, - .size = 32 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 8, - .sets = 64, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/*FIXME: CPUID leaf 0x80000005 is inconsistent with leaves 2 & 4 */ -static CPUCacheInfo legacy_l1d_cache_amd = { - .type = DATA_CACHE, - .level = 1, - .size = 64 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 2, - .sets = 512, - .partitions = 1, - .lines_per_tag = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/* L1 instruction cache: */ -static CPUCacheInfo legacy_l1i_cache = { - .type = INSTRUCTION_CACHE, - .level = 1, - .size = 32 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 8, - .sets = 64, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/*FIXME: CPUID leaf 0x80000005 is inconsistent with leaves 2 & 4 */ -static CPUCacheInfo legacy_l1i_cache_amd = { - .type = INSTRUCTION_CACHE, - .level = 1, - .size = 64 * KiB, - .self_init = 1, - .line_size = 64, - .associativity = 2, - .sets = 512, - .partitions = 1, - .lines_per_tag = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/* Level 2 unified cache: */ -static CPUCacheInfo legacy_l2_cache = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 4 * MiB, - .self_init = 1, - .line_size = 64, - .associativity = 16, - .sets = 4096, - .partitions = 1, - .no_invd_sharing = true, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, -}; - -/*FIXME: CPUID leaf 2 descriptor is inconsistent with CPUID leaf 4 */ -static CPUCacheInfo legacy_l2_cache_cpuid2 = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 2 * MiB, - .line_size = 64, - .associativity = 8, - .share_level = CPU_TOPOLOGY_LEVEL_INVALID, +static const CPUCaches legacy_amd_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 64 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 2, + .sets = 512, + .partitions = 1, + .lines_per_tag = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 64 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 2, + .sets = 512, + .partitions = 1, + .lines_per_tag = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 512 * KiB, + .line_size = 64, + .lines_per_tag = 1, + .associativity = 16, + .sets = 512, + .partitions = 1, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, }; - -/*FIXME: CPUID leaf 0x80000006 is inconsistent with leaves 2 & 4 */ -static CPUCacheInfo legacy_l2_cache_amd = { - .type = UNIFIED_CACHE, - .level = 2, - .size = 512 * KiB, - .line_size = 64, - .lines_per_tag = 1, - .associativity = 16, - .sets = 512, - .partitions = 1, - .share_level = CPU_TOPOLOGY_LEVEL_CORE, +/* + * Only used for the CPU models with CPUID level < 4. + * These CPUs (CPUID level < 4) only use CPUID leaf 2 to present + * cache information. + * + * Note: This cache model is just a default one, and is not + * guaranteed to match real hardwares. + */ +static const CPUCaches legacy_intel_cpuid2_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 2 * MiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 4096, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, }; -/* Level 3 unified cache: */ -static CPUCacheInfo legacy_l3_cache = { - .type = UNIFIED_CACHE, - .level = 3, - .size = 16 * MiB, - .line_size = 64, - .associativity = 16, - .sets = 16384, - .partitions = 1, - .lines_per_tag = 1, - .self_init = true, - .inclusive = true, - .complex_indexing = true, - .share_level = CPU_TOPOLOGY_LEVEL_DIE, +static const CPUCaches legacy_intel_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + .type = DATA_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + .type = INSTRUCTION_CACHE, + .level = 1, + .size = 32 * KiB, + .self_init = 1, + .line_size = 64, + .associativity = 8, + .sets = 64, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 2, + .size = 4 * MiB, + .self_init = 1, + .line_size = 64, + .associativity = 16, + .sets = 4096, + .partitions = 1, + .no_invd_sharing = true, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + .type = UNIFIED_CACHE, + .level = 3, + .size = 16 * MiB, + .line_size = 64, + .associativity = 16, + .sets = 16384, + .partitions = 1, + .lines_per_tag = 1, + .self_init = true, + .inclusive = true, + .complex_indexing = true, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, + }, }; /* TLB definitions: */ @@ -1943,7 +2066,7 @@ uint32_t xsave_area_size(uint64_t mask, bool compacted) static inline bool accel_uses_host_cpuid(void) { - return kvm_enabled() || hvf_enabled(); + return !tcg_enabled() && !qtest_enabled(); } static inline uint64_t x86_cpu_xsave_xcr0_components(X86CPU *cpu) @@ -2764,6 +2887,378 @@ static const CPUCaches epyc_turin_cache_info = { .no_invd_sharing = true, .complex_indexing = false, .share_level = CPU_TOPOLOGY_LEVEL_DIE, + } +}; + +static const CPUCaches xeon_spr_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 48 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 32 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 2048, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 2 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 15, + + /* CPUID 0x4.0x3.ECX */ + .sets = 65536, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 60 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, + }, +}; + +static const CPUCaches xeon_gnr_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 48 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 64 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 2048, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 2 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x3.ECX */ + .sets = 294912, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 288 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, + }, +}; + +static const CPUCaches xeon_srf_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 32 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x1.ECX */ + .sets = 128, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 64 * KiB, + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x2.ECX */ + .sets = 4096, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + .size = 4 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_MODULE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 12, + + /* CPUID 0x4.0x3.ECX */ + .sets = 147456, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = true, + + .size = 108 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_SOCKET, + }, +}; + +static const CPUCaches yongfeng_cache_info = { + .l1d_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x0.EAX */ + .type = DATA_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x0.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x0.ECX */ + .sets = 64, + + /* CPUID 0x4.0x0.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + /* CPUID 0x80000005.ECX */ + .lines_per_tag = 1, + .size = 32 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l1i_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x1.EAX */ + .type = INSTRUCTION_CACHE, + .level = 1, + .self_init = true, + + /* CPUID 0x4.0x1.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x1.ECX */ + .sets = 64, + + /* CPUID 0x4.0x1.EDX */ + .no_invd_sharing = false, + .inclusive = false, + .complex_indexing = false, + + /* CPUID 0x80000005.EDX */ + .lines_per_tag = 1, + .size = 64 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l2_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x2.EAX */ + .type = UNIFIED_CACHE, + .level = 2, + .self_init = true, + + /* CPUID 0x4.0x2.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 8, + + /* CPUID 0x4.0x2.ECX */ + .sets = 512, + + /* CPUID 0x4.0x2.EDX */ + .no_invd_sharing = false, + .inclusive = true, + .complex_indexing = false, + + /* CPUID 0x80000006.ECX */ + .size = 256 * KiB, + + .share_level = CPU_TOPOLOGY_LEVEL_CORE, + }, + .l3_cache = &(CPUCacheInfo) { + /* CPUID 0x4.0x3.EAX */ + .type = UNIFIED_CACHE, + .level = 3, + .self_init = true, + + /* CPUID 0x4.0x3.EBX */ + .line_size = 64, + .partitions = 1, + .associativity = 16, + + /* CPUID 0x4.0x3.ECX */ + .sets = 8192, + + /* CPUID 0x4.0x3.EDX */ + .no_invd_sharing = true, + .inclusive = true, + .complex_indexing = false, + + .size = 8 * MiB, + .share_level = CPU_TOPOLOGY_LEVEL_DIE, }, }; @@ -3019,6 +3514,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { I486_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium", @@ -3031,6 +3527,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium2", @@ -3043,6 +3540,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM2_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "pentium3", @@ -3055,6 +3553,7 @@ static const X86CPUDefinition builtin_x86_defs[] = { PENTIUM3_FEATURES, .xlevel = 0, .model_id = "", + .cache_info = &legacy_intel_cpuid2_cache_info, }, { .name = "athlon", @@ -4587,6 +5086,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 4, + .note = "with spr-sp cache model and 0x1f leaf", + .cache_info = &xeon_spr_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ } } }, @@ -4740,6 +5248,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with gnr-sp cache model and 0x1f leaf", + .cache_info = &xeon_gnr_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ }, }, }, @@ -4885,6 +5402,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with srf-sp cache model and 0x1f leaf", + .cache_info = &xeon_srf_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ }, }, }, @@ -6027,6 +6553,15 @@ static const X86CPUDefinition builtin_x86_defs[] = { { /* end of list */ } } }, + { + .version = 3, + .note = "with the cache model and 0x1f leaf", + .cache_info = &yongfeng_cache_info, + .props = (PropValue[]) { + { "x-force-cpuid-0x1f", "on" }, + { /* end of list */ }, + } + }, { /* end of list */ } } }, @@ -6187,6 +6722,7 @@ static void max_x86_cpu_class_init(ObjectClass *oc, const void *data) xcc->ordering = 9; + xcc->max_features = true; xcc->model_description = "Enables all features supported by the accelerator in the current host"; @@ -6197,22 +6733,21 @@ static void max_x86_cpu_class_init(ObjectClass *oc, const void *data) static void max_x86_cpu_initfn(Object *obj) { X86CPU *cpu = X86_CPU(obj); - - /* We can't fill the features array here because we don't know yet if - * "migratable" is true or false. - */ - cpu->max_features = true; - object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); + CPUX86State *env = &cpu->env; /* - * these defaults are used for TCG and all other accelerators - * besides KVM and HVF, which overwrite these values + * these defaults are used for TCG, other accelerators have overwritten + * these values */ - object_property_set_str(OBJECT(cpu), "vendor", CPUID_VENDOR_AMD, - &error_abort); - object_property_set_str(OBJECT(cpu), "model-id", - "QEMU TCG CPU version " QEMU_HW_VERSION, - &error_abort); + if (!env->cpuid_vendor1) { + object_property_set_str(OBJECT(cpu), "vendor", CPUID_VENDOR_AMD, + &error_abort); + } + if (!env->cpuid_model[0]) { + object_property_set_str(OBJECT(cpu), "model-id", + "QEMU TCG CPU version " QEMU_HW_VERSION, + &error_abort); + } } static const TypeInfo max_x86_cpu_type_info = { @@ -6324,10 +6859,7 @@ static void x86_cpuid_version_get_family(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = (env->cpuid_version >> 8) & 0xf; - if (value == 0xf) { - value += (env->cpuid_version >> 20) & 0xff; - } + value = x86_cpu_family(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -6365,8 +6897,7 @@ static void x86_cpuid_version_get_model(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = (env->cpuid_version >> 4) & 0xf; - value |= ((env->cpuid_version >> 16) & 0xf) << 4; + value = x86_cpu_model(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -6400,7 +6931,7 @@ static void x86_cpuid_version_get_stepping(Object *obj, Visitor *v, CPUX86State *env = &cpu->env; uint64_t value; - value = env->cpuid_version & 0xf; + value = x86_cpu_stepping(env->cpuid_version); visit_type_uint64(v, name, &value, errp); } @@ -6468,11 +6999,11 @@ static char *x86_cpuid_get_model_id(Object *obj, Error **errp) char *value; int i; - value = g_malloc(48 + 1); - for (i = 0; i < 48; i++) { + value = g_malloc(CPUID_MODEL_ID_SZ + 1); + for (i = 0; i < CPUID_MODEL_ID_SZ; i++) { value[i] = env->cpuid_model[i >> 2] >> (8 * (i & 3)); } - value[48] = '\0'; + value[CPUID_MODEL_ID_SZ] = '\0'; return value; } @@ -6487,7 +7018,7 @@ static void x86_cpuid_set_model_id(Object *obj, const char *model_id, model_id = ""; } len = strlen(model_id); - memset(env->cpuid_model, 0, 48); + memset(env->cpuid_model, 0, CPUID_MODEL_ID_SZ); for (i = 0; i < 48; i++) { if (i >= len) { c = '\0'; @@ -7347,11 +7878,35 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } *edx = env->features[FEAT_1_EDX]; if (threads_per_pkg > 1) { - *ebx |= threads_per_pkg << 16; + uint32_t num; + + /* + * For CPUID.01H.EBX[Bits 23-16], AMD requires logical processor + * count, but Intel needs maximum number of addressable IDs for + * logical processors per package. + */ + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + num = 1 << apicid_pkg_offset(topo_info); + } else { + num = threads_per_pkg; + } + + /* Fixup overflow: max value for bits 23-16 is 255. */ + *ebx |= MIN(num, 255) << 16; } break; - case 2: - /* cache info: needed for Pentium Pro compatibility */ + case 2: { /* cache info: needed for Pentium Pro compatibility */ + const CPUCaches *caches; + + if (env->enable_legacy_cpuid2_cache) { + caches = &legacy_intel_cpuid2_cache_info; + } else if (env->enable_legacy_vendor_cache) { + caches = &legacy_intel_cache_info; + } else { + caches = &env->cache_info; + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; @@ -7359,18 +7914,18 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *eax = *ebx = *ecx = *edx = 0; break; } - *eax = 1; /* Number of CPUID[EAX=2] calls required */ - *ebx = 0; - if (!cpu->enable_l3_cache) { - *ecx = 0; + encode_cache_cpuid2(cpu, caches, eax, ebx, ecx, edx); + break; + } + case 4: { + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_intel_cache_info; } else { - *ecx = cpuid2_cache_descriptor(env->cache_info_cpuid2.l3_cache); + caches = &env->cache_info; } - *edx = (cpuid2_cache_descriptor(env->cache_info_cpuid2.l1d_cache) << 16) | - (cpuid2_cache_descriptor(env->cache_info_cpuid2.l1i_cache) << 8) | - (cpuid2_cache_descriptor(env->cache_info_cpuid2.l2_cache)); - break; - case 4: + /* cache info: needed for Core compatibility */ if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, count, eax, ebx, ecx, edx); @@ -7382,13 +7937,13 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, int host_vcpus_per_cache = 1 + ((*eax & 0x3FFC000) >> 14); *eax &= ~0xFC000000; - *eax |= max_core_ids_in_package(topo_info) << 26; + *eax |= MIN(max_core_ids_in_package(topo_info), 63) << 26; if (host_vcpus_per_cache > threads_per_pkg) { *eax &= ~0x3FFC000; /* Share the cache at package level. */ - *eax |= max_thread_ids_for_cache(topo_info, - CPU_TOPOLOGY_LEVEL_SOCKET) << 14; + *eax |= MIN(max_thread_ids_for_cache(topo_info, + CPU_TOPOLOGY_LEVEL_SOCKET), 4095) << 14; } } } else if (cpu->vendor_cpuid_only && IS_AMD_CPU(env)) { @@ -7398,30 +7953,26 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, switch (count) { case 0: /* L1 dcache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l1d_cache, - topo_info, + encode_cache_cpuid4(caches->l1d_cache, topo_info, eax, ebx, ecx, edx); if (!cpu->l1_cache_per_core) { *eax &= ~MAKE_64BIT_MASK(14, 12); } break; case 1: /* L1 icache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l1i_cache, - topo_info, + encode_cache_cpuid4(caches->l1i_cache, topo_info, eax, ebx, ecx, edx); if (!cpu->l1_cache_per_core) { *eax &= ~MAKE_64BIT_MASK(14, 12); } break; case 2: /* L2 cache info */ - encode_cache_cpuid4(env->cache_info_cpuid4.l2_cache, - topo_info, + encode_cache_cpuid4(caches->l2_cache, topo_info, eax, ebx, ecx, edx); break; case 3: /* L3 cache info */ if (cpu->enable_l3_cache) { - encode_cache_cpuid4(env->cache_info_cpuid4.l3_cache, - topo_info, + encode_cache_cpuid4(caches->l3_cache, topo_info, eax, ebx, ecx, edx); break; } @@ -7432,6 +7983,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } } break; + } case 5: /* MONITOR/MWAIT Leaf */ *eax = cpu->mwait.eax; /* Smallest monitor-line size in bytes */ @@ -7522,21 +8074,6 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, assert(!(*eax & ~0x1f)); *ebx &= 0xffff; /* The count doesn't need to be reliable. */ break; - case 0x1C: - if (cpu->enable_pmu && (env->features[FEAT_7_0_EDX] & CPUID_7_0_EDX_ARCH_LBR)) { - x86_cpu_get_supported_cpuid(0x1C, 0, eax, ebx, ecx, edx); - *edx = 0; - } - break; - case 0x1F: - /* V2 Extended Topology Enumeration Leaf */ - if (!x86_has_cpuid_0x1f(cpu)) { - *eax = *ebx = *ecx = *edx = 0; - break; - } - - encode_topo_cpuid1f(env, count, topo_info, eax, ebx, ecx, edx); - break; case 0xD: { /* Processor Extended State */ *eax = 0; @@ -7677,6 +8214,12 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } break; } + case 0x1C: + if (cpu->enable_pmu && (env->features[FEAT_7_0_EDX] & CPUID_7_0_EDX_ARCH_LBR)) { + x86_cpu_get_supported_cpuid(0x1C, 0, eax, ebx, ecx, edx); + *edx = 0; + } + break; case 0x1D: { /* AMX TILE, for now hardcoded for Sapphire Rapids*/ *eax = 0; @@ -7714,6 +8257,15 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } break; } + case 0x1F: + /* V2 Extended Topology Enumeration Leaf */ + if (!x86_has_cpuid_0x1f(cpu)) { + *eax = *ebx = *ecx = *edx = 0; + break; + } + + encode_topo_cpuid1f(env, count, topo_info, eax, ebx, ecx, edx); + break; case 0x24: { *eax = 0; *ebx = 0; @@ -7750,9 +8302,15 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, break; case 0x80000000: *eax = env->cpuid_xlevel; - *ebx = env->cpuid_vendor1; - *edx = env->cpuid_vendor2; - *ecx = env->cpuid_vendor3; + + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *ebx = *ecx = *edx = 0; + } else { + *ebx = env->cpuid_vendor1; + *edx = env->cpuid_vendor2; + *ecx = env->cpuid_vendor3; + } break; case 0x80000001: *eax = env->cpuid_version; @@ -7760,7 +8318,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = env->features[FEAT_8000_0001_ECX]; *edx = env->features[FEAT_8000_0001_EDX]; - if (tcg_enabled() && env->cpuid_vendor1 == CPUID_VENDOR_INTEL_1 && + if (tcg_enabled() && IS_INTEL_CPU(env) && !(env->hflags & HF_LMA_MASK)) { *edx &= ~CPUID_EXT2_SYSCALL; } @@ -7773,41 +8331,78 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = env->cpuid_model[(index - 0x80000002) * 4 + 2]; *edx = env->cpuid_model[(index - 0x80000002) * 4 + 3]; break; - case 0x80000005: - /* cache info (L1 cache) */ + case 0x80000005: { + /* cache info (L1 cache/TLB Associativity Field) */ + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_amd_cache_info; + } else { + caches = &env->cache_info; + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; } + + if (cpu->vendor_cpuid_only_v2 && IS_INTEL_CPU(env)) { + *eax = *ebx = *ecx = *edx = 0; + break; + } + *eax = (L1_DTLB_2M_ASSOC << 24) | (L1_DTLB_2M_ENTRIES << 16) | (L1_ITLB_2M_ASSOC << 8) | (L1_ITLB_2M_ENTRIES); *ebx = (L1_DTLB_4K_ASSOC << 24) | (L1_DTLB_4K_ENTRIES << 16) | (L1_ITLB_4K_ASSOC << 8) | (L1_ITLB_4K_ENTRIES); - *ecx = encode_cache_cpuid80000005(env->cache_info_amd.l1d_cache); - *edx = encode_cache_cpuid80000005(env->cache_info_amd.l1i_cache); + *ecx = encode_cache_cpuid80000005(caches->l1d_cache); + *edx = encode_cache_cpuid80000005(caches->l1i_cache); break; - case 0x80000006: - /* cache info (L2 cache) */ + } + case 0x80000006: { /* cache info (L2 cache/TLB/L3 cache) */ + const CPUCaches *caches; + + if (env->enable_legacy_vendor_cache) { + caches = &legacy_amd_cache_info; + } else { + caches = &env->cache_info; + } + if (cpu->cache_info_passthrough) { x86_cpu_get_cache_cpuid(index, 0, eax, ebx, ecx, edx); break; } - *eax = (AMD_ENC_ASSOC(L2_DTLB_2M_ASSOC) << 28) | + + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *eax = *ebx = 0; + encode_cache_cpuid80000006(caches->l2_cache, + NULL, ecx, edx); + break; + } + + *eax = (X86_ENC_ASSOC(L2_DTLB_2M_ASSOC) << 28) | (L2_DTLB_2M_ENTRIES << 16) | - (AMD_ENC_ASSOC(L2_ITLB_2M_ASSOC) << 12) | + (X86_ENC_ASSOC(L2_ITLB_2M_ASSOC) << 12) | (L2_ITLB_2M_ENTRIES); - *ebx = (AMD_ENC_ASSOC(L2_DTLB_4K_ASSOC) << 28) | + *ebx = (X86_ENC_ASSOC(L2_DTLB_4K_ASSOC) << 28) | (L2_DTLB_4K_ENTRIES << 16) | - (AMD_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | + (X86_ENC_ASSOC(L2_ITLB_4K_ASSOC) << 12) | (L2_ITLB_4K_ENTRIES); - encode_cache_cpuid80000006(env->cache_info_amd.l2_cache, + + encode_cache_cpuid80000006(caches->l2_cache, cpu->enable_l3_cache ? - env->cache_info_amd.l3_cache : NULL, + caches->l3_cache : NULL, ecx, edx); break; + } case 0x80000007: *eax = 0; - *ebx = env->features[FEAT_8000_0007_EBX]; + if (cpu->vendor_cpuid_only_v2 && IS_INTEL_CPU(env)) { + *ebx = 0; + } else { + *ebx = env->features[FEAT_8000_0007_EBX]; + } *ecx = 0; *edx = env->features[FEAT_8000_0007_EDX]; break; @@ -7820,6 +8415,17 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *eax |= (cpu->guest_phys_bits << 16); } *ebx = env->features[FEAT_8000_0008_EBX]; + + /* + * Don't emulate Bits [7:0] & Bits [15:12] for Intel/Zhaoxin, since + * they're using 0x1f leaf. + */ + if (cpu->vendor_cpuid_only_v2 && + (IS_INTEL_CPU(env) || IS_ZHAOXIN_CPU(env))) { + *ecx = *edx = 0; + break; + } + if (threads_per_pkg > 1) { /* * Bits 15:12 is "The number of bits in the initial @@ -7855,19 +8461,19 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } switch (count) { case 0: /* L1 dcache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l1d_cache, + encode_cache_cpuid8000001d(env->cache_info.l1d_cache, topo_info, eax, ebx, ecx, edx); break; case 1: /* L1 icache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l1i_cache, + encode_cache_cpuid8000001d(env->cache_info.l1i_cache, topo_info, eax, ebx, ecx, edx); break; case 2: /* L2 cache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l2_cache, + encode_cache_cpuid8000001d(env->cache_info.l2_cache, topo_info, eax, ebx, ecx, edx); break; case 3: /* L3 cache info */ - encode_cache_cpuid8000001d(env->cache_info_amd.l3_cache, + encode_cache_cpuid8000001d(env->cache_info.l3_cache, topo_info, eax, ebx, ecx, edx); break; default: /* end of info */ @@ -7888,6 +8494,21 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *edx = 0; } break; + case 0x8000001F: + *eax = *ebx = *ecx = *edx = 0; + if (sev_enabled()) { + *eax = 0x2; + *eax |= sev_es_enabled() ? 0x8 : 0; + *eax |= sev_snp_enabled() ? 0x10 : 0; + *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */ + *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */ + } + break; + case 0x80000021: + *eax = *ebx = *ecx = *edx = 0; + *eax = env->features[FEAT_8000_0021_EAX]; + *ebx = env->features[FEAT_8000_0021_EBX]; + break; case 0x80000022: *eax = *ebx = *ecx = *edx = 0; /* AMD Extended Performance Monitoring and Debug */ @@ -7920,21 +8541,6 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, *ecx = 0; *edx = 0; break; - case 0x8000001F: - *eax = *ebx = *ecx = *edx = 0; - if (sev_enabled()) { - *eax = 0x2; - *eax |= sev_es_enabled() ? 0x8 : 0; - *eax |= sev_snp_enabled() ? 0x10 : 0; - *ebx = sev_get_cbit_position() & 0x3f; /* EBX[5:0] */ - *ebx |= (sev_get_reduced_phys_bits() & 0x3f) << 6; /* EBX[11:6] */ - } - break; - case 0x80000021: - *eax = *ebx = *ecx = *edx = 0; - *eax = env->features[FEAT_8000_0021_EAX]; - *ebx = env->features[FEAT_8000_0021_EBX]; - break; default: /* reserved values: zero */ *eax = 0; @@ -8154,7 +8760,7 @@ static void mce_init(X86CPU *cpu) CPUX86State *cenv = &cpu->env; unsigned int bank; - if (((cenv->cpuid_version >> 8) & 0xf) >= 6 + if (x86_cpu_family(cenv->cpuid_version) >= 6 && (cenv->features[FEAT_1_EDX] & (CPUID_MCE | CPUID_MCA)) == (CPUID_MCE | CPUID_MCA)) { cenv->mcg_cap = MCE_CAP_DEF | MCE_BANKS_DEF | @@ -8282,6 +8888,7 @@ static void x86_cpu_enable_xsave_components(X86CPU *cpu) */ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) { + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); CPUX86State *env = &cpu->env; FeatureWord w; int i; @@ -8301,12 +8908,12 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) } } - /*TODO: Now cpu->max_features doesn't overwrite features + /* TODO: Now xcc->max_features doesn't overwrite features * set using QOM properties, and we can convert * plus_features & minus_features to global properties * inside x86_cpu_parse_featurestr() too. */ - if (cpu->max_features) { + if (xcc->max_features) { for (w = 0; w < FEATURE_WORDS; w++) { /* Override only features that weren't set explicitly * by the user. @@ -8338,7 +8945,8 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) } } - if (!cpu->enable_pmu) { + /* PDCM is fixed1 bit for TDX */ + if (!cpu->enable_pmu && !is_tdx_vm()) { mark_unavailable_features(cpu, FEAT_1_ECX, env->user_features[FEAT_1_ECX] & CPUID_EXT_PDCM, "This feature is not available due to PMU being disabled"); @@ -8574,46 +9182,34 @@ static bool x86_cpu_update_smp_cache_topo(MachineState *ms, X86CPU *cpu, level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l1d_cache->share_level = level; - env->cache_info_amd.l1d_cache->share_level = level; + env->cache_info.l1d_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, - env->cache_info_cpuid4.l1d_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1D, - env->cache_info_amd.l1d_cache->share_level); + env->cache_info.l1d_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l1i_cache->share_level = level; - env->cache_info_amd.l1i_cache->share_level = level; + env->cache_info.l1i_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, - env->cache_info_cpuid4.l1i_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L1I, - env->cache_info_amd.l1i_cache->share_level); + env->cache_info.l1i_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l2_cache->share_level = level; - env->cache_info_amd.l2_cache->share_level = level; + env->cache_info.l2_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, - env->cache_info_cpuid4.l2_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L2, - env->cache_info_amd.l2_cache->share_level); + env->cache_info.l2_cache->share_level); } level = machine_get_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3); if (level != CPU_TOPOLOGY_LEVEL_DEFAULT) { - env->cache_info_cpuid4.l3_cache->share_level = level; - env->cache_info_amd.l3_cache->share_level = level; + env->cache_info.l3_cache->share_level = level; } else { machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, - env->cache_info_cpuid4.l3_cache->share_level); - machine_set_cache_topo_level(ms, CACHE_LEVEL_AND_TYPE_L3, - env->cache_info_amd.l3_cache->share_level); + env->cache_info.l3_cache->share_level); } if (!machine_check_smp_cache(ms, errp)) { @@ -8637,6 +9233,16 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) tcg_cflags_set(cs, CF_PCREL); #endif + /* + * x-vendor-cpuid-only and v2 should be initernal only. But + * QEMU doesn't support "internal" property. + */ + if (!cpu->vendor_cpuid_only && cpu->vendor_cpuid_only_v2) { + error_setg(errp, "x-vendor-cpuid-only-v2 property " + "depends on x-vendor-cpuid-only"); + return; + } + if (cpu->apic_id == UNASSIGNED_APIC_ID) { error_setg(errp, "apic-id property was not initialized properly"); return; @@ -8840,24 +9446,22 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) "CPU model '%s' doesn't support legacy-cache=off", name); return; } - env->cache_info_cpuid2 = env->cache_info_cpuid4 = env->cache_info_amd = - *cache_info; + env->cache_info = *cache_info; } else { /* Build legacy cache information */ - env->cache_info_cpuid2.l1d_cache = &legacy_l1d_cache; - env->cache_info_cpuid2.l1i_cache = &legacy_l1i_cache; - env->cache_info_cpuid2.l2_cache = &legacy_l2_cache_cpuid2; - env->cache_info_cpuid2.l3_cache = &legacy_l3_cache; + if (!cpu->consistent_cache) { + env->enable_legacy_cpuid2_cache = true; + } - env->cache_info_cpuid4.l1d_cache = &legacy_l1d_cache; - env->cache_info_cpuid4.l1i_cache = &legacy_l1i_cache; - env->cache_info_cpuid4.l2_cache = &legacy_l2_cache; - env->cache_info_cpuid4.l3_cache = &legacy_l3_cache; + if (!cpu->vendor_cpuid_only_v2) { + env->enable_legacy_vendor_cache = true; + } - env->cache_info_amd.l1d_cache = &legacy_l1d_cache_amd; - env->cache_info_amd.l1i_cache = &legacy_l1i_cache_amd; - env->cache_info_amd.l2_cache = &legacy_l2_cache_amd; - env->cache_info_amd.l3_cache = &legacy_l3_cache; + if (IS_AMD_CPU(env)) { + env->cache_info = legacy_amd_cache_info; + } else { + env->cache_info = legacy_intel_cache_info; + } } #ifndef CONFIG_USER_ONLY @@ -9036,8 +9640,6 @@ static void x86_cpu_post_initfn(Object *obj) } } - accel_cpu_instance_init(CPU(obj)); - #ifndef CONFIG_USER_ONLY if (current_machine && current_machine->cgs) { x86_confidential_guest_cpu_instance_init( @@ -9112,6 +9714,8 @@ static void x86_cpu_initfn(Object *obj) if (xcc->model) { x86_cpu_load_model(cpu, xcc->model); } + + accel_cpu_instance_init(CPU(obj)); } static int64_t x86_cpu_get_arch_id(CPUState *cs) @@ -9358,6 +9962,7 @@ static const Property x86_cpu_properties[] = { DEFINE_PROP_STRING("hv-vendor-id", X86CPU, hyperv_vendor), DEFINE_PROP_BOOL("cpuid-0xb", X86CPU, enable_cpuid_0xb, true), DEFINE_PROP_BOOL("x-vendor-cpuid-only", X86CPU, vendor_cpuid_only, true), + DEFINE_PROP_BOOL("x-vendor-cpuid-only-v2", X86CPU, vendor_cpuid_only_v2, true), DEFINE_PROP_BOOL("x-amd-topoext-features-only", X86CPU, amd_topoext_features_only, true), DEFINE_PROP_BOOL("lmce", X86CPU, enable_lmce, false), DEFINE_PROP_BOOL("l3-cache", X86CPU, enable_l3_cache, true), @@ -9372,6 +9977,7 @@ static const Property x86_cpu_properties[] = { * own cache information (see x86_cpu_load_def()). */ DEFINE_PROP_BOOL("legacy-cache", X86CPU, legacy_cache, true), + DEFINE_PROP_BOOL("x-consistent-cache", X86CPU, consistent_cache, true), DEFINE_PROP_BOOL("legacy-multi-node", X86CPU, legacy_multi_node, false), DEFINE_PROP_BOOL("xen-vapic", X86CPU, xen_vapic, false), @@ -9393,6 +9999,7 @@ static const Property x86_cpu_properties[] = { DEFINE_PROP_BOOL("x-intel-pt-auto-level", X86CPU, intel_pt_auto_level, true), DEFINE_PROP_BOOL("x-l1-cache-per-thread", X86CPU, l1_cache_per_core, true), + DEFINE_PROP_BOOL("x-force-cpuid-0x1f", X86CPU, force_cpuid_0x1f, false), }; #ifndef CONFIG_USER_ONLY diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 51e1013..f977fc4 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1159,7 +1159,8 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); /* PMM enabled */ #define CPUID_C000_0001_EDX_PMM_EN (1U << 13) -#define CPUID_VENDOR_SZ 12 +#define CPUID_VENDOR_SZ 12 +#define CPUID_MODEL_ID_SZ 48 #define CPUID_VENDOR_INTEL_1 0x756e6547 /* "Genu" */ #define CPUID_VENDOR_INTEL_2 0x49656e69 /* "ineI" */ @@ -2072,11 +2073,14 @@ typedef struct CPUArchState { /* Features that were explicitly enabled/disabled */ FeatureWordArray user_features; uint32_t cpuid_model[12]; - /* Cache information for CPUID. When legacy-cache=on, the cache data + /* + * Cache information for CPUID. When legacy-cache=on, the cache data * on each CPUID leaf will be different, because we keep compatibility * with old QEMU versions. */ - CPUCaches cache_info_cpuid2, cache_info_cpuid4, cache_info_amd; + CPUCaches cache_info; + bool enable_legacy_cpuid2_cache; + bool enable_legacy_vendor_cache; /* MTRRs */ uint64_t mtrr_fixed[11]; @@ -2196,7 +2200,6 @@ struct ArchCPU { bool expose_tcg; bool migratable; bool migrate_smi_count; - bool max_features; /* Enable all supported features automatically */ uint32_t apic_id; /* Enables publishing of TSC increment and Local APIC bus frequencies to @@ -2259,6 +2262,13 @@ struct ArchCPU { */ bool legacy_cache; + /* + * Compatibility bits for old machine types. + * If true, use the same cache model in CPUID leaf 0x2 + * and 0x4. + */ + bool consistent_cache; + /* Compatibility bits for old machine types. * If true decode the CPUID Function 0x8000001E_ECX to support multiple * nodes per processor @@ -2274,9 +2284,18 @@ struct ArchCPU { /* Enable auto level-increase for all CPUID leaves */ bool full_cpuid_auto_level; - /* Only advertise CPUID leaves defined by the vendor */ + /* + * Compatibility bits for old machine types (PC machine v6.0 and older). + * Only advertise CPUID leaves defined by the vendor. + */ bool vendor_cpuid_only; + /* + * Compatibility bits for old machine types (PC machine v10.0 and older). + * Only advertise CPUID leaves defined by the vendor. + */ + bool vendor_cpuid_only_v2; + /* Only advertise TOPOEXT features that AMD defines */ bool amd_topoext_features_only; @@ -2349,6 +2368,7 @@ struct X86CPUClass { */ const X86CPUModel *model; + bool max_features; /* Enable all supported features automatically */ bool host_cpuid_required; int ordering; bool migration_safe; @@ -2417,7 +2437,14 @@ static inline void cpu_x86_load_seg_cache(CPUX86State *env, SegmentCache *sc; unsigned int new_hflags; - sc = &env->segs[seg_reg]; + if (seg_reg == R_LDTR) { + sc = &env->ldt; + } else if (seg_reg == R_TR) { + sc = &env->tr; + } else { + sc = &env->segs[seg_reg]; + } + sc->selector = selector; sc->base = base; sc->limit = limit; @@ -2670,6 +2697,36 @@ static inline int32_t x86_get_a20_mask(CPUX86State *env) } } +static inline uint32_t x86_cpu_family(uint32_t eax) +{ + uint32_t family = (eax >> 8) & 0xf; + + if (family == 0xf) { + family += (eax >> 20) & 0xff; + } + + return family; +} + +static inline uint32_t x86_cpu_model(uint32_t eax) +{ + uint32_t family, model; + + family = x86_cpu_family(eax); + model = (eax >> 4) & 0xf; + + if (family >= 0x6) { + model += ((eax >> 16) & 0xf) << 4; + } + + return model; +} + +static inline uint32_t x86_cpu_stepping(uint32_t eax) +{ + return eax & 0xf; +} + static inline bool cpu_has_vmx(CPUX86State *env) { return env->features[FEAT_1_ECX] & CPUID_EXT_VMX; diff --git a/target/i386/host-cpu.c b/target/i386/host-cpu.c index 7512567..d5e2bb5 100644 --- a/target/i386/host-cpu.c +++ b/target/i386/host-cpu.c @@ -80,7 +80,6 @@ bool host_cpu_realizefn(CPUState *cs, Error **errp) return true; } -#define CPUID_MODEL_ID_SZ 48 /** * cpu_x86_fill_model_id: * Get CPUID model ID string from host CPU. @@ -118,13 +117,13 @@ void host_cpu_vendor_fms(char *vendor, int *family, int *model, int *stepping) host_cpuid(0x1, 0, &eax, &ebx, &ecx, &edx); if (family) { - *family = ((eax >> 8) & 0x0F) + ((eax >> 20) & 0xFF); + *family = x86_cpu_family(eax); } if (model) { - *model = ((eax >> 4) & 0x0F) | ((eax & 0xF0000) >> 12); + *model = x86_cpu_model(eax); } if (stepping) { - *stepping = eax & 0x0F; + *stepping = x86_cpu_stepping(eax); } } @@ -132,27 +131,27 @@ void host_cpu_instance_init(X86CPU *cpu) { X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); - if (xcc->model) { - char vendor[CPUID_VENDOR_SZ + 1]; - - host_cpu_vendor_fms(vendor, NULL, NULL, NULL); - object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); - } -} - -void host_cpu_max_instance_init(X86CPU *cpu) -{ char vendor[CPUID_VENDOR_SZ + 1] = { 0 }; char model_id[CPUID_MODEL_ID_SZ + 1] = { 0 }; int family, model, stepping; - /* Use max host physical address bits if -cpu max option is applied */ - object_property_set_bool(OBJECT(cpu), "host-phys-bits", true, &error_abort); - + /* + * setting vendor applies to both max/host and builtin_x86_defs CPU. + * FIXME: this probably should warn or should be skipped if vendors do + * not match, because family numbers are incompatible between Intel and AMD. + */ host_cpu_vendor_fms(vendor, &family, &model, &stepping); + object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); + + if (!xcc->max_features) { + return; + } + host_cpu_fill_model_id(model_id); - object_property_set_str(OBJECT(cpu), "vendor", vendor, &error_abort); + /* Use max host physical address bits if -cpu max option is applied */ + object_property_set_bool(OBJECT(cpu), "host-phys-bits", true, &error_abort); + object_property_set_int(OBJECT(cpu), "family", family, &error_abort); object_property_set_int(OBJECT(cpu), "model", model, &error_abort); object_property_set_int(OBJECT(cpu), "stepping", stepping, @@ -161,6 +160,15 @@ void host_cpu_max_instance_init(X86CPU *cpu) &error_abort); } +bool is_host_cpu_intel(void) +{ + char vendor[CPUID_VENDOR_SZ + 1]; + + host_cpu_vendor_fms(vendor, NULL, NULL, NULL); + + return g_str_equal(vendor, CPUID_VENDOR_INTEL); +} + static void host_cpu_class_init(ObjectClass *oc, const void *data) { X86CPUClass *xcc = X86_CPU_CLASS(oc); diff --git a/target/i386/host-cpu.h b/target/i386/host-cpu.h index b97ec01..10df4b3 100644 --- a/target/i386/host-cpu.h +++ b/target/i386/host-cpu.h @@ -17,4 +17,5 @@ bool host_cpu_realizefn(CPUState *cs, Error **errp); void host_cpu_vendor_fms(char *vendor, int *family, int *model, int *stepping); +bool is_host_cpu_intel(void); #endif /* HOST_CPU_H */ diff --git a/target/i386/hvf/hvf-cpu.c b/target/i386/hvf/hvf-cpu.c index dfdda70..94ee096 100644 --- a/target/i386/hvf/hvf-cpu.c +++ b/target/i386/hvf/hvf-cpu.c @@ -21,8 +21,6 @@ static void hvf_cpu_max_instance_init(X86CPU *cpu) { CPUX86State *env = &cpu->env; - host_cpu_max_instance_init(cpu); - env->cpuid_min_level = hvf_get_supported_cpuid(0x0, 0, R_EAX); env->cpuid_min_xlevel = @@ -61,13 +59,14 @@ static void hvf_cpu_xsave_init(void) static void hvf_cpu_instance_init(CPUState *cs) { X86CPU *cpu = X86_CPU(cs); + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); host_cpu_instance_init(cpu); /* Special cases not set in the X86CPUDefinition structs: */ /* TODO: in-kernel irqchip for hvf */ - if (cpu->max_features) { + if (xcc->max_features) { hvf_cpu_max_instance_init(cpu); } diff --git a/target/i386/kvm/kvm-cpu.c b/target/i386/kvm/kvm-cpu.c index 16bde4d..89a7953 100644 --- a/target/i386/kvm/kvm-cpu.c +++ b/target/i386/kvm/kvm-cpu.c @@ -41,6 +41,7 @@ static void kvm_set_guest_phys_bits(CPUState *cs) static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) { X86CPU *cpu = X86_CPU(cs); + X86CPUClass *xcc = X86_CPU_GET_CLASS(cpu); CPUX86State *env = &cpu->env; bool ret; @@ -63,7 +64,7 @@ static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) * check/update ucode_rev, phys_bits, guest_phys_bits, mwait * cpu_common_realizefn() (via xcc->parent_realize) */ - if (cpu->max_features) { + if (xcc->max_features) { if (enable_cpu_pm) { if (kvm_has_waitpkg()) { env->features[FEAT_7_0_ECX] |= CPUID_7_0_ECX_WAITPKG; @@ -72,7 +73,7 @@ static bool kvm_cpu_realizefn(CPUState *cs, Error **errp) if (env->features[FEAT_1_ECX] & CPUID_EXT_MONITOR) { host_cpuid(5, 0, &cpu->mwait.eax, &cpu->mwait.ebx, &cpu->mwait.ecx, &cpu->mwait.edx); - } + } } if (cpu->ucode_rev == 0) { cpu->ucode_rev = @@ -108,7 +109,7 @@ static void kvm_cpu_max_instance_init(X86CPU *cpu) CPUX86State *env = &cpu->env; KVMState *s = kvm_state; - host_cpu_max_instance_init(cpu); + object_property_set_bool(OBJECT(cpu), "pmu", true, &error_abort); if (lmce_supported()) { object_property_set_bool(OBJECT(cpu), "lmce", true, &error_abort); @@ -216,7 +217,7 @@ static void kvm_cpu_instance_init(CPUState *cs) x86_cpu_apply_props(cpu, kvm_default_props); } - if (cpu->max_features) { + if (xcc->max_features) { kvm_cpu_max_instance_init(cpu); } diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c index 234878c..e8c8be0 100644 --- a/target/i386/kvm/kvm.c +++ b/target/i386/kvm/kvm.c @@ -2259,7 +2259,7 @@ int kvm_arch_init_vcpu(CPUState *cs) cpuid_i = kvm_x86_build_cpuid(env, cpuid_data.entries, cpuid_i); cpuid_data.cpuid.nent = cpuid_i; - if (((env->cpuid_version >> 8)&0xF) >= 6 + if (x86_cpu_family(env->cpuid_version) >= 6 && (env->features[FEAT_1_EDX] & (CPUID_MCE | CPUID_MCA)) == (CPUID_MCE | CPUID_MCA)) { uint64_t mcg_cap, unsupported_caps; @@ -6182,6 +6182,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) case TDVMCALL_GET_TD_VM_CALL_INFO: tdx_handle_get_tdvmcall_info(cpu, run); break; + case TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT: + tdx_handle_setup_event_notify_interrupt(cpu, run); + break; } ret = 0; break; diff --git a/target/i386/kvm/tdx-quote-generator.c b/target/i386/kvm/tdx-quote-generator.c index f59715f..dee8334 100644 --- a/target/i386/kvm/tdx-quote-generator.c +++ b/target/i386/kvm/tdx-quote-generator.c @@ -75,7 +75,9 @@ static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task) { timer_del(&task->timer); - g_source_remove(task->watch); + if (task->watch) { + g_source_remove(task->watch); + } qio_channel_close(QIO_CHANNEL(task->sioc), NULL); object_unref(OBJECT(task->sioc)); diff --git a/target/i386/kvm/tdx-stub.c b/target/i386/kvm/tdx-stub.c index 76fee49..1f0e108 100644 --- a/target/i386/kvm/tdx-stub.c +++ b/target/i386/kvm/tdx-stub.c @@ -26,3 +26,7 @@ void tdx_handle_get_quote(X86CPU *cpu, struct kvm_run *run) void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) { } + +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run) +{ +} diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index e809e4b..7d69d6d 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -28,10 +28,13 @@ #include "cpu.h" #include "cpu-internal.h" #include "host-cpu.h" +#include "hw/i386/apic_internal.h" +#include "hw/i386/apic-msidef.h" #include "hw/i386/e820_memory_layout.h" #include "hw/i386/tdvf.h" #include "hw/i386/x86.h" #include "hw/i386/tdvf-hob.h" +#include "hw/pci/msi.h" #include "kvm_i386.h" #include "tdx.h" #include "tdx-quote-generator.h" @@ -1123,6 +1126,28 @@ int tdx_parse_tdvf(void *flash_ptr, int size) return tdvf_parse_metadata(&tdx_guest->tdvf, flash_ptr, size); } +static void tdx_inject_interrupt(uint32_t apicid, uint32_t vector) +{ + int ret; + + if (vector < 32 || vector > 255) { + return; + } + + MSIMessage msg = { + .address = ((apicid & 0xff) << MSI_ADDR_DEST_ID_SHIFT) | + (((uint64_t)apicid & 0xffffff00) << 32), + .data = vector | (APIC_DM_FIXED << MSI_DATA_DELIVERY_MODE_SHIFT), + }; + + ret = kvm_irqchip_send_msi(kvm_state, msg); + if (ret < 0) { + /* In this case, no better way to tell it to guest. Log it. */ + error_report("TDX: injection interrupt %d failed, interrupt lost (%s).", + vector, strerror(-ret)); + } +} + static void tdx_get_quote_completion(TdxGenerateQuoteTask *task) { TdxGuest *tdx = task->opaque; @@ -1154,6 +1179,9 @@ static void tdx_get_quote_completion(TdxGenerateQuoteTask *task) error_report("TDX: get-quote: failed to update GetQuote header."); } + tdx_inject_interrupt(tdx_guest->event_notify_apicid, + tdx_guest->event_notify_vector); + g_free(task->send_data); g_free(task->receive_buf); g_free(task); @@ -1256,20 +1284,45 @@ out_free: g_free(task); } +#define SUPPORTED_TDVMCALLINFO_1_R11 (TDG_VP_VMCALL_SUBFUNC_SET_EVENT_NOTIFY_INTERRUPT) +#define SUPPORTED_TDVMCALLINFO_1_R12 (0) + void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run) { if (run->tdx.get_tdvmcall_info.leaf != 1) { - return; + return; } - run->tdx.get_tdvmcall_info.r11 = TDG_VP_VMCALL_SUBFUNC_GET_QUOTE; - run->tdx.get_tdvmcall_info.r12 = 0; + run->tdx.get_tdvmcall_info.r11 = (tdx_caps->user_tdvmcallinfo_1_r11 & + SUPPORTED_TDVMCALLINFO_1_R11) | + tdx_caps->kernel_tdvmcallinfo_1_r11; + run->tdx.get_tdvmcall_info.r12 = (tdx_caps->user_tdvmcallinfo_1_r12 & + SUPPORTED_TDVMCALLINFO_1_R12) | + tdx_caps->kernel_tdvmcallinfo_1_r12; run->tdx.get_tdvmcall_info.r13 = 0; run->tdx.get_tdvmcall_info.r14 = 0; + + run->tdx.get_tdvmcall_info.ret = TDG_VP_VMCALL_SUCCESS; +} + +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run) +{ + uint64_t vector = run->tdx.setup_event_notify.vector; + + if (vector >= 32 && vector < 256) { + qemu_mutex_lock(&tdx_guest->lock); + tdx_guest->event_notify_vector = vector; + tdx_guest->event_notify_apicid = cpu->apic_id; + qemu_mutex_unlock(&tdx_guest->lock); + run->tdx.setup_event_notify.ret = TDG_VP_VMCALL_SUCCESS; + } else { + run->tdx.setup_event_notify.ret = TDG_VP_VMCALL_INVALID_OPERAND; + } } static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, - char *message, uint64_t gpa) + char *message, bool has_gpa, + uint64_t gpa) { GuestPanicInformation *panic_info; @@ -1278,6 +1331,7 @@ static void tdx_panicked_on_fatal_error(X86CPU *cpu, uint64_t error_code, panic_info->u.tdx.error_code = (uint32_t) error_code; panic_info->u.tdx.message = message; panic_info->u.tdx.gpa = gpa; + panic_info->u.tdx.has_gpa = has_gpa; qemu_system_guest_panicked(panic_info); } @@ -1297,6 +1351,7 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) char *message = NULL; uint64_t *tmp; uint64_t gpa = -1ull; + bool has_gpa = false; if (error_code & 0xffff) { error_report("TDX: REPORT_FATAL_ERROR: invalid error code: 0x%"PRIx64, @@ -1329,9 +1384,10 @@ int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run) if (error_code & TDX_REPORT_FATAL_ERROR_GPA_VALID) { gpa = run->system_event.data[R_R13]; + has_gpa = true; } - tdx_panicked_on_fatal_error(cpu, error_code, message, gpa); + tdx_panicked_on_fatal_error(cpu, error_code, message, has_gpa, gpa); return -1; } @@ -1468,6 +1524,9 @@ static void tdx_guest_init(Object *obj) NULL, NULL); qemu_mutex_init(&tdx->lock); + + tdx->event_notify_vector = -1; + tdx->event_notify_apicid = -1; } static void tdx_guest_finalize(Object *obj) diff --git a/target/i386/kvm/tdx.h b/target/i386/kvm/tdx.h index 35a09c1..1c38faf 100644 --- a/target/i386/kvm/tdx.h +++ b/target/i386/kvm/tdx.h @@ -25,6 +25,7 @@ typedef struct TdxGuestClass { #define TDVMCALL_GET_TD_VM_CALL_INFO 0x10000 #define TDVMCALL_GET_QUOTE 0x10002 +#define TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT 0x10004 #define TDG_VP_VMCALL_SUCCESS 0x0000000000000000ULL #define TDG_VP_VMCALL_RETRY 0x0000000000000001ULL @@ -32,7 +33,7 @@ typedef struct TdxGuestClass { #define TDG_VP_VMCALL_GPA_INUSE 0x8000000000000001ULL #define TDG_VP_VMCALL_ALIGN_ERROR 0x8000000000000002ULL -#define TDG_VP_VMCALL_SUBFUNC_GET_QUOTE 0x0000000000000001ULL +#define TDG_VP_VMCALL_SUBFUNC_SET_EVENT_NOTIFY_INTERRUPT BIT_ULL(1) enum TdxRamType { TDX_RAM_UNACCEPTED, @@ -66,6 +67,9 @@ typedef struct TdxGuest { /* GetQuote */ SocketAddress *qg_sock_addr; int num; + + uint32_t event_notify_vector; + uint32_t event_notify_apicid; } TdxGuest; #ifdef CONFIG_TDX @@ -80,5 +84,6 @@ int tdx_parse_tdvf(void *flash_ptr, int size); int tdx_handle_report_fatal_error(X86CPU *cpu, struct kvm_run *run); void tdx_handle_get_quote(X86CPU *cpu, struct kvm_run *run); void tdx_handle_get_tdvmcall_info(X86CPU *cpu, struct kvm_run *run); +void tdx_handle_setup_event_notify_interrupt(X86CPU *cpu, struct kvm_run *run); #endif /* QEMU_I386_TDX_H */ diff --git a/target/i386/kvm/vmsr_energy.c b/target/i386/kvm/vmsr_energy.c index d6aad52..58ce3df 100644 --- a/target/i386/kvm/vmsr_energy.c +++ b/target/i386/kvm/vmsr_energy.c @@ -27,15 +27,6 @@ char *vmsr_compute_default_paths(void) return g_build_filename(state, "run", "qemu-vmsr-helper.sock", NULL); } -bool is_host_cpu_intel(void) -{ - char vendor[CPUID_VENDOR_SZ + 1]; - - host_cpu_vendor_fms(vendor, NULL, NULL, NULL); - - return g_str_equal(vendor, CPUID_VENDOR_INTEL); -} - int is_rapl_enabled(void) { const char *path = "/sys/class/powercap/intel-rapl/enabled"; diff --git a/target/i386/kvm/vmsr_energy.h b/target/i386/kvm/vmsr_energy.h index 16cc1f4..151bcbd 100644 --- a/target/i386/kvm/vmsr_energy.h +++ b/target/i386/kvm/vmsr_energy.h @@ -94,6 +94,5 @@ double vmsr_get_ratio(uint64_t e_delta, unsigned long long delta_ticks, unsigned int maxticks); void vmsr_init_topo_info(X86CPUTopoInfo *topo_info, const MachineState *ms); -bool is_host_cpu_intel(void); int is_rapl_enabled(void); #endif /* VMSR_ENERGY_H */ diff --git a/target/i386/meson.build b/target/i386/meson.build index c1aacea..092af34 100644 --- a/target/i386/meson.build +++ b/target/i386/meson.build @@ -11,6 +11,8 @@ i386_ss.add(when: 'CONFIG_SEV', if_true: files('host-cpu.c', 'confidential-guest # x86 cpu type i386_ss.add(when: 'CONFIG_KVM', if_true: files('host-cpu.c')) i386_ss.add(when: 'CONFIG_HVF', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_WHPX', if_true: files('host-cpu.c')) +i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c')) i386_system_ss = ss.source_set() i386_system_ss.add(files( diff --git a/target/i386/nvmm/nvmm-accel-ops.c b/target/i386/nvmm/nvmm-accel-ops.c index a5517b0..3799260 100644 --- a/target/i386/nvmm/nvmm-accel-ops.c +++ b/target/i386/nvmm/nvmm-accel-ops.c @@ -10,7 +10,7 @@ #include "qemu/osdep.h" #include "system/kvm_int.h" #include "qemu/main-loop.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "qemu/guest-random.h" diff --git a/target/i386/nvmm/nvmm-all.c b/target/i386/nvmm/nvmm-all.c index b4a4d50..92e3b8b 100644 --- a/target/i386/nvmm/nvmm-all.c +++ b/target/i386/nvmm/nvmm-all.c @@ -12,6 +12,7 @@ #include "system/address-spaces.h" #include "system/ioport.h" #include "qemu/accel.h" +#include "accel/accel-ops.h" #include "system/nvmm.h" #include "system/cpus.h" #include "system/runstate.h" @@ -19,6 +20,8 @@ #include "qemu/error-report.h" #include "qapi/error.h" #include "qemu/queue.h" +#include "accel/accel-cpu-target.h" +#include "host-cpu.h" #include "migration/blocker.h" #include "strings.h" @@ -1207,10 +1210,33 @@ static const TypeInfo nvmm_accel_type = { .class_init = nvmm_accel_class_init, }; +static void nvmm_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); +} + +static void nvmm_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = nvmm_cpu_instance_init; +} + +static const TypeInfo nvmm_cpu_accel_type = { + .name = ACCEL_CPU_NAME("nvmm"), + + .parent = TYPE_ACCEL_CPU, + .class_init = nvmm_cpu_accel_class_init, + .abstract = true, +}; + static void nvmm_type_init(void) { type_register_static(&nvmm_accel_type); + type_register_static(&nvmm_cpu_accel_type); } type_init(nvmm_type_init); diff --git a/target/i386/sev.c b/target/i386/sev.c index 1a12f06..1057b8a 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -41,7 +41,9 @@ #include "confidential-guest.h" #include "hw/i386/pc.h" #include "system/address-spaces.h" +#include "hw/i386/e820_memory_layout.h" #include "qemu/queue.h" +#include "qemu/cutils.h" OBJECT_DECLARE_TYPE(SevCommonState, SevCommonStateClass, SEV_COMMON) OBJECT_DECLARE_TYPE(SevGuestState, SevCommonStateClass, SEV_GUEST) @@ -50,6 +52,15 @@ OBJECT_DECLARE_TYPE(SevSnpGuestState, SevCommonStateClass, SEV_SNP_GUEST) /* hard code sha256 digest size */ #define HASH_SIZE 32 +/* Hard coded GPA that KVM uses for the VMSA */ +#define KVM_VMSA_GPA 0xFFFFFFFFF000 + +/* Convert between SEV-ES VMSA and SegmentCache flags/attributes */ +#define FLAGS_VMSA_TO_SEGCACHE(flags) \ + ((((flags) & 0xff00) << 12) | (((flags) & 0xff) << 8)) +#define FLAGS_SEGCACHE_TO_VMSA(flags) \ + ((((flags) & 0xff00) >> 8) | (((flags) & 0xf00000) >> 12)) + typedef struct QEMU_PACKED SevHashTableEntry { QemuUUID guid; uint16_t len; @@ -89,6 +100,14 @@ typedef struct QEMU_PACKED SevHashTableDescriptor { uint32_t size; } SevHashTableDescriptor; +typedef struct SevLaunchVmsa { + QTAILQ_ENTRY(SevLaunchVmsa) next; + + uint16_t cpu_index; + uint64_t gpa; + struct sev_es_save_area vmsa; +} SevLaunchVmsa; + struct SevCommonState { X86ConfidentialGuest parent_obj; @@ -99,6 +118,8 @@ struct SevCommonState { uint32_t cbitpos; uint32_t reduced_phys_bits; bool kernel_hashes; + uint64_t sev_features; + uint64_t supported_sev_features; /* runtime state */ uint8_t api_major; @@ -107,9 +128,7 @@ struct SevCommonState { int sev_fd; SevState state; - uint32_t reset_cs; - uint32_t reset_ip; - bool reset_data_valid; + QTAILQ_HEAD(, SevLaunchVmsa) launch_vmsa; }; struct SevCommonStateClass { @@ -122,7 +141,8 @@ struct SevCommonStateClass { Error **errp); int (*launch_start)(SevCommonState *sev_common); void (*launch_finish)(SevCommonState *sev_common); - int (*launch_update_data)(SevCommonState *sev_common, hwaddr gpa, uint8_t *ptr, size_t len); + int (*launch_update_data)(SevCommonState *sev_common, hwaddr gpa, + uint8_t *ptr, size_t len, Error **errp); int (*kvm_init)(ConfidentialGuestSupport *cgs, Error **errp); }; @@ -363,6 +383,288 @@ static struct RAMBlockNotifier sev_ram_notifier = { .ram_block_removed = sev_ram_block_removed, }; +static void sev_apply_cpu_context(CPUState *cpu) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + X86CPU *x86; + CPUX86State *env; + struct SevLaunchVmsa *launch_vmsa; + + /* See if an initial VMSA has been provided for this CPU */ + QTAILQ_FOREACH(launch_vmsa, &sev_common->launch_vmsa, next) + { + if (cpu->cpu_index == launch_vmsa->cpu_index) { + x86 = X86_CPU(cpu); + env = &x86->env; + + /* + * Ideally we would provide the VMSA directly to kvm which would + * ensure that the resulting initial VMSA measurement which is + * calculated during KVM_SEV_LAUNCH_UPDATE_VMSA is calculated from + * exactly what we provide here. Currently this is not possible so + * we need to copy the parts of the VMSA structure that we currently + * support into the CPU state. + */ + cpu_load_efer(env, launch_vmsa->vmsa.efer); + cpu_x86_update_cr4(env, launch_vmsa->vmsa.cr4); + cpu_x86_update_cr0(env, launch_vmsa->vmsa.cr0); + cpu_x86_update_cr3(env, launch_vmsa->vmsa.cr3); + env->xcr0 = launch_vmsa->vmsa.xcr0; + env->pat = launch_vmsa->vmsa.g_pat; + + cpu_x86_load_seg_cache( + env, R_CS, launch_vmsa->vmsa.cs.selector, + launch_vmsa->vmsa.cs.base, launch_vmsa->vmsa.cs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.cs.attrib)); + cpu_x86_load_seg_cache( + env, R_DS, launch_vmsa->vmsa.ds.selector, + launch_vmsa->vmsa.ds.base, launch_vmsa->vmsa.ds.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ds.attrib)); + cpu_x86_load_seg_cache( + env, R_ES, launch_vmsa->vmsa.es.selector, + launch_vmsa->vmsa.es.base, launch_vmsa->vmsa.es.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.es.attrib)); + cpu_x86_load_seg_cache( + env, R_FS, launch_vmsa->vmsa.fs.selector, + launch_vmsa->vmsa.fs.base, launch_vmsa->vmsa.fs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.fs.attrib)); + cpu_x86_load_seg_cache( + env, R_GS, launch_vmsa->vmsa.gs.selector, + launch_vmsa->vmsa.gs.base, launch_vmsa->vmsa.gs.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.gs.attrib)); + cpu_x86_load_seg_cache( + env, R_SS, launch_vmsa->vmsa.ss.selector, + launch_vmsa->vmsa.ss.base, launch_vmsa->vmsa.ss.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ss.attrib)); + + env->gdt.base = launch_vmsa->vmsa.gdtr.base; + env->gdt.limit = launch_vmsa->vmsa.gdtr.limit; + env->gdt.flags = + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.gdtr.attrib); + env->idt.base = launch_vmsa->vmsa.idtr.base; + env->idt.limit = launch_vmsa->vmsa.idtr.limit; + env->idt.flags = + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.idtr.attrib); + + cpu_x86_load_seg_cache( + env, R_LDTR, launch_vmsa->vmsa.ldtr.selector, + launch_vmsa->vmsa.ldtr.base, launch_vmsa->vmsa.ldtr.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.ldtr.attrib)); + cpu_x86_load_seg_cache( + env, R_TR, launch_vmsa->vmsa.tr.selector, + launch_vmsa->vmsa.ldtr.base, launch_vmsa->vmsa.tr.limit, + FLAGS_VMSA_TO_SEGCACHE(launch_vmsa->vmsa.tr.attrib)); + + env->dr[6] = launch_vmsa->vmsa.dr6; + env->dr[7] = launch_vmsa->vmsa.dr7; + + env->regs[R_EAX] = launch_vmsa->vmsa.rax; + env->regs[R_ECX] = launch_vmsa->vmsa.rcx; + env->regs[R_EDX] = launch_vmsa->vmsa.rdx; + env->regs[R_EBX] = launch_vmsa->vmsa.rbx; + env->regs[R_ESP] = launch_vmsa->vmsa.rsp; + env->regs[R_EBP] = launch_vmsa->vmsa.rbp; + env->regs[R_ESI] = launch_vmsa->vmsa.rsi; + env->regs[R_EDI] = launch_vmsa->vmsa.rdi; +#ifdef TARGET_X86_64 + env->regs[R_R8] = launch_vmsa->vmsa.r8; + env->regs[R_R9] = launch_vmsa->vmsa.r9; + env->regs[R_R10] = launch_vmsa->vmsa.r10; + env->regs[R_R11] = launch_vmsa->vmsa.r11; + env->regs[R_R12] = launch_vmsa->vmsa.r12; + env->regs[R_R13] = launch_vmsa->vmsa.r13; + env->regs[R_R14] = launch_vmsa->vmsa.r14; + env->regs[R_R15] = launch_vmsa->vmsa.r15; +#endif + env->eip = launch_vmsa->vmsa.rip; + env->eflags = launch_vmsa->vmsa.rflags; + + cpu_set_fpuc(env, launch_vmsa->vmsa.x87_fcw); + env->mxcsr = launch_vmsa->vmsa.mxcsr; + + break; + } + } +} + +static int check_sev_features(SevCommonState *sev_common, uint64_t sev_features, + Error **errp) +{ + /* + * Ensure SEV_FEATURES is configured for correct SEV hardware and that + * the requested features are supported. If SEV-SNP is enabled then + * that feature must be enabled, otherwise it must be cleared. + */ + if (sev_snp_enabled() && !(sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) { + error_setg( + errp, + "%s: SEV_SNP is enabled but is not enabled in VMSA sev_features", + __func__); + return -1; + } else if (!sev_snp_enabled() && + (sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) { + error_setg( + errp, + "%s: SEV_SNP is not enabled but is enabled in VMSA sev_features", + __func__); + return -1; + } + if (sev_features & ~sev_common->supported_sev_features) { + error_setg(errp, + "%s: VMSA contains unsupported sev_features: %lX, " + "supported features: %lX", + __func__, sev_features, sev_common->supported_sev_features); + return -1; + } + return 0; +} + +static int check_vmsa_supported(SevCommonState *sev_common, hwaddr gpa, + const struct sev_es_save_area *vmsa, + Error **errp) +{ + struct sev_es_save_area vmsa_check; + + /* + * KVM always populates the VMSA at a fixed GPA which cannot be modified + * from userspace. Specifying a different GPA will not prevent the guest + * from starting but will cause the launch measurement to be different + * from expected. Therefore check that the provided GPA matches the KVM + * hardcoded value. + */ + if (gpa != KVM_VMSA_GPA) { + error_setg(errp, + "%s: The VMSA GPA must be %lX but is specified as %lX", + __func__, KVM_VMSA_GPA, gpa); + return -1; + } + + /* + * Clear all supported fields so we can then check the entire structure + * is zero. + */ + memcpy(&vmsa_check, vmsa, sizeof(struct sev_es_save_area)); + memset(&vmsa_check.es, 0, sizeof(vmsa_check.es)); + memset(&vmsa_check.cs, 0, sizeof(vmsa_check.cs)); + memset(&vmsa_check.ss, 0, sizeof(vmsa_check.ss)); + memset(&vmsa_check.ds, 0, sizeof(vmsa_check.ds)); + memset(&vmsa_check.fs, 0, sizeof(vmsa_check.fs)); + memset(&vmsa_check.gs, 0, sizeof(vmsa_check.gs)); + memset(&vmsa_check.gdtr, 0, sizeof(vmsa_check.gdtr)); + memset(&vmsa_check.idtr, 0, sizeof(vmsa_check.idtr)); + memset(&vmsa_check.ldtr, 0, sizeof(vmsa_check.ldtr)); + memset(&vmsa_check.tr, 0, sizeof(vmsa_check.tr)); + vmsa_check.efer = 0; + vmsa_check.cr0 = 0; + vmsa_check.cr3 = 0; + vmsa_check.cr4 = 0; + vmsa_check.xcr0 = 0; + vmsa_check.dr6 = 0; + vmsa_check.dr7 = 0; + vmsa_check.rax = 0; + vmsa_check.rcx = 0; + vmsa_check.rdx = 0; + vmsa_check.rbx = 0; + vmsa_check.rsp = 0; + vmsa_check.rbp = 0; + vmsa_check.rsi = 0; + vmsa_check.rdi = 0; + vmsa_check.r8 = 0; + vmsa_check.r9 = 0; + vmsa_check.r10 = 0; + vmsa_check.r11 = 0; + vmsa_check.r12 = 0; + vmsa_check.r13 = 0; + vmsa_check.r14 = 0; + vmsa_check.r15 = 0; + vmsa_check.rip = 0; + vmsa_check.rflags = 0; + + vmsa_check.g_pat = 0; + vmsa_check.xcr0 = 0; + + vmsa_check.x87_fcw = 0; + vmsa_check.mxcsr = 0; + + if (check_sev_features(sev_common, vmsa_check.sev_features, errp) < 0) { + return -1; + } + vmsa_check.sev_features = 0; + + if (!buffer_is_zero(&vmsa_check, sizeof(vmsa_check))) { + error_setg(errp, + "%s: The VMSA contains fields that are not " + "synchronized with KVM. Continuing would result in " + "either unpredictable guest behavior, or a " + "mismatched launch measurement.", + __func__); + return -1; + } + return 0; +} + +static int sev_set_cpu_context(uint16_t cpu_index, const void *ctx, + uint32_t ctx_len, hwaddr gpa, Error **errp) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + SevLaunchVmsa *launch_vmsa; + CPUState *cpu; + bool exists = false; + + /* + * Setting the CPU context is only supported for SEV-ES and SEV-SNP. The + * context buffer will contain a sev_es_save_area from the Linux kernel + * which is defined by "Table B-4. VMSA Layout, State Save Area for SEV-ES" + * in the AMD64 APM, Volume 2. + */ + + if (!sev_es_enabled()) { + error_setg(errp, "SEV: unable to set CPU context: Not supported"); + return -1; + } + + if (ctx_len < sizeof(struct sev_es_save_area)) { + error_setg(errp, "SEV: unable to set CPU context: " + "Invalid context provided"); + return -1; + } + + cpu = qemu_get_cpu(cpu_index); + if (!cpu) { + error_setg(errp, "SEV: unable to set CPU context for out of bounds " + "CPU index %d", cpu_index); + return -1; + } + + /* + * If the context of this VP has already been set then replace it with the + * new context. + */ + QTAILQ_FOREACH(launch_vmsa, &sev_common->launch_vmsa, next) + { + if (cpu_index == launch_vmsa->cpu_index) { + launch_vmsa->gpa = gpa; + memcpy(&launch_vmsa->vmsa, ctx, sizeof(launch_vmsa->vmsa)); + exists = true; + break; + } + } + + if (!exists) { + /* New VP context */ + launch_vmsa = g_new0(SevLaunchVmsa, 1); + memcpy(&launch_vmsa->vmsa, ctx, sizeof(launch_vmsa->vmsa)); + launch_vmsa->cpu_index = cpu_index; + launch_vmsa->gpa = gpa; + QTAILQ_INSERT_TAIL(&sev_common->launch_vmsa, launch_vmsa, next); + } + + /* Synchronise the VMSA with the current CPU state */ + sev_apply_cpu_context(cpu); + + return 0; +} + bool sev_enabled(void) { @@ -970,9 +1272,8 @@ sev_snp_adjust_cpuid_features(X86ConfidentialGuest *cg, uint32_t feature, uint32 return value; } -static int -sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, - uint8_t *addr, size_t len) +static int sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, + uint8_t *addr, size_t len, Error **errp) { int ret, fw_error; struct kvm_sev_launch_update_data update; @@ -987,8 +1288,8 @@ sev_launch_update_data(SevCommonState *sev_common, hwaddr gpa, ret = sev_ioctl(sev_common->sev_fd, KVM_SEV_LAUNCH_UPDATE_DATA, &update, &fw_error); if (ret) { - error_report("%s: LAUNCH_UPDATE ret=%d fw_error=%d '%s'", - __func__, ret, fw_error, fw_error_to_str(fw_error)); + error_setg(errp, "%s: LAUNCH_UPDATE ret=%d fw_error=%d '%s'", __func__, + ret, fw_error, fw_error_to_str(fw_error)); } return ret; @@ -998,6 +1299,16 @@ static int sev_launch_update_vmsa(SevGuestState *sev_guest) { int ret, fw_error; + CPUState *cpu; + + /* + * The initial CPU state is measured as part of KVM_SEV_LAUNCH_UPDATE_VMSA. + * Synchronise the CPU state to any provided launch VMSA structures. + */ + CPU_FOREACH(cpu) { + sev_apply_cpu_context(cpu); + } + ret = sev_ioctl(SEV_COMMON(sev_guest)->sev_fd, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL, &fw_error); @@ -1116,8 +1427,8 @@ sev_launch_finish(SevCommonState *sev_common) migrate_add_blocker(&sev_mig_blocker, &error_fatal); } -static int -snp_launch_update_data(uint64_t gpa, void *hva, size_t len, int type) +static int snp_launch_update_data(uint64_t gpa, void *hva, size_t len, + int type, Error **errp) { SevLaunchUpdateData *data; @@ -1132,23 +1443,21 @@ snp_launch_update_data(uint64_t gpa, void *hva, size_t len, int type) return 0; } -static int -sev_snp_launch_update_data(SevCommonState *sev_common, hwaddr gpa, - uint8_t *ptr, size_t len) +static int sev_snp_launch_update_data(SevCommonState *sev_common, hwaddr gpa, + uint8_t *ptr, size_t len, Error **errp) { - int ret = snp_launch_update_data(gpa, ptr, len, - KVM_SEV_SNP_PAGE_TYPE_NORMAL); - return ret; + return snp_launch_update_data(gpa, ptr, len, + KVM_SEV_SNP_PAGE_TYPE_NORMAL, errp); } static int sev_snp_cpuid_info_fill(SnpCpuidInfo *snp_cpuid_info, - const KvmCpuidInfo *kvm_cpuid_info) + const KvmCpuidInfo *kvm_cpuid_info, Error **errp) { size_t i; if (kvm_cpuid_info->cpuid.nent > SNP_CPUID_FUNCTION_MAXCOUNT) { - error_report("SEV-SNP: CPUID entry count (%d) exceeds max (%d)", + error_setg(errp, "SEV-SNP: CPUID entry count (%d) exceeds max (%d)", kvm_cpuid_info->cpuid.nent, SNP_CPUID_FUNCTION_MAXCOUNT); return -1; } @@ -1190,8 +1499,8 @@ sev_snp_cpuid_info_fill(SnpCpuidInfo *snp_cpuid_info, return 0; } -static int -snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, size_t cpuid_len) +static int snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, + size_t cpuid_len, Error **errp) { KvmCpuidInfo kvm_cpuid_info = {0}; SnpCpuidInfo snp_cpuid_info; @@ -1208,26 +1517,25 @@ snp_launch_update_cpuid(uint32_t cpuid_addr, void *hva, size_t cpuid_len) } while (ret == -E2BIG); if (ret) { - error_report("SEV-SNP: unable to query CPUID values for CPU: '%s'", - strerror(-ret)); - return 1; + error_setg(errp, "SEV-SNP: unable to query CPUID values for CPU: '%s'", + strerror(-ret)); + return -1; } - ret = sev_snp_cpuid_info_fill(&snp_cpuid_info, &kvm_cpuid_info); - if (ret) { - error_report("SEV-SNP: failed to generate CPUID table information"); - return 1; + ret = sev_snp_cpuid_info_fill(&snp_cpuid_info, &kvm_cpuid_info, errp); + if (ret < 0) { + return -1; } memcpy(hva, &snp_cpuid_info, sizeof(snp_cpuid_info)); return snp_launch_update_data(cpuid_addr, hva, cpuid_len, - KVM_SEV_SNP_PAGE_TYPE_CPUID); + KVM_SEV_SNP_PAGE_TYPE_CPUID, errp); } -static int -snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, uint32_t addr, - void *hva, uint32_t len) +static int snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, + uint32_t addr, void *hva, + uint32_t len, Error **errp) { int type = KVM_SEV_SNP_PAGE_TYPE_ZERO; if (sev_snp->parent_obj.kernel_hashes) { @@ -1239,7 +1547,7 @@ snp_launch_update_kernel_hashes(SevSnpGuestState *sev_snp, uint32_t addr, sizeof(*sev_snp->kernel_hashes_data)); type = KVM_SEV_SNP_PAGE_TYPE_NORMAL; } - return snp_launch_update_data(addr, hva, len, type); + return snp_launch_update_data(addr, hva, len, type, errp); } static int @@ -1277,12 +1585,14 @@ snp_populate_metadata_pages(SevSnpGuestState *sev_snp, } if (type == KVM_SEV_SNP_PAGE_TYPE_CPUID) { - ret = snp_launch_update_cpuid(desc->base, hva, desc->len); + ret = snp_launch_update_cpuid(desc->base, hva, desc->len, + &error_fatal); } else if (desc->type == SEV_DESC_TYPE_SNP_KERNEL_HASHES) { ret = snp_launch_update_kernel_hashes(sev_snp, desc->base, hva, - desc->len); + desc->len, &error_fatal); } else { - ret = snp_launch_update_data(desc->base, hva, desc->len, type); + ret = snp_launch_update_data(desc->base, hva, desc->len, type, + &error_fatal); } if (ret) { @@ -1304,18 +1614,26 @@ sev_snp_launch_finish(SevCommonState *sev_common) struct kvm_sev_snp_launch_finish *finish = &sev_snp->kvm_finish_conf; /* - * To boot the SNP guest, the hypervisor is required to populate the CPUID - * and Secrets page before finalizing the launch flow. The location of - * the secrets and CPUID page is available through the OVMF metadata GUID. + * Populate all the metadata pages if not using an IGVM file. In the case + * where an IGVM file is provided it will be used to configure the metadata + * pages directly. */ - metadata = pc_system_get_ovmf_sev_metadata_ptr(); - if (metadata == NULL) { - error_report("%s: Failed to locate SEV metadata header", __func__); - exit(1); - } + if (!X86_MACHINE(qdev_get_machine())->igvm) { + /* + * To boot the SNP guest, the hypervisor is required to populate the + * CPUID and Secrets page before finalizing the launch flow. The + * location of the secrets and CPUID page is available through the + * OVMF metadata GUID. + */ + metadata = pc_system_get_ovmf_sev_metadata_ptr(); + if (metadata == NULL) { + error_report("%s: Failed to locate SEV metadata header", __func__); + exit(1); + } - /* Populate all the metadata pages */ - snp_populate_metadata_pages(sev_snp, metadata); + /* Populate all the metadata pages */ + snp_populate_metadata_pages(sev_snp, metadata); + } QTAILQ_FOREACH(data, &launch_update, next) { ret = sev_snp_launch_update(sev_snp, data); @@ -1425,6 +1743,39 @@ static int sev_snp_kvm_type(X86ConfidentialGuest *cg) return KVM_X86_SNP_VM; } +static int sev_init_supported_features(ConfidentialGuestSupport *cgs, + SevCommonState *sev_common, Error **errp) +{ + X86ConfidentialGuestClass *x86_klass = + X86_CONFIDENTIAL_GUEST_GET_CLASS(cgs); + /* + * Older kernels do not support query or setting of sev_features. In this + * case the set of supported features must be zero to match the settings + * in the kernel. + */ + if (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common)) == + KVM_X86_DEFAULT_VM) { + sev_common->supported_sev_features = 0; + return 0; + } + + /* Query KVM for the supported set of sev_features */ + struct kvm_device_attr attr = { + .group = KVM_X86_GRP_SEV, + .attr = KVM_X86_SEV_VMSA_FEATURES, + .addr = (unsigned long)&sev_common->supported_sev_features, + }; + if (kvm_ioctl(kvm_state, KVM_GET_DEVICE_ATTR, &attr) < 0) { + error_setg(errp, "%s: failed to query supported sev_features", + __func__); + return -1; + } + if (sev_snp_enabled()) { + sev_common->supported_sev_features |= SVM_SEV_FEAT_SNP_ACTIVE; + } + return 0; +} + static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) { char *devname; @@ -1505,6 +1856,10 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) } } + if (sev_init_supported_features(cgs, sev_common, errp) < 0) { + return -1; + } + trace_kvm_sev_init(); switch (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common))) { case KVM_X86_DEFAULT_VM: @@ -1516,6 +1871,40 @@ static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp) case KVM_X86_SEV_ES_VM: case KVM_X86_SNP_VM: { struct kvm_sev_init args = { 0 }; + MachineState *machine = MACHINE(qdev_get_machine()); + X86MachineState *x86machine = X86_MACHINE(qdev_get_machine()); + + /* + * If configuration is provided via an IGVM file then the IGVM file + * might contain configuration of the initial vcpu context. For SEV + * the vcpu context includes the sev_features which should be applied + * to the vcpu. + * + * KVM does not synchronize sev_features from CPU state. Instead it + * requires sev_features to be provided as part of this initialization + * call which is subsequently automatically applied to the VMSA of + * each vcpu. + * + * The IGVM file is normally processed after initialization. Therefore + * we need to pre-process it here to extract sev_features in order to + * provide it to KVM_SEV_INIT2. Each cgs_* function that is called by + * the IGVM processor detects this pre-process by observing the state + * as SEV_STATE_UNINIT. + */ + if (x86machine->igvm) { + if (IGVM_CFG_GET_CLASS(x86machine->igvm) + ->process(x86machine->igvm, machine->cgs, true, errp) == + -1) { + return -1; + } + /* + * KVM maintains a bitmask of allowed sev_features. This does not + * include SVM_SEV_FEAT_SNP_ACTIVE which is set accordingly by KVM + * itself. Therefore we need to clear this flag. + */ + args.vmsa_features = sev_common->sev_features & + ~SVM_SEV_FEAT_SNP_ACTIVE; + } ret = sev_ioctl(sev_common->sev_fd, KVM_SEV_INIT2, &args, &fw_error); break; @@ -1615,9 +2004,8 @@ sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp) if (sev_check_state(sev_common, SEV_STATE_LAUNCH_UPDATE)) { int ret; - ret = klass->launch_update_data(sev_common, gpa, ptr, len); + ret = klass->launch_update_data(sev_common, gpa, ptr, len, errp); if (ret < 0) { - error_setg(errp, "SEV: Failed to encrypt pflash rom"); return ret; } } @@ -1782,40 +2170,109 @@ sev_es_find_reset_vector(void *flash_ptr, uint64_t flash_size, return sev_es_parse_reset_block(info, addr); } -void sev_es_set_reset_vector(CPUState *cpu) + +static void seg_to_vmsa(const SegmentCache *cpu_seg, struct vmcb_seg *vmsa_seg) { - X86CPU *x86; - CPUX86State *env; - ConfidentialGuestSupport *cgs = MACHINE(qdev_get_machine())->cgs; - SevCommonState *sev_common = SEV_COMMON( - object_dynamic_cast(OBJECT(cgs), TYPE_SEV_COMMON)); + vmsa_seg->selector = cpu_seg->selector; + vmsa_seg->base = cpu_seg->base; + vmsa_seg->limit = cpu_seg->limit; + vmsa_seg->attrib = FLAGS_SEGCACHE_TO_VMSA(cpu_seg->flags); +} - /* Only update if we have valid reset information */ - if (!sev_common || !sev_common->reset_data_valid) { - return; - } +static void initialize_vmsa(const CPUState *cpu, struct sev_es_save_area *vmsa) +{ + const X86CPU *x86 = X86_CPU(cpu); + const CPUX86State *env = &x86->env; - /* Do not update the BSP reset state */ - if (cpu->cpu_index == 0) { - return; + /* + * Initialize the SEV-ES save area from the current state of + * the CPU. The entire state does not need to be copied, only the state + * that is copied back to the CPUState in sev_apply_cpu_context. + */ + memset(vmsa, 0, sizeof(struct sev_es_save_area)); + vmsa->efer = env->efer; + vmsa->cr0 = env->cr[0]; + vmsa->cr3 = env->cr[3]; + vmsa->cr4 = env->cr[4]; + vmsa->xcr0 = env->xcr0; + vmsa->g_pat = env->pat; + + seg_to_vmsa(&env->segs[R_CS], &vmsa->cs); + seg_to_vmsa(&env->segs[R_DS], &vmsa->ds); + seg_to_vmsa(&env->segs[R_ES], &vmsa->es); + seg_to_vmsa(&env->segs[R_FS], &vmsa->fs); + seg_to_vmsa(&env->segs[R_GS], &vmsa->gs); + seg_to_vmsa(&env->segs[R_SS], &vmsa->ss); + + seg_to_vmsa(&env->gdt, &vmsa->gdtr); + seg_to_vmsa(&env->idt, &vmsa->idtr); + seg_to_vmsa(&env->ldt, &vmsa->ldtr); + seg_to_vmsa(&env->tr, &vmsa->tr); + + vmsa->dr6 = env->dr[6]; + vmsa->dr7 = env->dr[7]; + + vmsa->rax = env->regs[R_EAX]; + vmsa->rcx = env->regs[R_ECX]; + vmsa->rdx = env->regs[R_EDX]; + vmsa->rbx = env->regs[R_EBX]; + vmsa->rsp = env->regs[R_ESP]; + vmsa->rbp = env->regs[R_EBP]; + vmsa->rsi = env->regs[R_ESI]; + vmsa->rdi = env->regs[R_EDI]; + +#ifdef TARGET_X86_64 + vmsa->r8 = env->regs[R_R8]; + vmsa->r9 = env->regs[R_R9]; + vmsa->r10 = env->regs[R_R10]; + vmsa->r11 = env->regs[R_R11]; + vmsa->r12 = env->regs[R_R12]; + vmsa->r13 = env->regs[R_R13]; + vmsa->r14 = env->regs[R_R14]; + vmsa->r15 = env->regs[R_R15]; +#endif + + vmsa->rip = env->eip; + vmsa->rflags = env->eflags; +} + +static void sev_es_set_ap_context(uint32_t reset_addr) +{ + CPUState *cpu; + struct sev_es_save_area vmsa; + SegmentCache cs; + + cs.selector = 0xf000; + cs.base = reset_addr & 0xffff0000; + cs.limit = 0xffff; + cs.flags = DESC_P_MASK | DESC_S_MASK | DESC_CS_MASK | DESC_R_MASK | + DESC_A_MASK; + + CPU_FOREACH(cpu) { + if (cpu->cpu_index == 0) { + /* Do not update the BSP reset state */ + continue; + } + initialize_vmsa(cpu, &vmsa); + seg_to_vmsa(&cs, &vmsa.cs); + vmsa.rip = reset_addr & 0x0000ffff; + sev_set_cpu_context(cpu->cpu_index, &vmsa, + sizeof(struct sev_es_save_area), + 0, &error_fatal); } +} - x86 = X86_CPU(cpu); - env = &x86->env; - - cpu_x86_load_seg_cache(env, R_CS, 0xf000, sev_common->reset_cs, 0xffff, - DESC_P_MASK | DESC_S_MASK | DESC_CS_MASK | - DESC_R_MASK | DESC_A_MASK); - - env->eip = sev_common->reset_ip; +void sev_es_set_reset_vector(CPUState *cpu) +{ + if (sev_enabled()) { + sev_apply_cpu_context(cpu); + } } int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size) { - CPUState *cpu; uint32_t addr; int ret; - SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); if (!sev_es_enabled()) { return 0; @@ -1828,14 +2285,12 @@ int sev_es_save_reset_vector(void *flash_ptr, uint64_t flash_size) return ret; } + /* + * The reset vector is saved into a CPU context for each AP but not for + * the BSP. This is applied during guest startup or when the CPU is reset. + */ if (addr) { - sev_common->reset_cs = addr & 0xffff0000; - sev_common->reset_ip = addr & 0x0000ffff; - sev_common->reset_data_valid = true; - - CPU_FOREACH(cpu) { - sev_es_set_reset_vector(cpu); - } + sev_es_set_ap_context(addr); } return 0; @@ -2037,6 +2492,237 @@ static void sev_common_set_kernel_hashes(Object *obj, bool value, Error **errp) SEV_COMMON(obj)->kernel_hashes = value; } +static bool cgs_check_support(ConfidentialGuestPlatformType platform, + uint16_t platform_version, uint8_t highest_vtl, + uint64_t shared_gpa_boundary) +{ + return (((platform == CGS_PLATFORM_SEV_SNP) && sev_snp_enabled()) || + ((platform == CGS_PLATFORM_SEV_ES) && sev_es_enabled()) || + ((platform == CGS_PLATFORM_SEV) && sev_enabled())); +} + +static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, uint64_t len, + ConfidentialGuestPageType memory_type, + uint16_t cpu_index, Error **errp) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(sev_common); + + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + if ((cpu_index == 0) && (memory_type == CGS_PAGE_TYPE_VMSA)) { + const struct sev_es_save_area *sa = + (const struct sev_es_save_area *)ptr; + if (len < sizeof(*sa)) { + error_setg(errp, "%s: invalid VMSA length encountered", + __func__); + return -1; + } + if (check_sev_features(sev_common, sa->sev_features, errp) < 0) { + return -1; + } + sev_common->sev_features = sa->sev_features; + } + return 0; + } + + if (!sev_enabled()) { + error_setg(errp, "%s: attempt to configure guest memory, but SEV " + "is not enabled", __func__); + return -1; + } + + switch (memory_type) { + case CGS_PAGE_TYPE_NORMAL: + case CGS_PAGE_TYPE_ZERO: + return klass->launch_update_data(sev_common, gpa, ptr, len, errp); + + case CGS_PAGE_TYPE_VMSA: + if (!sev_es_enabled()) { + error_setg(errp, + "%s: attempt to configure initial VMSA, but SEV-ES " + "is not supported", + __func__); + return -1; + } + if (check_vmsa_supported(sev_common, gpa, + (const struct sev_es_save_area *)ptr, + errp) < 0) { + return -1; + } + return sev_set_cpu_context(cpu_index, ptr, len, gpa, errp); + + case CGS_PAGE_TYPE_UNMEASURED: + if (sev_snp_enabled()) { + return snp_launch_update_data( + gpa, ptr, len, KVM_SEV_SNP_PAGE_TYPE_UNMEASURED, errp); + } + /* No action required if not SEV-SNP */ + return 0; + + case CGS_PAGE_TYPE_SECRETS: + if (!sev_snp_enabled()) { + error_setg(errp, + "%s: attempt to configure secrets page, but SEV-SNP " + "is not supported", + __func__); + return -1; + } + return snp_launch_update_data(gpa, ptr, len, + KVM_SEV_SNP_PAGE_TYPE_SECRETS, errp); + + case CGS_PAGE_TYPE_REQUIRED_MEMORY: + if (kvm_convert_memory(gpa, len, true) < 0) { + error_setg( + errp, + "%s: failed to configure required memory. gpa: %lX, type: %d", + __func__, gpa, memory_type); + return -1; + } + return 0; + + case CGS_PAGE_TYPE_CPUID: + if (!sev_snp_enabled()) { + error_setg(errp, + "%s: attempt to configure CPUID page, but SEV-SNP " + "is not supported", + __func__); + return -1; + } + return snp_launch_update_cpuid(gpa, ptr, len, errp); + } + error_setg(errp, "%s: failed to update guest. gpa: %lX, type: %d", __func__, + gpa, memory_type); + return -1; +} + +static int cgs_get_mem_map_entry(int index, + ConfidentialGuestMemoryMapEntry *entry, + Error **errp) +{ + struct e820_entry *table; + int num_entries; + + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + return 1; + } + + num_entries = e820_get_table(&table); + if ((index < 0) || (index >= num_entries)) { + return 1; + } + entry->gpa = table[index].address; + entry->size = table[index].length; + switch (table[index].type) { + case E820_RAM: + entry->type = CGS_MEM_RAM; + break; + case E820_RESERVED: + entry->type = CGS_MEM_RESERVED; + break; + case E820_ACPI: + entry->type = CGS_MEM_ACPI; + break; + case E820_NVS: + entry->type = CGS_MEM_NVS; + break; + case E820_UNUSABLE: + entry->type = CGS_MEM_UNUSABLE; + break; + } + return 0; +} + +static int cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type, + uint64_t policy, void *policy_data1, + uint32_t policy_data1_size, void *policy_data2, + uint32_t policy_data2_size, Error **errp) +{ + SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs); + if (sev_common->state == SEV_STATE_UNINIT) { + /* Pre-processing of IGVM file called from sev_common_kvm_init() */ + return 0; + } + + if (policy_type != GUEST_POLICY_SEV) { + error_setg(errp, "%s: Invalid guest policy type provided for SEV: %d", + __func__, policy_type); + return -1; + } + /* + * SEV-SNP handles policy differently. The policy flags are defined in + * kvm_start_conf.policy and an ID block and ID auth can be provided. + */ + if (sev_snp_enabled()) { + SevSnpGuestState *sev_snp_guest = + SEV_SNP_GUEST(MACHINE(qdev_get_machine())->cgs); + struct kvm_sev_snp_launch_finish *finish = + &sev_snp_guest->kvm_finish_conf; + + /* + * The policy consists of flags in 'policy' and optionally an ID block + * and ID auth in policy_data1 and policy_data2 respectively. The ID + * block and auth are optional so clear any previous ID block and auth + * and set them if provided, but always set the policy flags. + */ + g_free(sev_snp_guest->id_block); + g_free((guchar *)finish->id_block_uaddr); + g_free(sev_snp_guest->id_auth); + g_free((guchar *)finish->id_auth_uaddr); + sev_snp_guest->id_block = NULL; + finish->id_block_uaddr = 0; + sev_snp_guest->id_auth = NULL; + finish->id_auth_uaddr = 0; + + if (policy_data1_size > 0) { + struct sev_snp_id_authentication *id_auth = + (struct sev_snp_id_authentication *)policy_data2; + + if (policy_data1_size != KVM_SEV_SNP_ID_BLOCK_SIZE) { + error_setg(errp, "%s: Invalid SEV-SNP ID block: incorrect size", + __func__); + return -1; + } + if (policy_data2_size != KVM_SEV_SNP_ID_AUTH_SIZE) { + error_setg(errp, + "%s: Invalid SEV-SNP ID auth block: incorrect size", + __func__); + return -1; + } + assert(policy_data1 != NULL); + assert(policy_data2 != NULL); + + finish->id_block_uaddr = + (__u64)g_memdup2(policy_data1, KVM_SEV_SNP_ID_BLOCK_SIZE); + finish->id_auth_uaddr = + (__u64)g_memdup2(policy_data2, KVM_SEV_SNP_ID_AUTH_SIZE); + + /* + * Check if an author key has been provided and use that to flag + * whether the author key is enabled. The first of the author key + * must be non-zero to indicate the key type, which will currently + * always be 2. + */ + sev_snp_guest->kvm_finish_conf.auth_key_en = + id_auth->author_key[0] ? 1 : 0; + finish->id_block_en = 1; + } + sev_snp_guest->kvm_start_conf.policy = policy; + } else { + SevGuestState *sev_guest = SEV_GUEST(MACHINE(qdev_get_machine())->cgs); + /* Only the policy flags are supported for SEV and SEV-ES */ + if ((policy_data1_size > 0) || (policy_data2_size > 0) || !sev_guest) { + error_setg(errp, "%s: An ID block/ID auth block has been provided " + "but SEV-SNP is not enabled", __func__); + return -1; + } + sev_guest->policy = policy; + } + return 0; +} + static void sev_common_class_init(ObjectClass *oc, const void *data) { @@ -2060,6 +2746,8 @@ static void sev_common_instance_init(Object *obj) { SevCommonState *sev_common = SEV_COMMON(obj); + ConfidentialGuestSupportClass *cgs = + CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(obj); sev_common->kvm_type = -1; @@ -2070,6 +2758,12 @@ sev_common_instance_init(Object *obj) object_property_add_uint32_ptr(obj, "reduced-phys-bits", &sev_common->reduced_phys_bits, OBJ_PROP_FLAG_READWRITE); + cgs->check_support = cgs_check_support; + cgs->set_guest_state = cgs_set_guest_state; + cgs->get_mem_map_entry = cgs_get_mem_map_entry; + cgs->set_guest_policy = cgs_set_guest_policy; + + QTAILQ_INIT(&sev_common->launch_vmsa); } /* sev guest info common to sev/sev-es/sev-snp */ diff --git a/target/i386/sev.h b/target/i386/sev.h index 373669e..9db1a80 100644 --- a/target/i386/sev.h +++ b/target/i386/sev.h @@ -44,6 +44,8 @@ bool sev_snp_enabled(void); #define SEV_SNP_POLICY_SMT 0x10000 #define SEV_SNP_POLICY_DBG 0x80000 +#define SVM_SEV_FEAT_SNP_ACTIVE 1 + typedef struct SevKernelLoaderContext { char *setup_data; size_t setup_size; @@ -55,6 +57,128 @@ typedef struct SevKernelLoaderContext { size_t cmdline_size; } SevKernelLoaderContext; +/* Save area definition for SEV-ES and SEV-SNP guests */ +struct QEMU_PACKED sev_es_save_area { + struct vmcb_seg es; + struct vmcb_seg cs; + struct vmcb_seg ss; + struct vmcb_seg ds; + struct vmcb_seg fs; + struct vmcb_seg gs; + struct vmcb_seg gdtr; + struct vmcb_seg ldtr; + struct vmcb_seg idtr; + struct vmcb_seg tr; + uint64_t vmpl0_ssp; + uint64_t vmpl1_ssp; + uint64_t vmpl2_ssp; + uint64_t vmpl3_ssp; + uint64_t u_cet; + uint8_t reserved_0xc8[2]; + uint8_t vmpl; + uint8_t cpl; + uint8_t reserved_0xcc[4]; + uint64_t efer; + uint8_t reserved_0xd8[104]; + uint64_t xss; + uint64_t cr4; + uint64_t cr3; + uint64_t cr0; + uint64_t dr7; + uint64_t dr6; + uint64_t rflags; + uint64_t rip; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr0_addr_mask; + uint64_t dr1_addr_mask; + uint64_t dr2_addr_mask; + uint64_t dr3_addr_mask; + uint8_t reserved_0x1c0[24]; + uint64_t rsp; + uint64_t s_cet; + uint64_t ssp; + uint64_t isst_addr; + uint64_t rax; + uint64_t star; + uint64_t lstar; + uint64_t cstar; + uint64_t sfmask; + uint64_t kernel_gs_base; + uint64_t sysenter_cs; + uint64_t sysenter_esp; + uint64_t sysenter_eip; + uint64_t cr2; + uint8_t reserved_0x248[32]; + uint64_t g_pat; + uint64_t dbgctl; + uint64_t br_from; + uint64_t br_to; + uint64_t last_excp_from; + uint64_t last_excp_to; + uint8_t reserved_0x298[80]; + uint32_t pkru; + uint32_t tsc_aux; + uint8_t reserved_0x2f0[24]; + uint64_t rcx; + uint64_t rdx; + uint64_t rbx; + uint64_t reserved_0x320; /* rsp already available at 0x01d8 */ + uint64_t rbp; + uint64_t rsi; + uint64_t rdi; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint8_t reserved_0x380[16]; + uint64_t guest_exit_info_1; + uint64_t guest_exit_info_2; + uint64_t guest_exit_int_info; + uint64_t guest_nrip; + uint64_t sev_features; + uint64_t vintr_ctrl; + uint64_t guest_exit_code; + uint64_t virtual_tom; + uint64_t tlb_id; + uint64_t pcpu_id; + uint64_t event_inj; + uint64_t xcr0; + uint8_t reserved_0x3f0[16]; + + /* Floating point area */ + uint64_t x87_dp; + uint32_t mxcsr; + uint16_t x87_ftw; + uint16_t x87_fsw; + uint16_t x87_fcw; + uint16_t x87_fop; + uint16_t x87_ds; + uint16_t x87_cs; + uint64_t x87_rip; + uint8_t fpreg_x87[80]; + uint8_t fpreg_xmm[256]; + uint8_t fpreg_ymm[256]; +}; + +struct QEMU_PACKED sev_snp_id_authentication { + uint32_t id_key_alg; + uint32_t auth_key_algo; + uint8_t reserved[56]; + uint8_t id_block_sig[512]; + uint8_t id_key[1028]; + uint8_t reserved2[60]; + uint8_t id_key_sig[512]; + uint8_t author_key[1028]; + uint8_t reserved3[892]; +}; + bool sev_add_kernel_loader_hashes(SevKernelLoaderContext *ctx, Error **errp); int sev_encrypt_flash(hwaddr gpa, uint8_t *ptr, uint64_t len, Error **errp); diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 55216e0..853b1c8 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2722,14 +2722,14 @@ static void disas_insn(DisasContext *s, CPUState *cpu) if (decode.e.check & X86_CHECK_i64) { goto illegal_op; } - if ((decode.e.check & X86_CHECK_i64_amd) && env->cpuid_vendor1 != CPUID_VENDOR_INTEL_1) { + if ((decode.e.check & X86_CHECK_i64_amd) && !IS_INTEL_CPU(env)) { goto illegal_op; } } else { if (decode.e.check & X86_CHECK_o64) { goto illegal_op; } - if ((decode.e.check & X86_CHECK_o64_intel) && env->cpuid_vendor1 == CPUID_VENDOR_INTEL_1) { + if ((decode.e.check & X86_CHECK_o64_intel) && IS_INTEL_CPU(env)) { goto illegal_op; } } diff --git a/target/i386/tcg/system/excp_helper.c b/target/i386/tcg/system/excp_helper.c index c162621..50040f6 100644 --- a/target/i386/tcg/system/excp_helper.c +++ b/target/i386/tcg/system/excp_helper.c @@ -25,7 +25,6 @@ #include "exec/page-protection.h" #include "exec/target_page.h" #include "exec/tlb-flags.h" -#include "exec/tswap.h" #include "tcg/helper-tcg.h" typedef struct TranslateParams { diff --git a/target/i386/whpx/whpx-accel-ops.c b/target/i386/whpx/whpx-accel-ops.c index 5f4841c..da58805 100644 --- a/target/i386/whpx/whpx-accel-ops.c +++ b/target/i386/whpx/whpx-accel-ops.c @@ -11,7 +11,7 @@ #include "qemu/osdep.h" #include "system/kvm_int.h" #include "qemu/main-loop.h" -#include "system/accel-ops.h" +#include "accel/accel-cpu-ops.h" #include "system/cpus.h" #include "qemu/guest-random.h" diff --git a/target/i386/whpx/whpx-all.c b/target/i386/whpx/whpx-all.c index faf56e1..b72dcff 100644 --- a/target/i386/whpx/whpx-all.c +++ b/target/i386/whpx/whpx-all.c @@ -14,6 +14,7 @@ #include "system/ioport.h" #include "gdbstub/helpers.h" #include "qemu/accel.h" +#include "accel/accel-ops.h" #include "system/whpx.h" #include "system/cpus.h" #include "system/runstate.h" @@ -26,6 +27,8 @@ #include "qapi/qapi-types-common.h" #include "qapi/qapi-visit-common.h" #include "migration/blocker.h" +#include "host-cpu.h" +#include "accel/accel-cpu-target.h" #include <winerror.h> #include "whpx-internal.h" @@ -2500,6 +2503,28 @@ static void whpx_set_kernel_irqchip(Object *obj, Visitor *v, } } +static void whpx_cpu_instance_init(CPUState *cs) +{ + X86CPU *cpu = X86_CPU(cs); + + host_cpu_instance_init(cpu); +} + +static void whpx_cpu_accel_class_init(ObjectClass *oc, const void *data) +{ + AccelCPUClass *acc = ACCEL_CPU_CLASS(oc); + + acc->cpu_instance_init = whpx_cpu_instance_init; +} + +static const TypeInfo whpx_cpu_accel_type = { + .name = ACCEL_CPU_NAME("whpx"), + + .parent = TYPE_ACCEL_CPU, + .class_init = whpx_cpu_accel_class_init, + .abstract = true, +}; + /* * Partition support */ @@ -2726,6 +2751,7 @@ static const TypeInfo whpx_accel_type = { static void whpx_type_init(void) { type_register_static(&whpx_accel_type); + type_register_static(&whpx_cpu_accel_type); } bool init_whp_dispatch(void) diff --git a/target/i386/xsave_helper.c b/target/i386/xsave_helper.c index 24ab7be..996e9f3 100644 --- a/target/i386/xsave_helper.c +++ b/target/i386/xsave_helper.c @@ -5,7 +5,6 @@ #include "qemu/osdep.h" #include "cpu.h" -#include "exec/tswap.h" void x86_cpu_xsave_all_areas(X86CPU *cpu, void *buf, uint32_t buflen) { diff --git a/target/loongarch/loongarch-qmp-cmds.c b/target/loongarch/loongarch-qmp-cmds.c index f5f1cd0..1d8cd32 100644 --- a/target/loongarch/loongarch-qmp-cmds.c +++ b/target/loongarch/loongarch-qmp-cmds.c @@ -7,6 +7,7 @@ */ #include "qemu/osdep.h" +#include "qemu/target-info.h" #include "qapi/error.h" #include "qapi/qapi-commands-machine.h" #include "cpu.h" @@ -32,7 +33,7 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) CpuDefinitionInfoList *cpu_list = NULL; GSList *list; - list = object_class_get_list(TYPE_LOONGARCH_CPU, false); + list = object_class_get_list(target_cpu_type(), false); g_slist_foreach(list, loongarch_cpu_add_definition, &cpu_list); g_slist_free(list); diff --git a/target/mips/cpu-defs.c.inc b/target/mips/cpu-defs.c.inc index 922fc39..d93b9d3 100644 --- a/target/mips/cpu-defs.c.inc +++ b/target/mips/cpu-defs.c.inc @@ -756,8 +756,9 @@ const mips_def_t mips_defs[] = (1 << CP0C3_RXI) | (1 << CP0C3_LPA) | (1 << CP0C3_VInt), .CP0_Config4 = MIPS_CONFIG4 | (1U << CP0C4_M) | (3 << CP0C4_IE) | (1 << CP0C4_AE) | (0xfc << CP0C4_KScrExist), - .CP0_Config5 = MIPS_CONFIG5 | (1 << CP0C5_XNP) | (1 << CP0C5_VP) | - (1 << CP0C5_LLB) | (1 << CP0C5_MRP) | (3 << CP0C5_GI), + .CP0_Config5 = MIPS_CONFIG5 | (1 << CP0C5_CRCP) | (1 << CP0C5_XNP) | + (1 << CP0C5_VP) | (1 << CP0C5_LLB) | (1 << CP0C5_MRP) | + (3 << CP0C5_GI), .CP0_Config5_rw_bitmask = (1 << CP0C5_MSAEn) | (1 << CP0C5_SBRI) | (1 << CP0C5_FRE) | (1 << CP0C5_UFE), .CP0_LLAddr_rw_bitmask = 0, @@ -796,8 +797,9 @@ const mips_def_t mips_defs[] = (1 << CP0C3_RXI) | (1 << CP0C3_LPA) | (1 << CP0C3_VInt), .CP0_Config4 = MIPS_CONFIG4 | (1U << CP0C4_M) | (3 << CP0C4_IE) | (1 << CP0C4_AE) | (0xfc << CP0C4_KScrExist), - .CP0_Config5 = MIPS_CONFIG5 | (1 << CP0C5_XNP) | (1 << CP0C5_VP) | - (1 << CP0C5_LLB) | (1 << CP0C5_MRP) | (3 << CP0C5_GI), + .CP0_Config5 = MIPS_CONFIG5 | (1 << CP0C5_CRCP) | (1 << CP0C5_XNP) | + (1 << CP0C5_VP) | (1 << CP0C5_LLB) | (1 << CP0C5_MRP) | + (3 << CP0C5_GI), .CP0_Config5_rw_bitmask = (1 << CP0C5_MSAEn) | (1 << CP0C5_SBRI) | (1 << CP0C5_FRE) | (1 << CP0C5_UFE), .CP0_LLAddr_rw_bitmask = 0, diff --git a/target/mips/helper.h b/target/mips/helper.h index 7e40041..b6cd53c 100644 --- a/target/mips/helper.h +++ b/target/mips/helper.h @@ -21,6 +21,8 @@ DEF_HELPER_FLAGS_1(bitswap, TCG_CALL_NO_RWG_SE, tl, tl) DEF_HELPER_FLAGS_1(dbitswap, TCG_CALL_NO_RWG_SE, tl, tl) #endif +DEF_HELPER_3(crc32, tl, tl, tl, i32) +DEF_HELPER_3(crc32c, tl, tl, tl, i32) DEF_HELPER_FLAGS_4(rotx, TCG_CALL_NO_RWG_SE, tl, tl, i32, i32, i32) /* microMIPS functions */ diff --git a/target/mips/meson.build b/target/mips/meson.build index 247979a..abf0ce3 100644 --- a/target/mips/meson.build +++ b/target/mips/meson.build @@ -7,6 +7,7 @@ mips_ss.add(files( 'gdbstub.c', 'msa.c', )) +mips_ss.add(zlib) if have_system subdir('system') diff --git a/target/mips/system/mips-qmp-cmds.c b/target/mips/system/mips-qmp-cmds.c index d98d662..b6a2874 100644 --- a/target/mips/system/mips-qmp-cmds.c +++ b/target/mips/system/mips-qmp-cmds.c @@ -7,6 +7,7 @@ */ #include "qemu/osdep.h" +#include "qemu/target-info.h" #include "qapi/error.h" #include "qapi/qapi-commands-machine.h" #include "cpu.h" @@ -40,7 +41,7 @@ CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp) CpuDefinitionInfoList *cpu_list = NULL; GSList *list; - list = object_class_get_list(TYPE_MIPS_CPU, false); + list = object_class_get_list(target_cpu_type(), false); g_slist_foreach(list, mips_cpu_add_definition, &cpu_list); g_slist_free(list); diff --git a/target/mips/tcg/micromips_translate.c.inc b/target/mips/tcg/micromips_translate.c.inc index c479bec..8fda7c8 100644 --- a/target/mips/tcg/micromips_translate.c.inc +++ b/target/mips/tcg/micromips_translate.c.inc @@ -1795,7 +1795,7 @@ static void decode_micromips32_opc(CPUMIPSState *env, DisasContext *ctx) return; case LSA: check_insn(ctx, ISA_MIPS_R6); - gen_lsa(ctx, rd, rt, rs, extract32(ctx->opcode, 9, 2)); + gen_lsa(ctx, rd, rt, rs, extract32(ctx->opcode, 9, 2) + 1); break; case ALIGN: check_insn(ctx, ISA_MIPS_R6); diff --git a/target/mips/tcg/msa_translate.c b/target/mips/tcg/msa_translate.c index 75cf80a..82b1499 100644 --- a/target/mips/tcg/msa_translate.c +++ b/target/mips/tcg/msa_translate.c @@ -780,7 +780,7 @@ TRANS_DF_iv(ST, trans_msa_ldst, gen_helper_msa_st); static bool trans_LSA(DisasContext *ctx, arg_r *a) { - return gen_lsa(ctx, a->rd, a->rt, a->rs, a->sa); + return gen_lsa(ctx, a->rd, a->rt, a->rs, a->sa + 1); } static bool trans_DLSA(DisasContext *ctx, arg_r *a) @@ -788,5 +788,5 @@ static bool trans_DLSA(DisasContext *ctx, arg_r *a) if (TARGET_LONG_BITS != 64) { return false; } - return gen_dlsa(ctx, a->rd, a->rt, a->rs, a->sa); + return gen_dlsa(ctx, a->rd, a->rt, a->rs, a->sa + 1); } diff --git a/target/mips/tcg/nanomips_translate.c.inc b/target/mips/tcg/nanomips_translate.c.inc index 1e27414..9d4e0be 100644 --- a/target/mips/tcg/nanomips_translate.c.inc +++ b/target/mips/tcg/nanomips_translate.c.inc @@ -3626,12 +3626,7 @@ static int decode_nanomips_32_48_opc(CPUMIPSState *env, DisasContext *ctx) gen_p_lsx(ctx, rd, rs, rt); break; case NM_LSA: - /* - * In nanoMIPS, the shift field directly encodes the shift - * amount, meaning that the supported shift values are in - * the range 0 to 3 (instead of 1 to 4 in MIPSR6). - */ - gen_lsa(ctx, rd, rt, rs, extract32(ctx->opcode, 9, 2) - 1); + gen_lsa(ctx, rd, rt, rs, extract32(ctx->opcode, 9, 2)); break; case NM_EXTW: gen_ext(ctx, 32, rd, rs, rt, extract32(ctx->opcode, 6, 5)); diff --git a/target/mips/tcg/octeon.decode b/target/mips/tcg/octeon.decode index 0c787cb..102a058 100644 --- a/target/mips/tcg/octeon.decode +++ b/target/mips/tcg/octeon.decode @@ -1,6 +1,7 @@ # Octeon Architecture Module instruction set # # Copyright (C) 2022 Pavel Dovgalyuk +# Copyright (C) 2024 Philippe Mathieu-Daudé # # SPDX-License-Identifier: LGPL-2.1-or-later # @@ -39,3 +40,10 @@ CINS 011100 ..... ..... ..... ..... 11001 . @bitfield POP 011100 rs:5 00000 rd:5 00000 10110 dw:1 SEQNE 011100 rs:5 rt:5 rd:5 00000 10101 ne:1 SEQNEI 011100 rs:5 rt:5 imm:s10 10111 ne:1 + +&lx base index rd +@lx ...... base:5 index:5 rd:5 ...... ..... &lx +LWX 011111 ..... ..... ..... 00000 001010 @lx +LHX 011111 ..... ..... ..... 00100 001010 @lx +LBUX 011111 ..... ..... ..... 00110 001010 @lx +LDX 011111 ..... ..... ..... 01000 001010 @lx diff --git a/target/mips/tcg/octeon_translate.c b/target/mips/tcg/octeon_translate.c index d9eb437..b2eca29 100644 --- a/target/mips/tcg/octeon_translate.c +++ b/target/mips/tcg/octeon_translate.c @@ -174,3 +174,15 @@ static bool trans_SEQNEI(DisasContext *ctx, arg_SEQNEI *a) } return true; } + +static bool trans_lx(DisasContext *ctx, arg_lx *a, MemOp mop) +{ + gen_lx(ctx, a->rd, a->base, a->index, mop); + + return true; +} + +TRANS(LBUX, trans_lx, MO_UB); +TRANS(LHX, trans_lx, MO_SW); +TRANS(LWX, trans_lx, MO_SL); +TRANS(LDX, trans_lx, MO_UQ); diff --git a/target/mips/tcg/op_helper.c b/target/mips/tcg/op_helper.c index b906d10..4502ae2 100644 --- a/target/mips/tcg/op_helper.c +++ b/target/mips/tcg/op_helper.c @@ -24,6 +24,8 @@ #include "exec/helper-proto.h" #include "exec/memop.h" #include "fpu_helper.h" +#include "qemu/crc32c.h" +#include <zlib.h> static inline target_ulong bitswap(target_ulong v) { @@ -142,6 +144,30 @@ target_ulong helper_rotx(target_ulong rs, uint32_t shift, uint32_t shiftx, return (int64_t)(int32_t)(uint32_t)tmp5; } +/* these crc32 functions are based on target/loongarch/tcg/op_helper.c */ +target_ulong helper_crc32(target_ulong val, target_ulong m, uint32_t sz) +{ + uint8_t buf[8]; + target_ulong mask = ((sz * 8) == 64) ? + (target_ulong) -1ULL : + ((1ULL << (sz * 8)) - 1); + + m &= mask; + stq_le_p(buf, m); + return (int32_t) (crc32(val ^ 0xffffffff, buf, sz) ^ 0xffffffff); +} + +target_ulong helper_crc32c(target_ulong val, target_ulong m, uint32_t sz) +{ + uint8_t buf[8]; + target_ulong mask = ((sz * 8) == 64) ? + (target_ulong) -1ULL : + ((1ULL << (sz * 8)) - 1); + m &= mask; + stq_le_p(buf, m); + return (int32_t) (crc32c(val, buf, sz) ^ 0xffffffff); +} + void helper_fork(target_ulong arg1, target_ulong arg2) { /* diff --git a/target/mips/tcg/rel6.decode b/target/mips/tcg/rel6.decode index d6989cf..7fbcb10 100644 --- a/target/mips/tcg/rel6.decode +++ b/target/mips/tcg/rel6.decode @@ -16,11 +16,16 @@ &r rs rt rd sa +&special3_crc rs rt c sz + @lsa ...... rs:5 rt:5 rd:5 ... sa:2 ...... &r +@crc32 ...... rs:5 rt:5 ..... c:3 sz:2 ...... &special3_crc LSA 000000 ..... ..... ..... 000 .. 000101 @lsa DLSA 000000 ..... ..... ..... 000 .. 010101 @lsa +CRC32 011111 ..... ..... 00000 ... .. 001111 @crc32 + REMOVED 010011 ----- ----- ----- ----- ------ # COP1X (COP3) REMOVED 011100 ----- ----- ----- ----- ------ # SPECIAL2 diff --git a/target/mips/tcg/rel6_translate.c b/target/mips/tcg/rel6_translate.c index 59f237b..4c05662 100644 --- a/target/mips/tcg/rel6_translate.c +++ b/target/mips/tcg/rel6_translate.c @@ -23,7 +23,7 @@ bool trans_REMOVED(DisasContext *ctx, arg_REMOVED *a) static bool trans_LSA(DisasContext *ctx, arg_r *a) { - return gen_lsa(ctx, a->rd, a->rt, a->rs, a->sa); + return gen_lsa(ctx, a->rd, a->rt, a->rs, a->sa + 1); } static bool trans_DLSA(DisasContext *ctx, arg_r *a) @@ -31,5 +31,17 @@ static bool trans_DLSA(DisasContext *ctx, arg_r *a) if (TARGET_LONG_BITS != 64) { return false; } - return gen_dlsa(ctx, a->rd, a->rt, a->rs, a->sa); + return gen_dlsa(ctx, a->rd, a->rt, a->rs, a->sa + 1); +} + +static bool trans_CRC32(DisasContext *ctx, arg_special3_crc *a) +{ + if (unlikely(!ctx->crcp) + || unlikely((a->sz == 3) && (!(ctx->hflags & MIPS_HFLAG_64))) + || unlikely((a->c >= 2))) { + gen_reserved_instruction(ctx); + return true; + } + gen_crc32(ctx, a->rt, a->rs, a->rt, a->sz, a->c); + return true; } diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c index 8658315..d91d6ef 100644 --- a/target/mips/tcg/translate.c +++ b/target/mips/tcg/translate.c @@ -1957,6 +1957,17 @@ void gen_base_offset_addr(DisasContext *ctx, TCGv addr, int base, int offset) } } +void gen_base_index_addr(DisasContext *ctx, TCGv addr, int base, int index) +{ + if (base == 0) { + gen_load_gpr(addr, index); + } else if (index == 0) { + gen_load_gpr(addr, base); + } else { + gen_op_addr_add(ctx, addr, cpu_gpr[base], cpu_gpr[index]); + } +} + static target_ulong pc_relative_pc(DisasContext *ctx) { target_ulong pc = ctx->base.pc_next; @@ -2025,6 +2036,15 @@ static void gen_lxr(DisasContext *ctx, TCGv reg, TCGv addr, tcg_gen_or_tl(reg, t0, t1); } +void gen_lx(DisasContext *ctx, int rd, int base, int index, MemOp mop) +{ + TCGv t0 = tcg_temp_new(); + + gen_base_index_addr(ctx, t0, base, index); + tcg_gen_qemu_ld_tl(t0, t0, ctx->mem_idx, mo_endian(ctx) | mop); + gen_store_gpr(t0, rd); +} + /* Load */ static void gen_ld(DisasContext *ctx, uint32_t opc, int rt, int base, int offset) @@ -10546,13 +10566,7 @@ static void gen_flt3_ldst(DisasContext *ctx, uint32_t opc, { TCGv t0 = tcg_temp_new(); - if (base == 0) { - gen_load_gpr(t0, index); - } else if (index == 0) { - gen_load_gpr(t0, base); - } else { - gen_op_addr_add(ctx, t0, cpu_gpr[base], cpu_gpr[index]); - } + gen_base_index_addr(ctx, t0, base, index); /* * Don't do NOP if destination is zero: we must perform the actual * memory access. @@ -11323,47 +11337,6 @@ enum { /* MIPSDSP functions. */ -/* Indexed load is not for DSP only */ -static void gen_mips_lx(DisasContext *ctx, uint32_t opc, - int rd, int base, int offset) -{ - TCGv t0; - - if (!(ctx->insn_flags & INSN_OCTEON)) { - check_dsp(ctx); - } - t0 = tcg_temp_new(); - - if (base == 0) { - gen_load_gpr(t0, offset); - } else if (offset == 0) { - gen_load_gpr(t0, base); - } else { - gen_op_addr_add(ctx, t0, cpu_gpr[base], cpu_gpr[offset]); - } - - switch (opc) { - case OPC_LBUX: - tcg_gen_qemu_ld_tl(t0, t0, ctx->mem_idx, MO_UB); - gen_store_gpr(t0, rd); - break; - case OPC_LHX: - tcg_gen_qemu_ld_tl(t0, t0, ctx->mem_idx, mo_endian(ctx) | MO_SW); - gen_store_gpr(t0, rd); - break; - case OPC_LWX: - tcg_gen_qemu_ld_tl(t0, t0, ctx->mem_idx, mo_endian(ctx) | MO_SL); - gen_store_gpr(t0, rd); - break; -#if defined(TARGET_MIPS64) - case OPC_LDX: - tcg_gen_qemu_ld_tl(t0, t0, ctx->mem_idx, mo_endian(ctx) | MO_UQ); - gen_store_gpr(t0, rd); - break; -#endif - } -} - static void gen_mipsdsp_arith(DisasContext *ctx, uint32_t op1, uint32_t op2, int ret, int v1, int v2) { @@ -13449,6 +13422,29 @@ static void decode_opc_special2_legacy(CPUMIPSState *env, DisasContext *ctx) } } +void gen_crc32(DisasContext *ctx, int rd, int rs, int rt, int sz, + int crc32c) +{ + TCGv t0; + TCGv t1; + TCGv_i32 tsz = tcg_constant_i32(1 << sz); + if (rd == 0) { + /* Treat as NOP. */ + return; + } + t0 = tcg_temp_new(); + t1 = tcg_temp_new(); + + gen_load_gpr(t0, rt); + gen_load_gpr(t1, rs); + + if (crc32c) { + gen_helper_crc32c(cpu_gpr[rd], t0, t1, tsz); + } else { + gen_helper_crc32(cpu_gpr[rd], t0, t1, tsz); + } +} + static void decode_opc_special3_r6(CPUMIPSState *env, DisasContext *ctx) { int rs, rt, rd, sa; @@ -13611,15 +13607,22 @@ static void decode_opc_special3_legacy(CPUMIPSState *env, DisasContext *ctx) } break; case OPC_LX_DSP: + check_dsp(ctx); op2 = MASK_LX(ctx->opcode); switch (op2) { #if defined(TARGET_MIPS64) case OPC_LDX: + gen_lx(ctx, rd, rs, rt, MO_UQ); + break; #endif case OPC_LBUX: + gen_lx(ctx, rd, rs, rt, MO_UB); + break; case OPC_LHX: + gen_lx(ctx, rd, rs, rt, MO_SW); + break; case OPC_LWX: - gen_mips_lx(ctx, op2, rd, rs, rt); + gen_lx(ctx, rd, rs, rt, MO_SL); break; default: /* Invalid */ MIPS_INVAL("MASK LX"); @@ -15095,6 +15098,7 @@ static void mips_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) ctx->abs2008 = (env->active_fpu.fcr31 >> FCR31_ABS2008) & 1; ctx->mi = (env->CP0_Config5 >> CP0C5_MI) & 1; ctx->gi = (env->CP0_Config5 >> CP0C5_GI) & 3; + ctx->crcp = (env->CP0_Config5 >> CP0C5_CRCP) & 1; restore_cpu_state(env, ctx); #ifdef CONFIG_USER_ONLY ctx->mem_idx = MIPS_HFLAG_UM; diff --git a/target/mips/tcg/translate.h b/target/mips/tcg/translate.h index 1bf153d..89dde1e 100644 --- a/target/mips/tcg/translate.h +++ b/target/mips/tcg/translate.h @@ -51,6 +51,7 @@ typedef struct DisasContext { bool abs2008; bool mi; int gi; + bool crcp; } DisasContext; #define DISAS_STOP DISAS_TARGET_0 @@ -153,6 +154,7 @@ void check_cp1_registers(DisasContext *ctx, int regs); void check_cop1x(DisasContext *ctx); void gen_base_offset_addr(DisasContext *ctx, TCGv addr, int base, int offset); +void gen_base_index_addr(DisasContext *ctx, TCGv addr, int base, int index); void gen_move_low32(TCGv ret, TCGv_i64 arg); void gen_move_high32(TCGv ret, TCGv_i64 arg); void gen_load_gpr(TCGv t, int reg); @@ -167,6 +169,7 @@ void gen_store_fpr32(DisasContext *ctx, TCGv_i32 t, int reg); void gen_store_fpr64(DisasContext *ctx, TCGv_i64 t, int reg); int get_fp_bit(int cc); +void gen_lx(DisasContext *ctx, int rd, int base, int index, MemOp mop); void gen_ldxs(DisasContext *ctx, int base, int index, int rd); void gen_align(DisasContext *ctx, int wordsz, int rd, int rs, int rt, int bp); void gen_addiupc(DisasContext *ctx, int rx, int imm, @@ -181,6 +184,7 @@ bool gen_lsa(DisasContext *ctx, int rd, int rt, int rs, int sa); bool gen_dlsa(DisasContext *ctx, int rd, int rt, int rs, int sa); void gen_rdhwr(DisasContext *ctx, int rt, int rd, int sel); +void gen_crc32(DisasContext *ctx, int rd, int rs, int rt, int sz, int crc32c); extern TCGv cpu_gpr[32], cpu_PC; #if defined(TARGET_MIPS64) diff --git a/target/mips/tcg/translate_addr_const.c b/target/mips/tcg/translate_addr_const.c index 6f4b39f..1d140e9 100644 --- a/target/mips/tcg/translate_addr_const.c +++ b/target/mips/tcg/translate_addr_const.c @@ -26,7 +26,7 @@ bool gen_lsa(DisasContext *ctx, int rd, int rt, int rs, int sa) t1 = tcg_temp_new(); gen_load_gpr(t0, rs); gen_load_gpr(t1, rt); - tcg_gen_shli_tl(t0, t0, sa + 1); + tcg_gen_shli_tl(t0, t0, sa); tcg_gen_add_tl(cpu_gpr[rd], t0, t1); tcg_gen_ext32s_tl(cpu_gpr[rd], cpu_gpr[rd]); return true; @@ -47,7 +47,7 @@ bool gen_dlsa(DisasContext *ctx, int rd, int rt, int rs, int sa) t1 = tcg_temp_new(); gen_load_gpr(t0, rs); gen_load_gpr(t1, rt); - tcg_gen_shli_tl(t0, t0, sa + 1); + tcg_gen_shli_tl(t0, t0, sa); tcg_gen_add_tl(cpu_gpr[rd], t0, t1); return true; } diff --git a/target/ppc/mmu-hash64.h b/target/ppc/mmu-hash64.h index b8fb12a..ae8d4b3 100644 --- a/target/ppc/mmu-hash64.h +++ b/target/ppc/mmu-hash64.h @@ -1,8 +1,6 @@ #ifndef MMU_HASH64_H #define MMU_HASH64_H -#include "exec/tswap.h" - #ifndef CONFIG_USER_ONLY #ifdef TARGET_PPC64 diff --git a/target/riscv/vector_helper.c b/target/riscv/vector_helper.c index b41c29d..7c67d67 100644 --- a/target/riscv/vector_helper.c +++ b/target/riscv/vector_helper.c @@ -27,7 +27,6 @@ #include "exec/helper-proto.h" #include "exec/tlb-flags.h" #include "exec/target_page.h" -#include "exec/tswap.h" #include "fpu/softfloat.h" #include "tcg/tcg-gvec-desc.h" #include "internals.h" diff --git a/tests/data/acpi/aarch64/virt/DSDT b/tests/data/acpi/aarch64/virt/DSDT Binary files differindex 36d3e5d..18d97e8 100644 --- a/tests/data/acpi/aarch64/virt/DSDT +++ b/tests/data/acpi/aarch64/virt/DSDT diff --git a/tests/data/acpi/aarch64/virt/DSDT.acpihmatvirt b/tests/data/acpi/aarch64/virt/DSDT.acpihmatvirt Binary files differindex e6154d0..2cef095 100644 --- a/tests/data/acpi/aarch64/virt/DSDT.acpihmatvirt +++ b/tests/data/acpi/aarch64/virt/DSDT.acpihmatvirt diff --git a/tests/data/acpi/aarch64/virt/DSDT.acpipcihp b/tests/data/acpi/aarch64/virt/DSDT.acpipcihp Binary files differnew file mode 100644 index 0000000..8d55a87 --- /dev/null +++ b/tests/data/acpi/aarch64/virt/DSDT.acpipcihp diff --git a/tests/data/acpi/aarch64/virt/DSDT.hpoffacpiindex b/tests/data/acpi/aarch64/virt/DSDT.hpoffacpiindex Binary files differnew file mode 100644 index 0000000..970d43f --- /dev/null +++ b/tests/data/acpi/aarch64/virt/DSDT.hpoffacpiindex diff --git a/tests/data/acpi/aarch64/virt/DSDT.memhp b/tests/data/acpi/aarch64/virt/DSDT.memhp Binary files differindex 33f011d..372ca3d 100644 --- a/tests/data/acpi/aarch64/virt/DSDT.memhp +++ b/tests/data/acpi/aarch64/virt/DSDT.memhp diff --git a/tests/data/acpi/aarch64/virt/DSDT.pxb b/tests/data/acpi/aarch64/virt/DSDT.pxb Binary files differindex c0fdc6e..c277988 100644 --- a/tests/data/acpi/aarch64/virt/DSDT.pxb +++ b/tests/data/acpi/aarch64/virt/DSDT.pxb diff --git a/tests/data/acpi/aarch64/virt/DSDT.topology b/tests/data/acpi/aarch64/virt/DSDT.topology Binary files differindex 029d03e..ebbeedc 100644 --- a/tests/data/acpi/aarch64/virt/DSDT.topology +++ b/tests/data/acpi/aarch64/virt/DSDT.topology diff --git a/tests/data/acpi/aarch64/virt/DSDT.viot b/tests/data/acpi/aarch64/virt/DSDT.viot Binary files differnew file mode 100644 index 0000000..b897d66 --- /dev/null +++ b/tests/data/acpi/aarch64/virt/DSDT.viot diff --git a/tests/data/acpi/aarch64/virt/PPTT b/tests/data/acpi/aarch64/virt/PPTT Binary files differindex 7a1258e..15598a9 100644 --- a/tests/data/acpi/aarch64/virt/PPTT +++ b/tests/data/acpi/aarch64/virt/PPTT diff --git a/tests/data/acpi/aarch64/virt/PPTT.acpihmatvirt b/tests/data/acpi/aarch64/virt/PPTT.acpihmatvirt Binary files differindex 4eef303..7b613dd 100644 --- a/tests/data/acpi/aarch64/virt/PPTT.acpihmatvirt +++ b/tests/data/acpi/aarch64/virt/PPTT.acpihmatvirt diff --git a/tests/data/acpi/aarch64/virt/PPTT.topology b/tests/data/acpi/aarch64/virt/PPTT.topology Binary files differindex 3fbcae5..6b864f0 100644 --- a/tests/data/acpi/aarch64/virt/PPTT.topology +++ b/tests/data/acpi/aarch64/virt/PPTT.topology diff --git a/tests/data/acpi/loongarch64/virt/APIC b/tests/data/acpi/loongarch64/virt/APIC Binary files differnew file mode 100644 index 0000000..3477789 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/APIC diff --git a/tests/data/acpi/loongarch64/virt/APIC.topology b/tests/data/acpi/loongarch64/virt/APIC.topology Binary files differnew file mode 100644 index 0000000..da0089d --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/APIC.topology diff --git a/tests/data/acpi/loongarch64/virt/DSDT b/tests/data/acpi/loongarch64/virt/DSDT Binary files differnew file mode 100644 index 0000000..b31841a --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/DSDT diff --git a/tests/data/acpi/loongarch64/virt/DSDT.memhp b/tests/data/acpi/loongarch64/virt/DSDT.memhp Binary files differnew file mode 100644 index 0000000..e291200 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/DSDT.memhp diff --git a/tests/data/acpi/loongarch64/virt/DSDT.numamem b/tests/data/acpi/loongarch64/virt/DSDT.numamem Binary files differnew file mode 100644 index 0000000..07923ac --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/DSDT.numamem diff --git a/tests/data/acpi/loongarch64/virt/DSDT.topology b/tests/data/acpi/loongarch64/virt/DSDT.topology Binary files differnew file mode 100644 index 0000000..6dfbb49 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/DSDT.topology diff --git a/tests/data/acpi/loongarch64/virt/FACP b/tests/data/acpi/loongarch64/virt/FACP Binary files differnew file mode 100644 index 0000000..04d8d4c --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/FACP diff --git a/tests/data/acpi/loongarch64/virt/MCFG b/tests/data/acpi/loongarch64/virt/MCFG Binary files differnew file mode 100644 index 0000000..5f93b05 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/MCFG diff --git a/tests/data/acpi/loongarch64/virt/PPTT b/tests/data/acpi/loongarch64/virt/PPTT Binary files differnew file mode 100644 index 0000000..15598a9 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/PPTT diff --git a/tests/data/acpi/loongarch64/virt/PPTT.topology b/tests/data/acpi/loongarch64/virt/PPTT.topology Binary files differnew file mode 100644 index 0000000..7fc9298 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/PPTT.topology diff --git a/tests/qapi-schema/doc-bad-section.out b/tests/data/acpi/loongarch64/virt/SLIT index e69de29..e69de29 100644 --- a/tests/qapi-schema/doc-bad-section.out +++ b/tests/data/acpi/loongarch64/virt/SLIT diff --git a/tests/data/acpi/loongarch64/virt/SLIT.numamem b/tests/data/acpi/loongarch64/virt/SLIT.numamem Binary files differnew file mode 100644 index 0000000..67f0081 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SLIT.numamem diff --git a/tests/data/acpi/loongarch64/virt/SPCR b/tests/data/acpi/loongarch64/virt/SPCR Binary files differnew file mode 100644 index 0000000..3cc9bbc --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SPCR diff --git a/tests/data/acpi/loongarch64/virt/SRAT b/tests/data/acpi/loongarch64/virt/SRAT Binary files differnew file mode 100644 index 0000000..ff234ce --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SRAT diff --git a/tests/data/acpi/loongarch64/virt/SRAT.memhp b/tests/data/acpi/loongarch64/virt/SRAT.memhp Binary files differnew file mode 100644 index 0000000..5253218 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SRAT.memhp diff --git a/tests/data/acpi/loongarch64/virt/SRAT.numamem b/tests/data/acpi/loongarch64/virt/SRAT.numamem Binary files differnew file mode 100644 index 0000000..2972a9a --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SRAT.numamem diff --git a/tests/data/acpi/loongarch64/virt/SRAT.topology b/tests/data/acpi/loongarch64/virt/SRAT.topology Binary files differnew file mode 100644 index 0000000..4a44831 --- /dev/null +++ b/tests/data/acpi/loongarch64/virt/SRAT.topology diff --git a/tests/data/acpi/rebuild-expected-aml.sh b/tests/data/acpi/rebuild-expected-aml.sh index c1092fb..cbf9ffe 100755 --- a/tests/data/acpi/rebuild-expected-aml.sh +++ b/tests/data/acpi/rebuild-expected-aml.sh @@ -12,7 +12,7 @@ # This work is licensed under the terms of the GNU GPLv2. # See the COPYING.LIB file in the top-level directory. -qemu_arches="x86_64 aarch64 riscv64" +qemu_arches="x86_64 aarch64 riscv64 loongarch64" if [ ! -e "tests/qtest/bios-tables-test" ]; then echo "Test: bios-tables-test is required! Run make check before this script." @@ -37,7 +37,7 @@ if [ -z "$qemu_bins" ]; then echo "Only the following architectures are currently supported: $qemu_arches" echo "None of these configured!" echo "To fix, run configure \ - --target-list=x86_64-softmmu,aarch64-softmmu,riscv64-softmmu" + --target-list=x86_64-softmmu,aarch64-softmmu,riscv64-softmmu,loongarch64-softmmu" exit 1; fi diff --git a/tests/data/acpi/riscv64/virt/DSDT b/tests/data/acpi/riscv64/virt/DSDT Binary files differindex 6a33f56..527f239 100644 --- a/tests/data/acpi/riscv64/virt/DSDT +++ b/tests/data/acpi/riscv64/virt/DSDT diff --git a/tests/data/acpi/x86/microvm/DSDT.pcie b/tests/data/acpi/x86/microvm/DSDT.pcie Binary files differindex 8eacd21..ba258f4 100644 --- a/tests/data/acpi/x86/microvm/DSDT.pcie +++ b/tests/data/acpi/x86/microvm/DSDT.pcie diff --git a/tests/functional/test_x86_cpu_model_versions.py b/tests/functional/test_x86_cpu_model_versions.py index bd18acd..36c968f 100755 --- a/tests/functional/test_x86_cpu_model_versions.py +++ b/tests/functional/test_x86_cpu_model_versions.py @@ -72,44 +72,11 @@ class X86CPUModelAliases(QemuSystemTest): self.assertNotIn("EPYC-IBPB-v1", cpus, "EPYC-IBPB shouldn't be versioned") - def test_4_0_alias_compatibility(self): - """ - Check if pc-*-4.0 unversioned CPU model won't be reported as aliases - """ - self.set_machine('pc-i440fx-4.0') - # pc-*-4.0 won't expose non-versioned CPU models as aliases - # We do this to help management software to keep compatibility - # with older QEMU versions that didn't have the versioned CPU model - self.vm.add_args('-S') - self.vm.launch() - cpus = dict((m['name'], m) for m in - self.vm.cmd('query-cpu-definitions')) - - self.assertFalse(cpus['Cascadelake-Server']['static'], - 'unversioned Cascadelake-Server CPU model must not be static') - self.assertNotIn('alias-of', cpus['Cascadelake-Server'], - 'Cascadelake-Server must not be an alias') - self.assertNotIn('alias-of', cpus['Cascadelake-Server-v1'], - 'Cascadelake-Server-v1 must not be an alias') - - self.assertFalse(cpus['qemu64']['static'], - 'unversioned qemu64 CPU model must not be static') - self.assertNotIn('alias-of', cpus['qemu64'], - 'qemu64 must not be an alias') - self.assertNotIn('alias-of', cpus['qemu64-v1'], - 'qemu64-v1 must not be an alias') - - self.validate_variant_aliases(cpus) - - # On pc-*-4.0, no CPU model should be reported as an alias: - for name,c in cpus.items(): - self.assertNotIn('alias-of', c, "%s shouldn't be an alias" % (name)) - - def test_4_1_alias(self): + def test_unversioned_alias(self): """ Check if unversioned CPU model is an alias pointing to right version """ - self.set_machine('pc-i440fx-4.1') + self.set_machine('pc') self.vm.add_args('-S') self.vm.launch() @@ -133,7 +100,7 @@ class X86CPUModelAliases(QemuSystemTest): self.validate_variant_aliases(cpus) - # On pc-*-4.1, -noTSX and -IBRS models should be aliases: + # On recent PC machines, -noTSX and -IBRS models should be aliases: self.assertEqual(cpus["Haswell"].get('alias-of'), "Haswell-v1", "Haswell must be an alias") @@ -247,8 +214,8 @@ class CascadelakeArchCapabilities(QemuSystemTest): cpu_path = self.vm.cmd('query-cpus-fast')[0].get('qom-path') return self.vm.cmd('qom-get', path=cpu_path, property=prop) - def test_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test(self): + self.set_machine('pc') # machine-type only: self.vm.add_args('-S') self.set_vm_arg('-cpu', @@ -256,80 +223,27 @@ class CascadelakeArchCapabilities(QemuSystemTest): 'enforce=off') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server should not have arch-capabilities') - - def test_4_0(self): - self.set_machine('pc-i440fx-4.0') - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server should not have arch-capabilities') - - def test_set_4_0(self): - self.set_machine('pc-i440fx-4.0') - # command line must override machine-type if CPU model is not versioned: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server,x-force-features=on,check=off,' - 'enforce=off,+arch-capabilities') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server,+arch-capabilities should have arch-capabilities') + 'pc + Cascadelake-Server should not have arch-capabilities') - def test_unset_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test_unset(self): + self.set_machine('pc') self.vm.add_args('-S') self.set_vm_arg('-cpu', 'Cascadelake-Server,x-force-features=on,check=off,' 'enforce=off,-arch-capabilities') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server,-arch-capabilities should not have arch-capabilities') - - def test_v1_4_0(self): - self.set_machine('pc-i440fx-4.0') - # versioned CPU model overrides machine-type: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v1,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v1 should not have arch-capabilities') - - def test_v2_4_0(self): - self.set_machine('pc-i440fx-4.0') - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v2,x-force-features=on,check=off,' - 'enforce=off') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v2 should have arch-capabilities') - - def test_v1_set_4_0(self): - self.set_machine('pc-i440fx-4.0') - # command line must override machine-type and versioned CPU model: - self.vm.add_args('-S') - self.set_vm_arg('-cpu', - 'Cascadelake-Server-v1,x-force-features=on,check=off,' - 'enforce=off,+arch-capabilities') - self.vm.launch() - self.assertTrue(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.0 + Cascadelake-Server-v1,+arch-capabilities should have arch-capabilities') + 'pc + Cascadelake-Server,-arch-capabilities should not have arch-capabilities') - def test_v2_unset_4_1(self): - self.set_machine('pc-i440fx-4.1') + def test_v2_unset(self): + self.set_machine('pc') self.vm.add_args('-S') self.set_vm_arg('-cpu', 'Cascadelake-Server-v2,x-force-features=on,check=off,' 'enforce=off,-arch-capabilities') self.vm.launch() self.assertFalse(self.get_cpu_prop('arch-capabilities'), - 'pc-i440fx-4.1 + Cascadelake-Server-v2,-arch-capabilities should not have arch-capabilities') + 'pc + Cascadelake-Server-v2,-arch-capabilities should not have arch-capabilities') if __name__ == '__main__': QemuSystemTest.main() diff --git a/tests/qapi-schema/doc-bad-section.err b/tests/qapi-schema/doc-bad-section.err deleted file mode 100644 index 785cacc..0000000 --- a/tests/qapi-schema/doc-bad-section.err +++ /dev/null @@ -1 +0,0 @@ -doc-bad-section.json:5:1: unexpected '=' markup in definition documentation diff --git a/tests/qapi-schema/doc-bad-section.json b/tests/qapi-schema/doc-bad-section.json deleted file mode 100644 index 8175d95..0000000 --- a/tests/qapi-schema/doc-bad-section.json +++ /dev/null @@ -1,10 +0,0 @@ -# = section within an expression comment - -## -# @Enum: -# == No good here -# @one: The _one_ {and only} -# -# @two is undocumented -## -{ 'enum': 'Enum', 'data': [ 'one', 'two' ] } diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json index 14b808f..fac1342 100644 --- a/tests/qapi-schema/doc-good.json +++ b/tests/qapi-schema/doc-good.json @@ -8,7 +8,9 @@ 'documentation-exceptions': [ 'Enum', 'Variant1', 'Alternate', 'cmd' ] } } ## -# = Section +# ******* +# Section +# ******* ## ## @@ -16,7 +18,8 @@ ## ## -# == Subsection +# Subsection +# ========== # # *with emphasis* # @var {in braces} @@ -144,7 +147,8 @@ 'if': { 'not': { 'any': [ 'IFONE', 'IFTWO' ] } } } ## -# == Another subsection +# Another subsection +# ================== ## ## diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out index dc8352e..04a5507 100644 --- a/tests/qapi-schema/doc-good.out +++ b/tests/qapi-schema/doc-good.out @@ -55,13 +55,16 @@ event EVT_BOXED Object feature feat3 doc freeform body= -= Section +******* +Section +******* doc freeform body= Just text, no heading. doc freeform body= -== Subsection +Subsection +========== *with emphasis* @var {in braces} @@ -155,7 +158,8 @@ description starts on the same line a feature doc freeform body= -== Another subsection +Another subsection +================== doc symbol=cmd body= diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt index 17a1d56..74b7368 100644 --- a/tests/qapi-schema/doc-good.txt +++ b/tests/qapi-schema/doc-good.txt @@ -1,6 +1,8 @@ Section ******* +Just text, no heading. + Subsection ========== @@ -35,249 +37,145 @@ Example: -> in <- out Examples: - *verbatim* - {braces} +Enum Enum + *Availability*: "IFCOND" -"Enum" (Enum) -------------- - - -Values -~~~~~~ - -"one" (**If: **"IFONE") - The _one_ {and only}, description on the same line - -"two" - Not documented - - -Features -~~~~~~~~ - -"enum-feat" - Also _one_ {and only} - -"enum-member-feat" - a member feature - -"two" is undocumented - - -If -~~ - -"IFCOND" - - -"Base" (Object) ---------------- - - -Members -~~~~~~~ - -"base1": "Enum" - description starts on a new line, minimally indented - - -If -~~ - -"IFALL1 and IFALL2" - - -"Variant1" (Object) -------------------- - -A paragraph - -Another paragraph - -"var1" is undocumented + Values: + * **one** -- The _one_ {and only}, description on the same line + * **two** -- Not documented -Members -~~~~~~~ + Features: + * **enum-feat** -- Also _one_ {and only} -"var1": "string" (**If: **"IFSTR") - Not documented + * **enum-member-feat** -- a member feature + "two" is undocumented -Features -~~~~~~~~ +Object Base + *Availability*: "IFALL1 and IFALL2" -"variant1-feat" - a feature + Members: + * **base1** ("Enum") -- description starts on a new line, + minimally indented -"member-feat" - a member feature +Object Variant1 + A paragraph -"Variant2" (Object) -------------------- + Another paragraph + "var1" is undocumented -"Object" (Object) ------------------ + Members: + * **var1** ("string") -- Not documented + Features: + * **variant1-feat** -- a feature -Members -~~~~~~~ + * **member-feat** -- a member feature -The members of "Base" -The members of "Variant1" when "base1" is ""one"" -The members of "Variant2" when "base1" is ""two"" (**If: **"IFONE or -IFTWO") +Object Variant2 -Features -~~~~~~~~ +Object Object -"union-feat1" - a feature + Members: + * The members of "Base". + * When "base1" is "one": The members of "Variant1". -"Alternate" (Alternate) ------------------------ + * When "base1" is "two": The members of "Variant2". + Features: + * **union-feat1** -- a feature -Members -~~~~~~~ +Alternate Alternate + *Availability*: "not (IFONE or IFTWO)" -"i": "int" - description starts on the same line remainder indented the same "b" - is undocumented + Alternatives: + * **i** ("int") -- description starts on the same line remainder + indented the same "b" is undocumented -"b": "boolean" - Not documented + * **b** ("boolean") -- Not documented - -Features -~~~~~~~~ - -"alt-feat" - a feature - - -If -~~ - -"not (IFONE or IFTWO)" + Features: + * **alt-feat** -- a feature Another subsection ================== +Command cmd (Since: 2.10) -"cmd" (Command) ---------------- - - -Arguments -~~~~~~~~~ - -"arg1": "int" - description starts on a new line, indented - -"arg2": "string" (optional) - description starts on the same line remainder indented differently - -"arg3": "boolean" - Not documented - - -Features -~~~~~~~~ - -"cmd-feat1" - a feature + Arguments: + * **arg1** ("int") -- description starts on a new line, indented -"cmd-feat2" - another feature - -Note: - - "arg3" is undocumented - - -Returns -~~~~~~~ - -"Object" - - -Errors -~~~~~~ - -some - -Notes: - -* Lorem ipsum dolor sit amet - -* Ut enim ad minim veniam - -Duis aute irure dolor + * **arg2** ("string", *optional*) -- description starts on the + same line remainder indented differently -Example: Ideal fast-food burger situation: + * **arg3** ("boolean") -- Not documented - -> "in" - <- "out" + Features: + * **cmd-feat1** -- a feature -Examples: + * **cmd-feat2** -- another feature - - Not a QMP code block - - Merely a preformatted code block literal - It isn't even an rST list. - - *verbatim* - - {braces} + Note: -Note:: - Ceci n'est pas une note + "arg3" is undocumented + Return: + "Object" -- "Object" -Since -~~~~~ + Errors: + some -2.10 + Notes: + * Lorem ipsum dolor sit amet -"cmd-boxed" (Command) ---------------------- + * Ut enim ad minim veniam -If you're bored enough to read this, go see a video of boxed cats + Duis aute irure dolor + Example: Ideal fast-food burger situation: -Arguments -~~~~~~~~~ + -> "in" + <- "out" -The members of "Object" + Examples: -Features -~~~~~~~~ + - Not a QMP code block + - Merely a preformatted code block literal + It isn't even an rST list. + - *verbatim* + - {braces} -"cmd-feat1" - a feature + Note:: + Ceci n'est pas une note -"cmd-feat2" - another feature +Command cmd-boxed -Example:: + If you're bored enough to read this, go see a video of boxed cats - -> "this example" + Arguments: + * The members of "Object". - <- ... has no title ... + Features: + * **cmd-feat1** -- a feature + * **cmd-feat2** -- another feature -"EVT_BOXED" (Event) -------------------- + Example:: + -> "this example" -Arguments -~~~~~~~~~ + <- ... has no title ... -The members of "Object" +Event EVT_BOXED -Features -~~~~~~~~ + Members: + * The members of "Object". -"feat3" - a feature + Features: + * **feat3** -- a feature diff --git a/tests/qapi-schema/meson.build b/tests/qapi-schema/meson.build index 9577178..c47025d 100644 --- a/tests/qapi-schema/meson.build +++ b/tests/qapi-schema/meson.build @@ -61,7 +61,6 @@ schemas = [ 'doc-bad-event-arg.json', 'doc-bad-feature.json', 'doc-bad-indent.json', - 'doc-bad-section.json', 'doc-bad-symbol.json', 'doc-bad-union-member.json', 'doc-before-include.json', diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out index 34e1b45..70c6275 100644 --- a/tests/qemu-iotests/049.out +++ b/tests/qemu-iotests/049.out @@ -98,8 +98,7 @@ qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2 qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size' qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: '-1k' qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2 qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64 @@ -107,8 +106,7 @@ Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta- and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: '1kilobyte' qemu-img create -f qcow2 -o size=1kilobyte TEST_DIR/t.qcow2 qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64 @@ -116,8 +114,7 @@ Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta- and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- foobar -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: 'foobar' qemu-img create -f qcow2 -o size=foobar TEST_DIR/t.qcow2 qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64 diff --git a/tests/qemu-iotests/153 b/tests/qemu-iotests/153 index 9bc3be8..1e02f6a 100755 --- a/tests/qemu-iotests/153 +++ b/tests/qemu-iotests/153 @@ -63,7 +63,7 @@ _supported_proto file _run_cmd() { echo - (echo "$@"; "$@" 2>&1 1>/dev/null) | _filter_testdir + (echo "$@"; "$@" 2>&1 1>/dev/null) | _filter_testdir | _filter_qemu_img } _do_run_qemu() diff --git a/tests/qemu-iotests/153.out b/tests/qemu-iotests/153.out index ff8e558..28e1a22 100644 --- a/tests/qemu-iotests/153.out +++ b/tests/qemu-iotests/153.out @@ -120,16 +120,16 @@ _qemu_img_wrapper compare -U TEST_DIR/t.qcow2 TEST_DIR/t.qcow2 _qemu_img_wrapper map -U TEST_DIR/t.qcow2 _qemu_img_wrapper amend -o size=32M -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img amend: invalid option -- 'U' +Try 'qemu-img amend --help' for more information _qemu_img_wrapper commit -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img commit: invalid option -- 'U' +Try 'qemu-img commit --help' for more information _qemu_img_wrapper resize -U TEST_DIR/t.qcow2 32M -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img resize: invalid option -- 'U' +Try 'qemu-img resize --help' for more information _qemu_img_wrapper rebase -U TEST_DIR/t.qcow2 -b TEST_DIR/t.qcow2.base -F qcow2 qemu-img: Could not open 'TEST_DIR/t.qcow2': Failed to get "write" lock @@ -244,16 +244,16 @@ _qemu_img_wrapper compare -U TEST_DIR/t.qcow2 TEST_DIR/t.qcow2 _qemu_img_wrapper map -U TEST_DIR/t.qcow2 _qemu_img_wrapper amend -o size=32M -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img amend: invalid option -- 'U' +Try 'qemu-img amend --help' for more information _qemu_img_wrapper commit -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img commit: invalid option -- 'U' +Try 'qemu-img commit --help' for more information _qemu_img_wrapper resize -U TEST_DIR/t.qcow2 32M -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img resize: invalid option -- 'U' +Try 'qemu-img resize --help' for more information _qemu_img_wrapper rebase -U TEST_DIR/t.qcow2 -b TEST_DIR/t.qcow2.base -F qcow2 qemu-img: Could not open 'TEST_DIR/t.qcow2': Failed to get "write" lock @@ -349,16 +349,16 @@ _qemu_img_wrapper compare -U TEST_DIR/t.qcow2 TEST_DIR/t.qcow2 _qemu_img_wrapper map -U TEST_DIR/t.qcow2 _qemu_img_wrapper amend -o size=32M -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img amend: invalid option -- 'U' +Try 'qemu-img amend --help' for more information _qemu_img_wrapper commit -U TEST_DIR/t.qcow2 -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img commit: invalid option -- 'U' +Try 'qemu-img commit --help' for more information _qemu_img_wrapper resize -U TEST_DIR/t.qcow2 32M -qemu-img: unrecognized option '-U' -Try 'qemu-img --help' for more information +qemu-img resize: invalid option -- 'U' +Try 'qemu-img resize --help' for more information _qemu_img_wrapper rebase -U TEST_DIR/t.qcow2 -b TEST_DIR/t.qcow2.base -F qcow2 diff --git a/tests/qemu-iotests/178 b/tests/qemu-iotests/178 index 8df241e..463c59a 100755 --- a/tests/qemu-iotests/178 +++ b/tests/qemu-iotests/178 @@ -58,7 +58,7 @@ $QEMU_IMG measure -f qcow2 # missing filename $QEMU_IMG measure -l snap1 # missing filename $QEMU_IMG measure -o , # invalid option list $QEMU_IMG measure -l snapshot.foo=bar # invalid snapshot option -$QEMU_IMG measure --output foo # invalid output format +$QEMU_IMG measure --output foo 2>&1 | _filter_qemu_img # invalid output format $QEMU_IMG measure --size -1 # invalid image size $QEMU_IMG measure -O foo "$TEST_IMG" # unknown image file format diff --git a/tests/qemu-iotests/178.out.qcow2 b/tests/qemu-iotests/178.out.qcow2 index fe193fd..61506b5 100644 --- a/tests/qemu-iotests/178.out.qcow2 +++ b/tests/qemu-iotests/178.out.qcow2 @@ -12,7 +12,8 @@ qemu-img: --image-opts, -f, and -l require a filename argument. qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar' -qemu-img: --output must be used with human or json as argument. +qemu-img: --output expects 'human' or 'json', not 'foo' +Try 'qemu-img measure --help' for more information qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. qemu-img: Unknown file format 'foo' diff --git a/tests/qemu-iotests/178.out.raw b/tests/qemu-iotests/178.out.raw index 445e460..6d994a4 100644 --- a/tests/qemu-iotests/178.out.raw +++ b/tests/qemu-iotests/178.out.raw @@ -12,7 +12,8 @@ qemu-img: --image-opts, -f, and -l require a filename argument. qemu-img: Invalid option list: , qemu-img: Invalid parameter 'snapshot.foo' qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar' -qemu-img: --output must be used with human or json as argument. +qemu-img: --output expects 'human' or 'json', not 'foo' +Try 'qemu-img measure --help' for more information qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807. qemu-img: Unknown file format 'foo' diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out index 52692b6..ef99bb2 100644 --- a/tests/qemu-iotests/184.out +++ b/tests/qemu-iotests/184.out @@ -41,6 +41,12 @@ Testing: }, "iops_wr": 0, "ro": false, + "children": [ + { + "node-name": "disk0", + "child": "file" + } + ], "node-name": "throttle0", "backing_file_depth": 1, "drv": "throttle", @@ -69,6 +75,8 @@ Testing: }, "iops_wr": 0, "ro": false, + "children": [ + ], "node-name": "disk0", "backing_file_depth": 0, "drv": "null-co", diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index fc3c64b..67f819d 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -86,6 +86,12 @@ _filter_qemu() -e $'s#\r##' # QEMU monitor uses \r\n line endings } +# replace occurrences of QEMU_IMG_PROG with "qemu-img" +_filter_qemu_img() +{ + sed -e "s#$QEMU_IMG_PROG#qemu-img#g" +} + # replace problematic QMP output like timestamps _filter_qmp() { diff --git a/tests/qemu-iotests/tests/qom-set-drive b/tests/qemu-iotests/tests/qom-set-drive new file mode 100755 index 0000000..ec8ddac --- /dev/null +++ b/tests/qemu-iotests/tests/qom-set-drive @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# group: quick +# +# Test how changing the 'drive' property via 'qom-set' behaves. +# +# Copyright (C) Proxmox Server Solutions GmbH +# +# 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/>. +# + +import os +import iotests +from iotests import imgfmt, log, qemu_img_create, QMPTestCase + +image_size = 1 * 1024 * 1024 +images = [os.path.join(iotests.test_dir, f'{i}.img') for i in range(0, 4)] + +class TestQOMSetDrive(QMPTestCase): + def setUp(self) -> None: + for image in images: + qemu_img_create('-f', imgfmt, image, str(image_size)) + + self.vm = iotests.VM() + for i, image in enumerate(images): + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': imgfmt, + 'node-name': f'node{i}', + 'file': { + 'driver': 'file', + 'filename': image, + } + })) + self.vm.add_object('iothread,id=iothread0') + self.vm.add_device('virtio-scsi,iothread=iothread0') + self.vm.add_device('scsi-hd,id=iot,drive=node0') + self.vm.add_device('virtio-scsi') + self.vm.add_device('scsi-hd,id=no-iot,drive=node1') + self.vm.launch() + + def tearDown(self) -> None: + self.vm.shutdown() + for image in images: + os.remove(image) + + def test_qom_set_drive(self) -> None: + log(self.vm.qmp('qom-get', path='/machine/peripheral/iot', + property='drive')) + log(self.vm.qmp('qom-set', path='/machine/peripheral/iot', + property='drive', value='node2')) + log(self.vm.qmp('qom-get', path='/machine/peripheral/iot', + property='drive')) + + log(self.vm.qmp('qom-get', path='/machine/peripheral/no-iot', + property='drive')) + log(self.vm.qmp('qom-set', path='/machine/peripheral/no-iot', + property='drive', value='node3')) + log(self.vm.qmp('qom-get', path='/machine/peripheral/no-iot', + property='drive')) + +if __name__ == '__main__': + iotests.activate_logging() + # LUKS would require special key-secret handling in add_blockdevs() + iotests.main(supported_fmts=['generic'], + unsupported_fmts=['luks']) diff --git a/tests/qemu-iotests/tests/qom-set-drive.out b/tests/qemu-iotests/tests/qom-set-drive.out new file mode 100644 index 0000000..7fc243d --- /dev/null +++ b/tests/qemu-iotests/tests/qom-set-drive.out @@ -0,0 +1,11 @@ +{"return": "node0"} +{"error": {"class": "GenericError", "desc": "Different aio context is not supported for new node"}} +{"return": "node0"} +{"return": "node1"} +{"return": {}} +{"return": "node3"} +. +---------------------------------------------------------------------- +Ran 1 tests + +OK diff --git a/tests/qtest/bios-tables-test.c b/tests/qtest/bios-tables-test.c index 4dbc07e..6aec68d 100644 --- a/tests/qtest/bios-tables-test.c +++ b/tests/qtest/bios-tables-test.c @@ -1643,6 +1643,54 @@ static void test_acpi_aarch64_virt_tcg_memhp(void) } +static void test_acpi_aarch64_virt_acpi_pci_hotplug(void) +{ + test_data data = { + .machine = "virt", + .arch = "aarch64", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", + .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2", + .ram_start = 0x40000000ULL, + .scan_len = 256ULL * MiB, + .variant = ".acpipcihp", + }; + + /* Use ACPI PCI Hotplug */ + test_acpi_one(" -global acpi-ged.acpi-pci-hotplug-with-bridge-support=on" + " -cpu cortex-a57" + " -device pcie-root-port,id=pcie.1,bus=pcie.0,chassis=0,slot=1,addr=7.0" + " -device pci-testdev,bus=pcie.1", + &data); + + free_test_data(&data); +} + +static void test_acpi_aarch64_virt_pcie_root_port_hpoff(void) +{ + test_data data = { + .machine = "virt", + .arch = "aarch64", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", + .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2", + .ram_start = 0x40000000ULL, + .scan_len = 256ULL * MiB, + .variant = ".hpoffacpiindex", + }; + + /* turn hotplug off on the pcie-root-port and use static acpi-index*/ + test_acpi_one(" -device pcie-root-port,id=pcie.1,chassis=0," + "slot=1,hotplug=off,addr=7.0" + " -device pci-testdev,bus=pcie.1,acpi-index=12" + " -cpu cortex-a57", + &data); + + free_test_data(&data); +} + static void test_acpi_microvm_prepare(test_data *data) { data->machine = "microvm"; @@ -1789,6 +1837,44 @@ static void test_acpi_aarch64_virt_tcg_pxb(void) free_test_data(&data); } +static void test_acpi_aarch64_virt_tcg_acpi_spcr(void) +{ + test_data data = { + .machine = "virt", + .arch = "aarch64", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", + .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.aarch64.iso.qcow2", + .ram_start = 0x40000000ULL, + .scan_len = 128ULL * 1024 * 1024, + .variant = ".acpispcr", + }; + + test_acpi_one("-cpu cortex-a57 " + " -machine spcr=off", &data); + free_test_data(&data); +} + +static void test_acpi_riscv64_virt_tcg_acpi_spcr(void) +{ + test_data data = { + .machine = "virt", + .arch = "riscv64", + .tcg_only = true, + .uefi_fl1 = "pc-bios/edk2-riscv-code.fd", + .uefi_fl2 = "pc-bios/edk2-riscv-vars.fd", + .cd = "tests/data/uefi-boot-images/bios-tables-test.riscv64.iso.qcow2", + .ram_start = 0x80000000ULL, + .scan_len = 128ULL * 1024 * 1024, + .variant = ".acpispcr", + }; + + test_acpi_one("-cpu rva22s64 " + "-machine spcr=off", &data); + free_test_data(&data); +} + static void test_acpi_tcg_acpi_hmat(const char *machine, const char *arch) { test_data data = {}; @@ -2237,6 +2323,7 @@ static void test_acpi_aarch64_virt_viot(void) test_data data = { .machine = "virt", .arch = "aarch64", + .variant = ".viot", .tcg_only = true, .uefi_fl1 = "pc-bios/edk2-aarch64-code.fd", .uefi_fl2 = "pc-bios/edk2-arm-vars.fd", @@ -2439,6 +2526,74 @@ static void test_acpi_aarch64_virt_oem_fields(void) g_free(args); } +#define LOONGARCH64_INIT_TEST_DATA(data) \ + test_data data = { \ + .machine = "virt", \ + .arch = "loongarch64", \ + .tcg_only = true, \ + .uefi_fl1 = "pc-bios/edk2-loongarch64-code.fd", \ + .uefi_fl2 = "pc-bios/edk2-loongarch64-vars.fd", \ + .cd = "tests/data/uefi-boot-images/" \ + "bios-tables-test.loongarch64.iso.qcow2", \ + .ram_start = 0, \ + .scan_len = 128ULL * MiB, \ + } + +static void test_acpi_loongarch64_virt(void) +{ + LOONGARCH64_INIT_TEST_DATA(data); + + test_acpi_one("-cpu la464 ", &data); + free_test_data(&data); +} + +static void test_acpi_loongarch64_virt_topology(void) +{ + LOONGARCH64_INIT_TEST_DATA(data); + + data.variant = ".topology"; + test_acpi_one("-cpu la464 -smp sockets=1,cores=2,threads=2", &data); + free_test_data(&data); +} + +static void test_acpi_loongarch64_virt_numamem(void) +{ + LOONGARCH64_INIT_TEST_DATA(data); + + data.variant = ".numamem"; + test_acpi_one(" -cpu la464 -m 128" + " -object memory-backend-ram,id=ram0,size=64M" + " -object memory-backend-ram,id=ram1,size=64M" + " -numa node,memdev=ram0 -numa node,memdev=ram1" + " -numa dist,src=0,dst=1,val=21", + &data); + free_test_data(&data); +} + +static void test_acpi_loongarch64_virt_memhp(void) +{ + LOONGARCH64_INIT_TEST_DATA(data); + + data.variant = ".memhp"; + test_acpi_one(" -cpu la464 -m 128,slots=2,maxmem=256M" + " -object memory-backend-ram,id=ram0,size=128M", + &data); + free_test_data(&data); +} + +static void test_acpi_loongarch64_virt_oem_fields(void) +{ + LOONGARCH64_INIT_TEST_DATA(data); + char *args; + + args = test_acpi_create_args(&data, "-cpu la464 "OEM_TEST_ARGS); + data.qts = qtest_init(args); + test_acpi_load_tables(&data); + test_oem_fields(&data); + qtest_quit(data.qts); + free_test_data(&data); + g_free(args); +} int main(int argc, char *argv[]) { @@ -2601,9 +2756,15 @@ int main(int argc, char *argv[]) qtest_add_func("acpi/virt/numamem", test_acpi_aarch64_virt_tcg_numamem); qtest_add_func("acpi/virt/memhp", test_acpi_aarch64_virt_tcg_memhp); + qtest_add_func("acpi/virt/acpipcihp", + test_acpi_aarch64_virt_acpi_pci_hotplug); + qtest_add_func("acpi/virt/hpoffacpiindex", + test_acpi_aarch64_virt_pcie_root_port_hpoff); qtest_add_func("acpi/virt/pxb", test_acpi_aarch64_virt_tcg_pxb); qtest_add_func("acpi/virt/oem-fields", test_acpi_aarch64_virt_oem_fields); + qtest_add_func("acpi/virt/acpispcr", + test_acpi_aarch64_virt_tcg_acpi_spcr); if (qtest_has_device("virtio-iommu-pci")) { qtest_add_func("acpi/virt/viot", test_acpi_aarch64_virt_viot); } @@ -2613,6 +2774,19 @@ int main(int argc, char *argv[]) qtest_add_func("acpi/virt", test_acpi_riscv64_virt_tcg); qtest_add_func("acpi/virt/numamem", test_acpi_riscv64_virt_tcg_numamem); + qtest_add_func("acpi/virt/acpispcr", + test_acpi_riscv64_virt_tcg_acpi_spcr); + } + } else if (strcmp(arch, "loongarch64") == 0) { + if (has_tcg) { + qtest_add_func("acpi/virt", test_acpi_loongarch64_virt); + qtest_add_func("acpi/virt/topology", + test_acpi_loongarch64_virt_topology); + qtest_add_func("acpi/virt/numamem", + test_acpi_loongarch64_virt_numamem); + qtest_add_func("acpi/virt/memhp", test_acpi_loongarch64_virt_memhp); + qtest_add_func("acpi/virt/oem-fields", + test_acpi_loongarch64_virt_oem_fields); } } ret = g_test_run(); diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 5ad969f..669d07c 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -148,6 +148,7 @@ qtests_hppa = \ qtests_loongarch64 = qtests_filter + \ (config_all_devices.has_key('CONFIG_LOONGARCH_VIRT') ? ['numa-test'] : []) + \ + (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ ['boot-serial-test', 'cpu-plug-test'] diff --git a/tests/tcg/mips/include/wrappers_mips64r6.h b/tests/tcg/mips/include/wrappers_mips64r6.h index d1e5edb..33d03de 100644 --- a/tests/tcg/mips/include/wrappers_mips64r6.h +++ b/tests/tcg/mips/include/wrappers_mips64r6.h @@ -23,6 +23,7 @@ #ifndef WRAPPERS_MIPS64R6_H #define WRAPPERS_MIPS64R6_H +#include <string.h> #define DO_MIPS64R6__RD__RS(suffix, mnemonic) \ static inline void do_mips64r6_##suffix(const void *input, \ @@ -80,4 +81,35 @@ DO_MIPS64R6__RD__RS_RT(DMULU, dmulu) DO_MIPS64R6__RD__RS_RT(DMUHU, dmuhu) +#define DO_MIPS64R6__RT__RS_RT(suffix, mnemonic) \ +static inline void do_mips64r6_##suffix(const void *input1, \ + const void *input2, \ + void *output) \ +{ \ + if (strncmp(#mnemonic, "crc32", 5) == 0) \ + __asm__ volatile ( \ + ".set crc\n\t" \ + ); \ + \ + __asm__ volatile ( \ + "ld $t1, 0(%0)\n\t" \ + "ld $t2, 0(%1)\n\t" \ + #mnemonic " $t2, $t1, $t2\n\t" \ + "sd $t2, 0(%2)\n\t" \ + : \ + : "r" (input1), "r" (input2), "r" (output) \ + : "t0", "t1", "t2", "memory" \ + ); \ +} + +DO_MIPS64R6__RT__RS_RT(CRC32B, crc32b) +DO_MIPS64R6__RT__RS_RT(CRC32H, crc32h) +DO_MIPS64R6__RT__RS_RT(CRC32W, crc32w) +DO_MIPS64R6__RT__RS_RT(CRC32D, crc32d) + +DO_MIPS64R6__RT__RS_RT(CRC32CB, crc32cb) +DO_MIPS64R6__RT__RS_RT(CRC32CH, crc32ch) +DO_MIPS64R6__RT__RS_RT(CRC32CW, crc32cw) +DO_MIPS64R6__RT__RS_RT(CRC32CD, crc32cd) + #endif diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/Makefile b/tests/tcg/mips/user/isa/mips64r6/crc/Makefile new file mode 100644 index 0000000..b7f5811 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/Makefile @@ -0,0 +1,40 @@ +# +# Test program for MIPS64R6 CRC32 instructions +# +# Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +ifndef PREFIX + $(error "PREFIX not set, please export GNU Toolchain install directory.") +endif + +ifndef SYSROOT + $(error "SYSROOT not set, please export GNU Toolchain system root directory.") +endif + +SIM = ../../../../../../../build/qemu-mips64 +SIM_FLAGS = -L $(SYSROOT) + +CC = $(PREFIX)/bin/mips64-r6-linux-gnu-gcc + +TESTCASES = test_mips64r6_crc32b.tst +TESTCASES += test_mips64r6_crc32h.tst +TESTCASES += test_mips64r6_crc32w.tst +TESTCASES += test_mips64r6_crc32d.tst +TESTCASES += test_mips64r6_crc32cb.tst +TESTCASES += test_mips64r6_crc32ch.tst +TESTCASES += test_mips64r6_crc32cw.tst +TESTCASES += test_mips64r6_crc32cd.tst + +all: $(TESTCASES) + @for case in $(TESTCASES); do \ + echo $(SIM) $(SIM_FLAGS) ./$$case; \ + $(SIM) $(SIM_FLAGS) ./$$case; \ + echo $(RM) -rf ./$$case; \ + $(RM) -rf ./$$case; \ + done + +%.tst: %.c + $(CC) $< -o $@ diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32b.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32b.c new file mode 100644 index 0000000..bb1f3f6 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32b.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32B + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0xEDB88320"; + char *instruction_name = "CRC32B"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x0000000000ffffffULL, /* 0 */ + 0x000000002d02ef8dULL, + 0x000000001bab0fd1ULL, + 0x0000000036561fa3ULL, + 0xffffffffbf1caddaULL, + 0xffffffff92e1bda8ULL, + 0x00000000278c7949ULL, + 0x000000000a71693bULL, + 0x000000002dfd1072ULL, /* 8 */ + 0x0000000000000000ULL, + 0x0000000036a9e05cULL, + 0x000000001b54f02eULL, + 0xffffffff921e4257ULL, + 0xffffffffbfe35225ULL, + 0x000000000a8e96c4ULL, + 0x00000000277386b6ULL, + 0x000000001bfe5a84ULL, /* 16 */ + 0x0000000036034af6ULL, + 0x0000000000aaaaaaULL, + 0x000000002d57bad8ULL, + 0xffffffffa41d08a1ULL, + 0xffffffff89e018d3ULL, + 0x000000003c8ddc32ULL, + 0x000000001170cc40ULL, + 0x0000000036fcb509ULL, /* 24 */ + 0x000000001b01a57bULL, + 0x000000002da84527ULL, + 0x0000000000555555ULL, + 0xffffffff891fe72cULL, + 0xffffffffa4e2f75eULL, + 0x00000000118f33bfULL, + 0x000000003c7223cdULL, + 0xffffffffbf2f9ee9ULL, /* 32 */ + 0xffffffff92d28e9bULL, + 0xffffffffa47b6ec7ULL, + 0xffffffff89867eb5ULL, + 0x0000000000ccccccULL, + 0x000000002d31dcbeULL, + 0xffffffff985c185fULL, + 0xffffffffb5a1082dULL, + 0xffffffff922d7164ULL, /* 40 */ + 0xffffffffbfd06116ULL, + 0xffffffff8979814aULL, + 0xffffffffa4849138ULL, + 0x000000002dce2341ULL, + 0x0000000000333333ULL, + 0xffffffffb55ef7d2ULL, + 0xffffffff98a3e7a0ULL, + 0x0000000027fdbe55ULL, /* 48 */ + 0x000000000a00ae27ULL, + 0x000000003ca94e7bULL, + 0x0000000011545e09ULL, + 0xffffffff981eec70ULL, + 0xffffffffb5e3fc02ULL, + 0x00000000008e38e3ULL, + 0x000000002d732891ULL, + 0x000000000aff51d8ULL, /* 56 */ + 0x00000000270241aaULL, + 0x0000000011aba1f6ULL, + 0x000000003c56b184ULL, + 0xffffffffb51c03fdULL, + 0xffffffff98e1138fULL, + 0x000000002d8cd76eULL, + 0x000000000071c71cULL, + 0x0000000000286255ULL, /* 64 */ + 0x00000000784a5a65ULL, + 0xffffffff9bdd0d3bULL, + 0xffffffffe7e61ce5ULL, + 0x00000000782fabf7ULL, + 0x00000000004d93c7ULL, + 0xffffffffe3dac499ULL, + 0xffffffff9fe1d547ULL, + 0xffffffff9b4ca0e5ULL, /* 72 */ + 0xffffffffe32e98d5ULL, + 0x0000000000b9cf8bULL, + 0x000000007c82de55ULL, + 0xffffffffe7904f52ULL, + 0xffffffff9ff27762ULL, + 0x000000007c65203cULL, + 0x00000000005e31e2ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32B(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32B(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cb.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cb.c new file mode 100644 index 0000000..1439d44 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cb.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32CB + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0x82F63B78"; + char *instruction_name = "CRC32CB"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x0000000000ffffffULL, /* 0 */ + 0xffffffffad7d5351ULL, + 0x00000000647e6465ULL, + 0xffffffffc9fcc8cbULL, + 0x00000000237f7689ULL, + 0xffffffff8efdda27ULL, + 0xffffffff837defedULL, + 0x000000002eff4343ULL, + 0xffffffffad82acaeULL, /* 8 */ + 0x0000000000000000ULL, + 0xffffffffc9033734ULL, + 0x0000000064819b9aULL, + 0xffffffff8e0225d8ULL, + 0x0000000023808976ULL, + 0x000000002e00bcbcULL, + 0xffffffff83821012ULL, + 0x00000000642b3130ULL, /* 16 */ + 0xffffffffc9a99d9eULL, + 0x0000000000aaaaaaULL, + 0xffffffffad280604ULL, + 0x0000000047abb846ULL, + 0xffffffffea2914e8ULL, + 0xffffffffe7a92122ULL, + 0x000000004a2b8d8cULL, + 0xffffffffc9566261ULL, /* 24 */ + 0x0000000064d4cecfULL, + 0xffffffffadd7f9fbULL, + 0x0000000000555555ULL, + 0xffffffffead6eb17ULL, + 0x00000000475447b9ULL, + 0x000000004ad47273ULL, + 0xffffffffe756deddULL, + 0x00000000234c45baULL, /* 32 */ + 0xffffffff8ecee914ULL, + 0x0000000047cdde20ULL, + 0xffffffffea4f728eULL, + 0x0000000000ccccccULL, + 0xffffffffad4e6062ULL, + 0xffffffffa0ce55a8ULL, + 0x000000000d4cf906ULL, + 0xffffffff8e3116ebULL, /* 40 */ + 0x0000000023b3ba45ULL, + 0xffffffffeab08d71ULL, + 0x00000000473221dfULL, + 0xffffffffadb19f9dULL, + 0x0000000000333333ULL, + 0x000000000db306f9ULL, + 0xffffffffa031aa57ULL, + 0xffffffff830c28f1ULL, /* 48 */ + 0x000000002e8e845fULL, + 0xffffffffe78db36bULL, + 0x000000004a0f1fc5ULL, + 0xffffffffa08ca187ULL, + 0x000000000d0e0d29ULL, + 0x00000000008e38e3ULL, + 0xffffffffad0c944dULL, + 0x000000002e717ba0ULL, /* 56 */ + 0xffffffff83f3d70eULL, + 0x000000004af0e03aULL, + 0xffffffffe7724c94ULL, + 0x000000000df1f2d6ULL, + 0xffffffffa0735e78ULL, + 0xffffffffadf36bb2ULL, + 0x000000000071c71cULL, + 0x0000000000286255ULL, /* 64 */ + 0xffffffffcbefd6b4ULL, + 0xffffffffc334e94fULL, + 0xffffffffac268ec5ULL, + 0xffffffffcb8a2726ULL, + 0x00000000004d93c7ULL, + 0x000000000896ac3cULL, + 0x000000006784cbb6ULL, + 0xffffffffc3a54491ULL, /* 72 */ + 0x000000000862f070ULL, + 0x0000000000b9cf8bULL, + 0x000000006faba801ULL, + 0xffffffffac50dd72ULL, + 0x0000000067976993ULL, + 0x000000006f4c5668ULL, + 0x00000000005e31e2ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CB(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CB(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cd.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cd.c new file mode 100644 index 0000000..bf258e0 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cd.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32CD + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0x82F63B78"; + char *instruction_name = "CRC32CD"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0xffffffffb798b438ULL, /* 0 */ + 0xffffffffc44ff94dULL, + 0xffffffff992a70ebULL, + 0xffffffffeafd3d9eULL, + 0x000000005152da26ULL, + 0x0000000022859753ULL, + 0x0000000015cb6d32ULL, + 0x00000000661c2047ULL, + 0x0000000073d74d75ULL, /* 8 */ + 0x0000000000000000ULL, + 0x000000005d6589a6ULL, + 0x000000002eb2c4d3ULL, + 0xffffffff951d236bULL, + 0xffffffffe6ca6e1eULL, + 0xffffffffd184947fULL, + 0xffffffffa253d90aULL, + 0x0000000008f9ceacULL, /* 16 */ + 0x000000007b2e83d9ULL, + 0x00000000264b0a7fULL, + 0x00000000559c470aULL, + 0xffffffffee33a0b2ULL, + 0xffffffff9de4edc7ULL, + 0xffffffffaaaa17a6ULL, + 0xffffffffd97d5ad3ULL, + 0xffffffffccb637e1ULL, /* 24 */ + 0xffffffffbf617a94ULL, + 0xffffffffe204f332ULL, + 0xffffffff91d3be47ULL, + 0x000000002a7c59ffULL, + 0x0000000059ab148aULL, + 0x000000006ee5eeebULL, + 0x000000001d32a39eULL, + 0x0000000021e3b01bULL, /* 32 */ + 0x000000005234fd6eULL, + 0x000000000f5174c8ULL, + 0x000000007c8639bdULL, + 0xffffffffc729de05ULL, + 0xffffffffb4fe9370ULL, + 0xffffffff83b06911ULL, + 0xfffffffff0672464ULL, + 0xffffffffe5ac4956ULL, /* 40 */ + 0xffffffff967b0423ULL, + 0xffffffffcb1e8d85ULL, + 0xffffffffb8c9c0f0ULL, + 0x0000000003662748ULL, + 0x0000000070b16a3dULL, + 0x0000000047ff905cULL, + 0x000000003428dd29ULL, + 0xffffffffb89d59a6ULL, /* 48 */ + 0xffffffffcb4a14d3ULL, + 0xffffffff962f9d75ULL, + 0xffffffffe5f8d000ULL, + 0x000000005e5737b8ULL, + 0x000000002d807acdULL, + 0x000000001ace80acULL, + 0x000000006919cdd9ULL, + 0x000000007cd2a0ebULL, /* 56 */ + 0x000000000f05ed9eULL, + 0x0000000052606438ULL, + 0x0000000021b7294dULL, + 0xffffffff9a18cef5ULL, + 0xffffffffe9cf8380ULL, + 0xffffffffde8179e1ULL, + 0xffffffffad563494ULL, + 0x000000003a358bb3ULL, /* 64 */ + 0xffffffff975446ebULL, + 0x0000000041d37ad6ULL, + 0x000000004be84fe1ULL, + 0xffffffff9671b1b3ULL, + 0x000000003b107cebULL, + 0xffffffffed9740d6ULL, + 0xffffffffe7ac75e1ULL, + 0xffffffffa1489696ULL, /* 72 */ + 0x000000000c295bceULL, + 0xffffffffdaae67f3ULL, + 0xffffffffd09552c4ULL, + 0x0000000042bd7071ULL, + 0xffffffffefdcbd29ULL, + 0x00000000395b8114ULL, + 0x000000003360b423ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CD(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CD(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32ch.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32ch.c new file mode 100644 index 0000000..0e7b677 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32ch.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32CH + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0x82F63B78"; + char *instruction_name = "CRC32CH"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x000000000000ffffULL, /* 0 */ + 0x000000000e9e77d2ULL, + 0xfffffffff92eaa4bULL, + 0xfffffffff7b02266ULL, + 0x00000000571acc93ULL, + 0x00000000598444beULL, + 0xfffffffff1e6ca77ULL, + 0xffffffffff78425aULL, + 0x000000000e9e882dULL, /* 8 */ + 0x0000000000000000ULL, + 0xfffffffff7b0dd99ULL, + 0xfffffffff92e55b4ULL, + 0x000000005984bb41ULL, + 0x00000000571a336cULL, + 0xffffffffff78bda5ULL, + 0xfffffffff1e63588ULL, + 0xfffffffff92eff1eULL, /* 16 */ + 0xfffffffff7b07733ULL, + 0x000000000000aaaaULL, + 0x000000000e9e2287ULL, + 0xffffffffae34cc72ULL, + 0xffffffffa0aa445fULL, + 0x0000000008c8ca96ULL, + 0x00000000065642bbULL, + 0xfffffffff7b088ccULL, /* 24 */ + 0xfffffffff92e00e1ULL, + 0x000000000e9edd78ULL, + 0x0000000000005555ULL, + 0xffffffffa0aabba0ULL, + 0xffffffffae34338dULL, + 0x000000000656bd44ULL, + 0x0000000008c83569ULL, + 0x00000000571affa0ULL, /* 32 */ + 0x000000005984778dULL, + 0xffffffffae34aa14ULL, + 0xffffffffa0aa2239ULL, + 0x000000000000ccccULL, + 0x000000000e9e44e1ULL, + 0xffffffffa6fcca28ULL, + 0xffffffffa8624205ULL, + 0x0000000059848872ULL, /* 40 */ + 0x00000000571a005fULL, + 0xffffffffa0aaddc6ULL, + 0xffffffffae3455ebULL, + 0x000000000e9ebb1eULL, + 0x0000000000003333ULL, + 0xffffffffa862bdfaULL, + 0xffffffffa6fc35d7ULL, + 0xfffffffff1e6bbb0ULL, /* 48 */ + 0xffffffffff78339dULL, + 0x0000000008c8ee04ULL, + 0x0000000006566629ULL, + 0xffffffffa6fc88dcULL, + 0xffffffffa86200f1ULL, + 0x0000000000008e38ULL, + 0x000000000e9e0615ULL, + 0xffffffffff78cc62ULL, /* 56 */ + 0xfffffffff1e6444fULL, + 0x00000000065699d6ULL, + 0x0000000008c811fbULL, + 0xffffffffa862ff0eULL, + 0xffffffffa6fc7723ULL, + 0x000000000e9ef9eaULL, + 0x00000000000071c7ULL, + 0x0000000000002862ULL, /* 64 */ + 0x000000001190c4cfULL, + 0x000000007b7fdbbeULL, + 0xffffffff9204da99ULL, + 0x000000001190a13eULL, + 0x0000000000004d93ULL, + 0x000000006aef52e2ULL, + 0xffffffff839453c5ULL, + 0x000000007b7f4a13ULL, /* 72 */ + 0x000000006aefa6beULL, + 0x000000000000b9cfULL, + 0xffffffffe97bb8e8ULL, + 0xffffffff9204accaULL, + 0xffffffff83944067ULL, + 0xffffffffe97b5f16ULL, + 0x0000000000005e31ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CH(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CH(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cw.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cw.c new file mode 100644 index 0000000..f7110b3 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32cw.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32CW + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0x82F63B78"; + char *instruction_name = "CRC32CW"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x0000000000000000ULL, /* 0 */ + 0xffffffffb798b438ULL, + 0xffffffff91d3be47ULL, + 0x00000000264b0a7fULL, + 0x0000000070b16a3dULL, + 0xffffffffc729de05ULL, + 0x0000000063c5950aULL, + 0xffffffffd45d2132ULL, + 0xffffffffb798b438ULL, /* 8 */ + 0x0000000000000000ULL, + 0x00000000264b0a7fULL, + 0xffffffff91d3be47ULL, + 0xffffffffc729de05ULL, + 0x0000000070b16a3dULL, + 0xffffffffd45d2132ULL, + 0x0000000063c5950aULL, + 0xffffffff91d3be47ULL, /* 16 */ + 0x00000000264b0a7fULL, + 0x0000000000000000ULL, + 0xffffffffb798b438ULL, + 0xffffffffe162d47aULL, + 0x0000000056fa6042ULL, + 0xfffffffff2162b4dULL, + 0x00000000458e9f75ULL, + 0x00000000264b0a7fULL, /* 24 */ + 0xffffffff91d3be47ULL, + 0xffffffffb798b438ULL, + 0x0000000000000000ULL, + 0x0000000056fa6042ULL, + 0xffffffffe162d47aULL, + 0x00000000458e9f75ULL, + 0xfffffffff2162b4dULL, + 0x0000000070b16a3dULL, /* 32 */ + 0xffffffffc729de05ULL, + 0xffffffffe162d47aULL, + 0x0000000056fa6042ULL, + 0x0000000000000000ULL, + 0xffffffffb798b438ULL, + 0x000000001374ff37ULL, + 0xffffffffa4ec4b0fULL, + 0xffffffffc729de05ULL, /* 40 */ + 0x0000000070b16a3dULL, + 0x0000000056fa6042ULL, + 0xffffffffe162d47aULL, + 0xffffffffb798b438ULL, + 0x0000000000000000ULL, + 0xffffffffa4ec4b0fULL, + 0x000000001374ff37ULL, + 0x0000000063c5950aULL, /* 48 */ + 0xffffffffd45d2132ULL, + 0xfffffffff2162b4dULL, + 0x00000000458e9f75ULL, + 0x000000001374ff37ULL, + 0xffffffffa4ec4b0fULL, + 0x0000000000000000ULL, + 0xffffffffb798b438ULL, + 0xffffffffd45d2132ULL, /* 56 */ + 0x0000000063c5950aULL, + 0x00000000458e9f75ULL, + 0xfffffffff2162b4dULL, + 0xffffffffa4ec4b0fULL, + 0x000000001374ff37ULL, + 0xffffffffb798b438ULL, + 0x0000000000000000ULL, + 0x0000000000000000ULL, /* 64 */ + 0xffffffffea0755b2ULL, + 0x0000000008b188e6ULL, + 0xffffffffff3cc8d9ULL, + 0xffffffffea0755b2ULL, + 0x0000000000000000ULL, + 0xffffffffe2b6dd54ULL, + 0x00000000153b9d6bULL, + 0x0000000008b188e6ULL, /* 72 */ + 0xffffffffe2b6dd54ULL, + 0x0000000000000000ULL, + 0xfffffffff78d403fULL, + 0xffffffffff3cc8d9ULL, + 0x00000000153b9d6bULL, + 0xfffffffff78d403fULL, + 0x0000000000000000ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CW(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32CW(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32d.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32d.c new file mode 100644 index 0000000..e391be8 --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32d.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32D + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0xEDB88320"; + char *instruction_name = "CRC32D"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0xffffffffdebb20e3ULL, /* 0 */ + 0x0000000044660075ULL, + 0x000000001e20c2aeULL, + 0xffffffff84fde238ULL, + 0x00000000281d7ce7ULL, + 0xffffffffb2c05c71ULL, + 0xffffffffd660a024ULL, + 0x000000004cbd80b2ULL, + 0xffffffff9add2096ULL, /* 8 */ + 0x0000000000000000ULL, + 0x000000005a46c2dbULL, + 0xffffffffc09be24dULL, + 0x000000006c7b7c92ULL, + 0xfffffffff6a65c04ULL, + 0xffffffff9206a051ULL, + 0x0000000008db80c7ULL, + 0x000000005449dd0fULL, /* 16 */ + 0xffffffffce94fd99ULL, + 0xffffffff94d23f42ULL, + 0x000000000e0f1fd4ULL, + 0xffffffffa2ef810bULL, + 0x000000003832a19dULL, + 0x000000005c925dc8ULL, + 0xffffffffc64f7d5eULL, + 0x00000000102fdd7aULL, /* 24 */ + 0xffffffff8af2fdecULL, + 0xffffffffd0b43f37ULL, + 0x000000004a691fa1ULL, + 0xffffffffe689817eULL, + 0x000000007c54a1e8ULL, + 0x0000000018f45dbdULL, + 0xffffffff82297d2bULL, + 0xffffffffa7157447ULL, /* 32 */ + 0x000000003dc854d1ULL, + 0x00000000678e960aULL, + 0xfffffffffd53b69cULL, + 0x0000000051b32843ULL, + 0xffffffffcb6e08d5ULL, + 0xffffffffafcef480ULL, + 0x000000003513d416ULL, + 0xffffffffe3737432ULL, /* 40 */ + 0x0000000079ae54a4ULL, + 0x0000000023e8967fULL, + 0xffffffffb935b6e9ULL, + 0x0000000015d52836ULL, + 0xffffffff8f0808a0ULL, + 0xffffffffeba8f4f5ULL, + 0x000000007175d463ULL, + 0x000000007a6adc3eULL, /* 48 */ + 0xffffffffe0b7fca8ULL, + 0xffffffffbaf13e73ULL, + 0x00000000202c1ee5ULL, + 0xffffffff8ccc803aULL, + 0x000000001611a0acULL, + 0x0000000072b15cf9ULL, + 0xffffffffe86c7c6fULL, + 0x000000003e0cdc4bULL, /* 56 */ + 0xffffffffa4d1fcddULL, + 0xfffffffffe973e06ULL, + 0x00000000644a1e90ULL, + 0xffffffffc8aa804fULL, + 0x000000005277a0d9ULL, + 0x0000000036d75c8cULL, + 0xffffffffac0a7c1aULL, + 0xffffffffed857593ULL, /* 64 */ + 0xffffffffe0b6f95fULL, + 0x00000000253b462cULL, + 0xffffffffe15579b9ULL, + 0x0000000074897c83ULL, + 0x0000000079baf04fULL, + 0xffffffffbc374f3cULL, + 0x00000000785970a9ULL, + 0xffffffffa6bae0a9ULL, /* 72 */ + 0xffffffffab896c65ULL, + 0x000000006e04d316ULL, + 0xffffffffaa6aec83ULL, + 0x000000005ae171feULL, + 0x0000000057d2fd32ULL, + 0xffffffff925f4241ULL, + 0x0000000056317dd4ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32D(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32D(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32h.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32h.c new file mode 100644 index 0000000..100f02c --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32h.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32H + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0xEDB88320"; + char *instruction_name = "CRC32H"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x000000000000ffffULL, /* 0 */ + 0xffffffffbe2612ffULL, + 0xffffffffdccda6c0ULL, + 0x0000000062eb4bc0ULL, + 0x000000004bbbc8eaULL, + 0xfffffffff59d25eaULL, + 0x0000000022259ac0ULL, + 0xffffffff9c0377c0ULL, + 0xffffffffbe26ed00ULL, /* 8 */ + 0x0000000000000000ULL, + 0x0000000062ebb43fULL, + 0xffffffffdccd593fULL, + 0xfffffffff59dda15ULL, + 0x000000004bbb3715ULL, + 0xffffffff9c03883fULL, + 0x000000002225653fULL, + 0xffffffffdccdf395ULL, /* 16 */ + 0x0000000062eb1e95ULL, + 0x000000000000aaaaULL, + 0xffffffffbe2647aaULL, + 0xffffffff9776c480ULL, + 0x0000000029502980ULL, + 0xfffffffffee896aaULL, + 0x0000000040ce7baaULL, + 0x0000000062ebe16aULL, /* 24 */ + 0xffffffffdccd0c6aULL, + 0xffffffffbe26b855ULL, + 0x0000000000005555ULL, + 0x000000002950d67fULL, + 0xffffffff97763b7fULL, + 0x0000000040ce8455ULL, + 0xfffffffffee86955ULL, + 0x000000004bbbfbd9ULL, /* 32 */ + 0xfffffffff59d16d9ULL, + 0xffffffff9776a2e6ULL, + 0x0000000029504fe6ULL, + 0x000000000000ccccULL, + 0xffffffffbe2621ccULL, + 0x00000000699e9ee6ULL, + 0xffffffffd7b873e6ULL, + 0xfffffffff59de926ULL, /* 40 */ + 0x000000004bbb0426ULL, + 0x000000002950b019ULL, + 0xffffffff97765d19ULL, + 0xffffffffbe26de33ULL, + 0x0000000000003333ULL, + 0xffffffffd7b88c19ULL, + 0x00000000699e6119ULL, + 0x000000002225eb07ULL, /* 48 */ + 0xffffffff9c030607ULL, + 0xfffffffffee8b238ULL, + 0x0000000040ce5f38ULL, + 0x00000000699edc12ULL, + 0xffffffffd7b83112ULL, + 0x0000000000008e38ULL, + 0xffffffffbe266338ULL, + 0xffffffff9c03f9f8ULL, /* 56 */ + 0x00000000222514f8ULL, + 0x0000000040cea0c7ULL, + 0xfffffffffee84dc7ULL, + 0xffffffffd7b8ceedULL, + 0x00000000699e23edULL, + 0xffffffffbe269cc7ULL, + 0x00000000000071c7ULL, + 0x0000000000002862ULL, /* 64 */ + 0x0000000026a17af6ULL, + 0xffffffffaa919152ULL, + 0xffffffffcb865590ULL, + 0x0000000026a11f07ULL, + 0x0000000000004d93ULL, + 0xffffffff8c30a637ULL, + 0xffffffffed2762f5ULL, + 0xffffffffaa9100ffULL, /* 72 */ + 0xffffffff8c30526bULL, + 0x000000000000b9cfULL, + 0x0000000061177d0dULL, + 0xffffffffcb8623c3ULL, + 0xffffffffed277157ULL, + 0x0000000061179af3ULL, + 0x0000000000005e31ULL + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32H(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32H(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32w.c b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32w.c new file mode 100644 index 0000000..b4f5f4b --- /dev/null +++ b/tests/tcg/mips/user/isa/mips64r6/crc/test_mips64r6_crc32w.c @@ -0,0 +1,142 @@ +/* + * Test program for MIPS64R6 instruction CRC32W + * + * Copyright (C) 2019 Wave Computing, Inc. + * Copyright (C) 2019 Aleksandar Markovic <amarkovic@wavecomp.com> + * Copyright (C) 2025 Aleksandar Rakic <aleksandar.rakic@htecgroup.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <sys/time.h> +#include <stdint.h> + +#include "../../../../include/wrappers_mips64r6.h" +#include "../../../../include/test_inputs_64.h" +#include "../../../../include/test_utils_64.h" + +#define TEST_COUNT_TOTAL (PATTERN_INPUTS_64_COUNT + RANDOM_INPUTS_64_COUNT) + +int32_t main(void) +{ + char *isa_ase_name = "mips64r6"; + char *group_name = "CRC with reversed polynomial 0xEDB88320"; + char *instruction_name = "CRC32W"; + int32_t ret; + uint32_t i, j; + struct timeval start, end; + double elapsed_time; + + uint64_t b64_result[TEST_COUNT_TOTAL]; + uint64_t b64_expect[TEST_COUNT_TOTAL] = { + 0x0000000000000000ULL, /* 0 */ + 0xffffffffdebb20e3ULL, + 0x000000004a691fa1ULL, + 0xffffffff94d23f42ULL, + 0xffffffff8f0808a0ULL, + 0x0000000051b32843ULL, + 0x0000000065069dceULL, + 0xffffffffbbbdbd2dULL, + 0xffffffffdebb20e3ULL, /* 8 */ + 0x0000000000000000ULL, + 0xffffffff94d23f42ULL, + 0x000000004a691fa1ULL, + 0x0000000051b32843ULL, + 0xffffffff8f0808a0ULL, + 0xffffffffbbbdbd2dULL, + 0x0000000065069dceULL, + 0x000000004a691fa1ULL, /* 16 */ + 0xffffffff94d23f42ULL, + 0x0000000000000000ULL, + 0xffffffffdebb20e3ULL, + 0xffffffffc5611701ULL, + 0x000000001bda37e2ULL, + 0x000000002f6f826fULL, + 0xfffffffff1d4a28cULL, + 0xffffffff94d23f42ULL, /* 24 */ + 0x000000004a691fa1ULL, + 0xffffffffdebb20e3ULL, + 0x0000000000000000ULL, + 0x000000001bda37e2ULL, + 0xffffffffc5611701ULL, + 0xfffffffff1d4a28cULL, + 0x000000002f6f826fULL, + 0xffffffff8f0808a0ULL, /* 32 */ + 0x0000000051b32843ULL, + 0xffffffffc5611701ULL, + 0x000000001bda37e2ULL, + 0x0000000000000000ULL, + 0xffffffffdebb20e3ULL, + 0xffffffffea0e956eULL, + 0x0000000034b5b58dULL, + 0x0000000051b32843ULL, /* 40 */ + 0xffffffff8f0808a0ULL, + 0x000000001bda37e2ULL, + 0xffffffffc5611701ULL, + 0xffffffffdebb20e3ULL, + 0x0000000000000000ULL, + 0x0000000034b5b58dULL, + 0xffffffffea0e956eULL, + 0x0000000065069dceULL, /* 48 */ + 0xffffffffbbbdbd2dULL, + 0x000000002f6f826fULL, + 0xfffffffff1d4a28cULL, + 0xffffffffea0e956eULL, + 0x0000000034b5b58dULL, + 0x0000000000000000ULL, + 0xffffffffdebb20e3ULL, + 0xffffffffbbbdbd2dULL, /* 56 */ + 0x0000000065069dceULL, + 0xfffffffff1d4a28cULL, + 0x000000002f6f826fULL, + 0x0000000034b5b58dULL, + 0xffffffffea0e956eULL, + 0xffffffffdebb20e3ULL, + 0x0000000000000000ULL, + 0x0000000000000000ULL, /* 64 */ + 0xffffffff90485967ULL, + 0x000000006dfb974aULL, + 0x00000000083e4538ULL, + 0xffffffff90485967ULL, + 0x0000000000000000ULL, + 0xfffffffffdb3ce2dULL, + 0xffffffff98761c5fULL, + 0x000000006dfb974aULL, /* 72 */ + 0xfffffffffdb3ce2dULL, + 0x0000000000000000ULL, + 0x0000000065c5d272ULL, + 0x00000000083e4538ULL, + 0xffffffff98761c5fULL, + 0x0000000065c5d272ULL, + 0x0000000000000000ULL, + }; + + gettimeofday(&start, NULL); + + for (i = 0; i < PATTERN_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < PATTERN_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32W(b64_pattern + i, b64_pattern + j, + b64_result + (PATTERN_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + for (i = 0; i < RANDOM_INPUTS_64_SHORT_COUNT; i++) { + for (j = 0; j < RANDOM_INPUTS_64_SHORT_COUNT; j++) { + do_mips64r6_CRC32W(b64_random + i, b64_random + j, + b64_result + (((PATTERN_INPUTS_64_SHORT_COUNT) * + (PATTERN_INPUTS_64_SHORT_COUNT)) + + RANDOM_INPUTS_64_SHORT_COUNT * i + j)); + } + } + + gettimeofday(&end, NULL); + + elapsed_time = (end.tv_sec - start.tv_sec) * 1000.0; + elapsed_time += (end.tv_usec - start.tv_usec) / 1000.0; + + ret = check_results_64(isa_ase_name, group_name, instruction_name, + TEST_COUNT_TOTAL, elapsed_time, b64_result, + b64_expect); + + return ret; +} diff --git a/tests/tcg/plugins/mem.c b/tests/tcg/plugins/mem.c index ca4e888..9649bce 100644 --- a/tests/tcg/plugins/mem.c +++ b/tests/tcg/plugins/mem.c @@ -20,6 +20,7 @@ * few things provided by compiler.h. */ #include <compiler.h> +#include <stdbool.h> #include <bswap.h> #include <qemu-plugin.h> diff --git a/tests/unit/test-bdrv-drain.c b/tests/unit/test-bdrv-drain.c index 59c2793..43b0ba8 100644 --- a/tests/unit/test-bdrv-drain.c +++ b/tests/unit/test-bdrv-drain.c @@ -193,7 +193,9 @@ static BlockBackend * no_coroutine_fn test_setup(void) blk_insert_bs(blk, bs, &error_abort); backing = bdrv_new_open_driver(&bdrv_test, "backing", 0, &error_abort); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(bs, backing, &error_abort); + bdrv_graph_wrunlock(); bdrv_unref(backing); bdrv_unref(bs); @@ -386,7 +388,9 @@ static void test_nested(void) backing = bdrv_new_open_driver(&bdrv_test, "backing", 0, &error_abort); backing_s = backing->opaque; + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(bs, backing, &error_abort); + bdrv_graph_wrunlock(); for (outer = 0; outer < DRAIN_TYPE_MAX; outer++) { for (inner = 0; inner < DRAIN_TYPE_MAX; inner++) { @@ -733,10 +737,12 @@ static void test_blockjob_common_drain_node(enum drain_type drain_type, src_overlay = bdrv_new_open_driver(&bdrv_test, "source-overlay", BDRV_O_RDWR, &error_abort); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(src_overlay, src, &error_abort); bdrv_unref(src); bdrv_set_backing_hd(src, src_backing, &error_abort); bdrv_unref(src_backing); + bdrv_graph_wrunlock(); blk_src = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); blk_insert_bs(blk_src, src_overlay, &error_abort); @@ -772,11 +778,9 @@ static void test_blockjob_common_drain_node(enum drain_type drain_type, tjob->bs = src; job = &tjob->common; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); block_job_add_bdrv(job, "target", target, 0, BLK_PERM_ALL, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); switch (result) { case TEST_JOB_SUCCESS: @@ -955,13 +959,11 @@ static void bdrv_test_top_close(BlockDriverState *bs) { BdrvChild *c, *next_c; - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) { bdrv_unref_child(bs, c); } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } static int coroutine_fn GRAPH_RDLOCK @@ -1053,12 +1055,10 @@ static void do_test_delete_by_drain(bool detach_instead_of_delete, null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL, &error_abort); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(bs, null_bs, "null-child", &child_of_bds, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); /* This child will be the one to pass to requests through to, and * it will stall until a drain occurs */ @@ -1066,25 +1066,21 @@ static void do_test_delete_by_drain(bool detach_instead_of_delete, &error_abort); child_bs->total_sectors = 65536 >> BDRV_SECTOR_BITS; /* Takes our reference to child_bs */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); tts->wait_child = bdrv_attach_child(bs, child_bs, "wait-child", &child_of_bds, BDRV_CHILD_DATA | BDRV_CHILD_PRIMARY, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); /* This child is just there to be deleted * (for detach_instead_of_delete == true) */ null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL, &error_abort); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(bs, null_bs, "null-child", &child_of_bds, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); blk_insert_bs(blk, bs, &error_abort); @@ -1167,8 +1163,7 @@ static void no_coroutine_fn detach_indirect_bh(void *opaque) bdrv_dec_in_flight(data->child_b->bs); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_unref_child(data->parent_b, data->child_b); bdrv_ref(data->c); @@ -1176,7 +1171,6 @@ static void no_coroutine_fn detach_indirect_bh(void *opaque) &child_of_bds, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); } static void coroutine_mixed_fn detach_by_parent_aio_cb(void *opaque, int ret) @@ -1274,8 +1268,7 @@ static void TSA_NO_TSA test_detach_indirect(bool by_parent_cb) /* Set child relationships */ bdrv_ref(b); bdrv_ref(a); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); child_b = bdrv_attach_child(parent_b, b, "PB-B", &child_of_bds, BDRV_CHILD_DATA, &error_abort); child_a = bdrv_attach_child(parent_b, a, "PB-A", &child_of_bds, @@ -1286,7 +1279,6 @@ static void TSA_NO_TSA test_detach_indirect(bool by_parent_cb) by_parent_cb ? &child_of_bds : &detach_by_driver_cb_class, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); g_assert_cmpint(parent_a->refcnt, ==, 1); g_assert_cmpint(parent_b->refcnt, ==, 1); @@ -1450,8 +1442,10 @@ static void test_drop_backing_job_commit(Job *job) TestDropBackingBlockJob *s = container_of(job, TestDropBackingBlockJob, common.job); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(s->bs, NULL, &error_abort); bdrv_set_backing_hd(s->detach_also, NULL, &error_abort); + bdrv_graph_wrunlock(); *s->did_complete = true; } @@ -1544,7 +1538,9 @@ static void test_blockjob_commit_by_drained_end(void) snprintf(name, sizeof(name), "parent-node-%i", i); bs_parents[i] = bdrv_new_open_driver(&bdrv_test, name, BDRV_O_RDWR, &error_abort); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(bs_parents[i], bs_child, &error_abort); + bdrv_graph_wrunlock(); } job = block_job_create("job", &test_drop_backing_job_driver, NULL, @@ -1693,14 +1689,13 @@ static void test_drop_intermediate_poll(void) job_node = bdrv_new_open_driver(&bdrv_test, "job-node", BDRV_O_RDWR, &error_abort); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(job_node, chain[1], &error_abort); /* * Establish the chain last, so the chain links are the first * elements in the BDS.parents lists */ - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); for (i = 0; i < 3; i++) { if (i) { /* Takes the reference to chain[i - 1] */ @@ -1709,7 +1704,6 @@ static void test_drop_intermediate_poll(void) } } bdrv_graph_wrunlock(); - bdrv_drain_all_end(); job = block_job_create("job", &test_simple_job_driver, NULL, job_node, 0, BLK_PERM_ALL, 0, 0, NULL, NULL, &error_abort); @@ -1956,12 +1950,10 @@ static void do_test_replace_child_mid_drain(int old_drain_count, new_child_bs->total_sectors = 1; bdrv_ref(old_child_bs); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(parent_bs, old_child_bs, "child", &child_of_bds, BDRV_CHILD_COW, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); parent_s->setup_completed = true; for (i = 0; i < old_drain_count; i++) { diff --git a/tests/unit/test-bdrv-graph-mod.c b/tests/unit/test-bdrv-graph-mod.c index 7b03ebe..567db99 100644 --- a/tests/unit/test-bdrv-graph-mod.c +++ b/tests/unit/test-bdrv-graph-mod.c @@ -137,12 +137,10 @@ static void test_update_perm_tree(void) blk_insert_bs(root, bs, &error_abort); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(filter, bs, "child", &child_of_bds, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); ret = bdrv_append(filter, bs, NULL); g_assert_cmpint(ret, <, 0); @@ -204,15 +202,13 @@ static void test_should_update_child(void) blk_insert_bs(root, bs, &error_abort); + bdrv_graph_wrlock_drained(); bdrv_set_backing_hd(target, bs, &error_abort); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); g_assert(target->backing->bs == bs); bdrv_attach_child(filter, target, "target", &child_of_bds, BDRV_CHILD_DATA, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_append(filter, bs, &error_abort); bdrv_graph_rdlock_main_loop(); @@ -248,8 +244,7 @@ static void test_parallel_exclusive_write(void) bdrv_ref(base); bdrv_ref(fl1); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(top, fl1, "backing", &child_of_bds, BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort); @@ -262,7 +257,6 @@ static void test_parallel_exclusive_write(void) bdrv_replace_node(fl1, fl2, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_drained_end(fl2); bdrv_drained_end(fl1); @@ -369,8 +363,7 @@ static void test_parallel_perm_update(void) */ bdrv_ref(base); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(top, ws, "file", &child_of_bds, BDRV_CHILD_DATA, &error_abort); c_fl1 = bdrv_attach_child(ws, fl1, "first", &child_of_bds, @@ -384,7 +377,6 @@ static void test_parallel_perm_update(void) BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); /* Select fl1 as first child to be active */ s->selected = c_fl1; @@ -438,13 +430,11 @@ static void test_append_greedy_filter(void) BlockDriverState *base = no_perm_node("base"); BlockDriverState *fl = exclusive_writer_node("fl1"); - bdrv_drain_all_begin(); - bdrv_graph_wrlock(); + bdrv_graph_wrlock_drained(); bdrv_attach_child(top, base, "backing", &child_of_bds, BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, &error_abort); bdrv_graph_wrunlock(); - bdrv_drain_all_end(); bdrv_append(fl, base, &error_abort); bdrv_unref(fl); diff --git a/tests/vm/freebsd b/tests/vm/freebsd index 74b3b1e..2e96c9e 100755 --- a/tests/vm/freebsd +++ b/tests/vm/freebsd @@ -28,8 +28,8 @@ class FreeBSDVM(basevm.BaseVM): name = "freebsd" arch = "x86_64" - link = "https://download.freebsd.org/releases/CI-IMAGES/14.1-RELEASE/amd64/Latest/FreeBSD-14.1-RELEASE-amd64-BASIC-CI.raw.xz" - csum = "202fe27a05427f0a86d3ebb97712745186f2776ccc4f70d95466dd99a0238ba5" + link = "https://download.freebsd.org/releases/CI-IMAGES/14.3-RELEASE/amd64/Latest/FreeBSD-14.3-RELEASE-amd64-BASIC-CI.raw.xz" + csum = "ec0f5a4bbe63aa50a725d9fee0f1931f850e9a21cbebdadb991df00f168d6805" size = "20G" BUILD_SCRIPT = """ diff --git a/ui/console-gl.c b/ui/console-gl.c index 103b954..403fc36 100644 --- a/ui/console-gl.c +++ b/ui/console-gl.c @@ -25,6 +25,7 @@ * THE SOFTWARE. */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "ui/console.h" #include "ui/shader.h" @@ -96,6 +97,53 @@ void surface_gl_create_texture(QemuGLShader *gls, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } +bool surface_gl_create_texture_from_fd(DisplaySurface *surface, + int fd, GLuint *texture, + GLuint *mem_obj) +{ + unsigned long size = surface_stride(surface) * surface_height(surface); + GLenum err = glGetError(); + *texture = 0; + *mem_obj = 0; + + if (!epoxy_has_gl_extension("GL_EXT_memory_object") || + !epoxy_has_gl_extension("GL_EXT_memory_object_fd")) { + error_report("spice: required OpenGL extensions not supported: " + "GL_EXT_memory_object and GL_EXT_memory_object_fd"); + return false; + } + +#ifdef GL_EXT_memory_object_fd + glCreateMemoryObjectsEXT(1, mem_obj); + glImportMemoryFdEXT(*mem_obj, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); + + err = glGetError(); + if (err != GL_NO_ERROR) { + error_report("spice: cannot import memory object from fd"); + goto cleanup_mem; + } + + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT); + glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, surface_width(surface), + surface_height(surface), *mem_obj, 0); + err = glGetError(); + if (err != GL_NO_ERROR) { + error_report("spice: cannot create texture from memory object"); + goto cleanup_tex_and_mem; + } + return true; + +cleanup_tex_and_mem: + glDeleteTextures(1, texture); +cleanup_mem: + glDeleteMemoryObjectsEXT(1, mem_obj); + +#endif + return false; +} + void surface_gl_update_texture(QemuGLShader *gls, DisplaySurface *surface, int x, int y, int w, int h) @@ -136,6 +184,12 @@ void surface_gl_destroy_texture(QemuGLShader *gls, } glDeleteTextures(1, &surface->texture); surface->texture = 0; +#ifdef GL_EXT_memory_object_fd + if (surface->mem_obj) { + glDeleteMemoryObjectsEXT(1, &surface->mem_obj); + surface->mem_obj = 0; + } +#endif } void surface_gl_setup_viewport(QemuGLShader *gls, diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index 5503a79..e3f2872 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -295,6 +295,7 @@ bool egl_dmabuf_export_texture(uint32_t tex_id, int *fd, EGLint *offset, { EGLImageKHR image; EGLuint64KHR modifiers[DMABUF_MAX_PLANES]; + int i; image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR, @@ -314,6 +315,11 @@ bool egl_dmabuf_export_texture(uint32_t tex_id, int *fd, EGLint *offset, *modifier = modifiers[0]; } + for (i = 0; i < *num_planes; i++) { + if (fd[i] < 0) { + return false; + } + } return true; } @@ -67,6 +67,7 @@ #define VC_TERM_X_MIN 80 #define VC_TERM_Y_MIN 25 #define VC_SCALE_MIN 0.25 +#define VC_SCALE_MAX 4 #define VC_SCALE_STEP 0.25 #ifdef GDK_WINDOWING_X11 @@ -272,15 +273,11 @@ static void gd_update_geometry_hints(VirtualConsole *vc) if (!vc->gfx.ds) { return; } - if (s->free_scale) { - geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN; - geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN; - mask |= GDK_HINT_MIN_SIZE; - } else { - geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x; - geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y; - mask |= GDK_HINT_MIN_SIZE; - } + double scale_x = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_x; + double scale_y = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_y; + geo.min_width = surface_width(vc->gfx.ds) * scale_x; + geo.min_height = surface_height(vc->gfx.ds) * scale_y; + mask |= GDK_HINT_MIN_SIZE; geo_widget = vc->gfx.drawing_area; gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height); @@ -828,8 +825,12 @@ void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh) sx = (double)ww / fbw; sy = (double)wh / fbh; - - vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy); + if (vc->s->keep_aspect_ratio) { + vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy); + } else { + vc->gfx.scale_x = sx; + vc->gfx.scale_y = sy; + } } } /** @@ -1575,8 +1576,8 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) } s->full_screen = FALSE; if (vc->type == GD_VC_GFX) { - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; gd_update_windowsize(vc); } } @@ -1632,8 +1633,8 @@ static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque) GtkDisplayState *s = opaque; VirtualConsole *vc = gd_vc_find_current(s); - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; gd_update_windowsize(vc); } @@ -1647,8 +1648,8 @@ static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque) s->free_scale = TRUE; } else { s->free_scale = FALSE; - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; } gd_update_windowsize(vc); @@ -2239,6 +2240,11 @@ static void gl_area_realize(GtkGLArea *area, VirtualConsole *vc) } #endif +static bool gd_scale_valid(double scale) +{ + return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX; +} + static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, QemuConsole *con, int idx, GSList *group, GtkWidget *view_menu) @@ -2248,8 +2254,18 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, vc->label = qemu_console_get_label(con); vc->s = s; - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.preferred_scale = 1.0; + if (s->opts->u.gtk.has_scale) { + if (gd_scale_valid(s->opts->u.gtk.scale)) { + vc->gfx.preferred_scale = s->opts->u.gtk.scale; + } else { + error_report("Invalid scale value %lf given, being ignored", + s->opts->u.gtk.scale); + s->opts->u.gtk.has_scale = false; + } + } + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; #if defined(CONFIG_OPENGL) if (display_opengl) { @@ -2328,6 +2344,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, s->free_scale = true; } + s->keep_aspect_ratio = true; + if (s->opts->u.gtk.has_keep_aspect_ratio) + s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio; + for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { struct touch_slot *slot = &touch_slots[i]; slot->tracking_id = -1; diff --git a/ui/spice-core.c b/ui/spice-core.c index 0326c63..5992f9d 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -56,6 +56,8 @@ struct SpiceTimer { QEMUTimer *timer; }; +#define DEFAULT_MAX_REFRESH_RATE 30 + static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) { SpiceTimer *timer; @@ -489,6 +491,12 @@ static QemuOptsList qemu_spice_opts = { .name = "streaming-video", .type = QEMU_OPT_STRING, },{ + .name = "video-codec", + .type = QEMU_OPT_STRING, + },{ + .name = "max-refresh-rate", + .type = QEMU_OPT_NUMBER, + },{ .name = "agent-mouse", .type = QEMU_OPT_BOOL, },{ @@ -801,6 +809,13 @@ static void qemu_spice_init(void) spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF); } + spice_max_refresh_rate = qemu_opt_get_number(opts, "max-refresh-rate", + DEFAULT_MAX_REFRESH_RATE); + if (spice_max_refresh_rate <= 0) { + error_report("max refresh rate/fps is invalid"); + exit(1); + } + spice_server_set_agent_mouse (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); spice_server_set_playback_compression @@ -836,9 +851,26 @@ static void qemu_spice_init(void) #ifdef HAVE_SPICE_GL if (qemu_opt_get_bool(opts, "gl", 0)) { if ((port != 0) || (tls_port != 0)) { +#if SPICE_SERVER_VERSION >= 0x000f03 /* release 0.15.3 */ + const char *video_codec = NULL; + g_autofree char *enc_codec = NULL; + + spice_remote_client = 1; + + video_codec = qemu_opt_get(opts, "video-codec"); + if (video_codec) { + enc_codec = g_strconcat("gstreamer:", video_codec, NULL); + } + if (spice_server_set_video_codecs(spice_server, + enc_codec ?: "gstreamer:h264")) { + error_report("invalid video codec"); + exit(1); + } +#else error_report("SPICE GL support is local-only for now and " "incompatible with -spice port/tls-port"); exit(1); +#endif } egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal); spice_opengl = 1; diff --git a/ui/spice-display.c b/ui/spice-display.c index 9c39d2c..9ce622c 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -31,6 +31,8 @@ #include "standard-headers/drm/drm_fourcc.h" bool spice_opengl; +bool spice_remote_client; +int spice_max_refresh_rate; int qemu_spice_rect_is_empty(const QXLRect* r) { @@ -843,12 +845,32 @@ static void qemu_spice_gl_block_timer(void *opaque) warn_report("spice: no gl-draw-done within one second"); } +static void spice_gl_draw(SimpleSpiceDisplay *ssd, + uint32_t x, uint32_t y, uint32_t w, uint32_t h) +{ + uint64_t cookie; + + cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); + spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie); +} + static void spice_gl_refresh(DisplayChangeListener *dcl) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); - uint64_t cookie; - if (!ssd->ds || qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (!ssd->ds) { + return; + } + + if (qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) { + glFlush(); + spice_gl_draw(ssd, 0, 0, + surface_width(ssd->ds), surface_height(ssd->ds)); + ssd->gl_updates = 0; + /* E.g, to achieve 60 FPS, update_interval needs to be ~16.66 ms */ + dcl->update_interval = 1000 / spice_max_refresh_rate; + } return; } @@ -856,11 +878,8 @@ static void spice_gl_refresh(DisplayChangeListener *dcl) if (ssd->gl_updates && ssd->have_surface) { qemu_spice_gl_block(ssd, true); glFlush(); - cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); - spice_qxl_gl_draw_async(&ssd->qxl, 0, 0, - surface_width(ssd->ds), - surface_height(ssd->ds), - cookie); + spice_gl_draw(ssd, 0, 0, + surface_width(ssd->ds), surface_height(ssd->ds)); ssd->gl_updates = 0; } } @@ -874,6 +893,81 @@ static void spice_gl_update(DisplayChangeListener *dcl, ssd->gl_updates++; } +static bool spice_gl_replace_fd_texture(SimpleSpiceDisplay *ssd, + int *fds, uint64_t *modifier, + int *num_planes) +{ + uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES]; + GLuint texture; + GLuint mem_obj; + int fourcc; + bool ret; + + if (!spice_remote_client) { + return true; + } + + if (*modifier == DRM_FORMAT_MOD_LINEAR) { + return true; + } + + if (*num_planes > 1) { + error_report("spice: cannot replace texture with multiple planes"); + return false; + } + + /* + * We really want to ensure that the memory layout of the texture + * is linear; otherwise, the encoder's output may show corruption. + */ + if (!surface_gl_create_texture_from_fd(ssd->ds, fds[0], &texture, + &mem_obj)) { + error_report("spice: cannot create new texture from fd"); + return false; + } + + /* + * A successful return after glImportMemoryFdEXT() means that + * the ownership of fd has been passed to GL. In other words, + * the fd we got above should not be used anymore. + */ + ret = egl_dmabuf_export_texture(texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + num_planes, + modifier); + if (!ret) { + glDeleteTextures(1, &texture); +#ifdef GL_EXT_memory_object_fd + glDeleteMemoryObjectsEXT(1, &mem_obj); +#endif + + /* + * Since we couldn't export our newly create texture (or create, + * an fd associated with it) we need to backtrack and try to + * recreate the fd associated with the original texture. + */ + ret = egl_dmabuf_export_texture(ssd->ds->texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + num_planes, + modifier); + if (!ret) { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + warn_report("spice: no texture available to display"); + } + } else { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + ssd->ds->texture = texture; + ssd->ds->mem_obj = mem_obj; + } + return ret; +} + static void spice_server_gl_scanout(QXLInstance *qxl, const int *fd, uint32_t width, uint32_t height, @@ -898,6 +992,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl, struct DisplaySurface *new_surface) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); + bool ret; if (ssd->ds) { surface_gl_destroy_texture(ssd->gls, ssd->ds); @@ -920,6 +1015,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl, return; } + ret = spice_gl_replace_fd_texture(ssd, fd, &modifier, &num_planes); + if (!ret) { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + return; + } + trace_qemu_spice_gl_surface(ssd->qxl.id, surface_width(ssd->ds), surface_height(ssd->ds), @@ -953,6 +1054,20 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl) SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); trace_qemu_spice_gl_scanout_disable(ssd->qxl.id); + + /* + * We need to check for the case of "lost" updates, where a gl_draw + * was not submitted because the timer did not get a chance to run. + * One case where this happens is when the Guest VM is getting + * rebooted. If the console is blocked in this situation, we need + * to unblock it. Otherwise, newer updates would not take effect. + */ + if (qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) { + ssd->gl_updates = 0; + qemu_spice_gl_block(ssd, false); + } + } spice_server_gl_scanout(&ssd->qxl, NULL, 0, 0, NULL, NULL, 0, DRM_FORMAT_INVALID, DRM_FORMAT_MOD_INVALID, false); qemu_spice_gl_monitor_config(ssd, 0, 0, 0, 0); @@ -971,7 +1086,7 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl, { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); EGLint offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES], fourcc = 0; - int fd[DMABUF_MAX_PLANES], num_planes; + int fd[DMABUF_MAX_PLANES], num_planes, i; uint64_t modifier; assert(tex_id); @@ -983,11 +1098,26 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl, trace_qemu_spice_gl_scanout_texture(ssd->qxl.id, w, h, fourcc); - /* note: spice server will close the fd */ - spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height, - (uint32_t *)offset, (uint32_t *)stride, num_planes, - fourcc, modifier, y_0_top); - qemu_spice_gl_monitor_config(ssd, x, y, w, h); + if (spice_remote_client && modifier != DRM_FORMAT_MOD_LINEAR) { + egl_fb_destroy(&ssd->guest_fb); + egl_fb_setup_for_tex(&ssd->guest_fb, + backing_width, backing_height, + tex_id, false); + ssd->backing_y_0_top = y_0_top; + ssd->blit_scanout_texture = true; + ssd->new_scanout_texture = true; + + for (i = 0; i < num_planes; i++) { + close(fd[i]); + } + } else { + /* note: spice server will close the fd */ + spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height, + (uint32_t *)offset, (uint32_t *)stride, + num_planes, fourcc, modifier, y_0_top); + qemu_spice_gl_monitor_config(ssd, x, y, w, h); + } + ssd->have_surface = false; ssd->have_scanout = true; } @@ -1053,6 +1183,50 @@ static void qemu_spice_gl_release_dmabuf(DisplayChangeListener *dcl, egl_dmabuf_release_texture(dmabuf); } +static bool spice_gl_blit_scanout_texture(SimpleSpiceDisplay *ssd, + egl_fb *scanout_tex_fb) +{ + uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES]; + int fds[DMABUF_MAX_PLANES], num_planes, fourcc; + uint64_t modifier; + bool ret; + + egl_fb_destroy(scanout_tex_fb); + egl_fb_setup_for_tex(scanout_tex_fb, + surface_width(ssd->ds), surface_height(ssd->ds), + ssd->ds->texture, false); + egl_fb_blit(scanout_tex_fb, &ssd->guest_fb, false); + glFlush(); + + if (!ssd->new_scanout_texture) { + return true; + } + + ret = egl_dmabuf_export_texture(ssd->ds->texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + &num_planes, + &modifier); + if (!ret) { + error_report("spice: failed to get fd for texture"); + return false; + } + + spice_server_gl_scanout(&ssd->qxl, fds, + surface_width(ssd->ds), + surface_height(ssd->ds), + (uint32_t *)offsets, (uint32_t *)strides, + num_planes, fourcc, modifier, + ssd->backing_y_0_top); + qemu_spice_gl_monitor_config(ssd, 0, 0, + surface_width(ssd->ds), + surface_height(ssd->ds)); + ssd->new_scanout_texture = false; + return true; +} + static void qemu_spice_gl_update(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { @@ -1060,7 +1234,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl, EGLint fourcc = 0; bool render_cursor = false; bool y_0_top = false; /* FIXME */ - uint64_t cookie; + bool ret; uint32_t width, height, texture; if (!ssd->have_scanout) { @@ -1155,11 +1329,31 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl, glFlush(); } + if (spice_remote_client && ssd->blit_scanout_texture) { + egl_fb scanout_tex_fb; + + ret = spice_gl_blit_scanout_texture(ssd, &scanout_tex_fb); + if (!ret) { + return; + } + } + trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y); qemu_spice_gl_block(ssd, true); glFlush(); - cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); - spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie); + + /* + * In the case of remote clients, the submission of gl_draw request is + * deferred here, so that it can be submitted later (to spice server) + * from spice_gl_refresh() timer callback. This is done to ensure that + * Guest updates are submitted at a steady rate (e.g. 60 FPS) instead + * of submitting them arbitrarily. + */ + if (spice_remote_client) { + ssd->gl_updates++; + } else { + spice_gl_draw(ssd, x, y, w, h); + } } static const DisplayChangeListenerOps display_listener_gl_ops = { diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c index 25c7b2c..9dfe6ae 100644 --- a/ui/vnc-enc-tight.c +++ b/ui/vnc-enc-tight.c @@ -72,8 +72,8 @@ static const struct { }; -static int tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h); +static int tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); #ifdef CONFIG_VNC_JPEG static const struct { @@ -111,12 +111,12 @@ static const struct { { 9, PNG_ALL_FILTERS }, }; -static int send_png_rect(VncState *vs, int x, int y, int w, int h, - VncPalette *palette); +static int send_png_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, VncPalette *palette); -static bool tight_can_send_png_rect(VncState *vs, int w, int h) +static bool tight_can_send_png_rect(VncState *vs, VncTight *tight, int w, int h) { - if (vs->tight->type != VNC_ENCODING_TIGHT_PNG) { + if (tight->type != VNC_ENCODING_TIGHT_PNG) { return false; } @@ -135,7 +135,7 @@ static bool tight_can_send_png_rect(VncState *vs, int w, int h) */ static unsigned int -tight_detect_smooth_image24(VncState *vs, int w, int h) +tight_detect_smooth_image24(VncState *vs, VncTight *tight, int w, int h) { int off; int x, y, d, dx; @@ -144,7 +144,7 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) int pixels = 0; int pix, left[3]; unsigned int errors; - unsigned char *buf = vs->tight->tight.buffer; + unsigned char *buf = tight->tight.buffer; /* * If client is big-endian, color samples begin from the second @@ -205,7 +205,8 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) #define DEFINE_DETECT_FUNCTION(bpp) \ \ static unsigned int \ - tight_detect_smooth_image##bpp(VncState *vs, int w, int h) { \ + tight_detect_smooth_image##bpp(VncState *vs, VncTight *tight, \ + int w, int h) { \ bool endian; \ uint##bpp##_t pix; \ int max[3], shift[3]; \ @@ -215,7 +216,7 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) int pixels = 0; \ int sample, sum, left[3]; \ unsigned int errors; \ - unsigned char *buf = vs->tight->tight.buffer; \ + unsigned char *buf = tight->tight.buffer; \ \ endian = 0; /* FIXME */ \ \ @@ -293,11 +294,11 @@ DEFINE_DETECT_FUNCTION(16) DEFINE_DETECT_FUNCTION(32) static int -tight_detect_smooth_image(VncState *vs, int w, int h) +tight_detect_smooth_image(VncState *vs, VncTight *tight, int w, int h) { unsigned int errors; - int compression = vs->tight->compression; - int quality = vs->tight->quality; + int compression = tight->compression; + int quality = tight->quality; if (!vs->vd->lossy) { return 0; @@ -309,7 +310,7 @@ tight_detect_smooth_image(VncState *vs, int w, int h) return 0; } - if (vs->tight->quality != (uint8_t)-1) { + if (tight->quality != (uint8_t)-1) { if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) { return 0; } @@ -320,17 +321,17 @@ tight_detect_smooth_image(VncState *vs, int w, int h) } if (vs->client_pf.bytes_per_pixel == 4) { - if (vs->tight->pixel24) { - errors = tight_detect_smooth_image24(vs, w, h); - if (vs->tight->quality != (uint8_t)-1) { + if (tight->pixel24) { + errors = tight_detect_smooth_image24(vs, tight, w, h); + if (tight->quality != (uint8_t)-1) { return (errors < tight_conf[quality].jpeg_threshold24); } return (errors < tight_conf[compression].gradient_threshold24); } else { - errors = tight_detect_smooth_image32(vs, w, h); + errors = tight_detect_smooth_image32(vs, tight, w, h); } } else { - errors = tight_detect_smooth_image16(vs, w, h); + errors = tight_detect_smooth_image16(vs, tight, w, h); } if (quality != (uint8_t)-1) { return (errors < tight_conf[quality].jpeg_threshold); @@ -344,15 +345,15 @@ tight_detect_smooth_image(VncState *vs, int w, int h) #define DEFINE_FILL_PALETTE_FUNCTION(bpp) \ \ static int \ - tight_fill_palette##bpp(VncState *vs, int x, int y, \ - int max, size_t count, \ + tight_fill_palette##bpp(VncState *vs, VncTight *tight, \ + int x, int y, int max, size_t count, \ uint32_t *bg, uint32_t *fg, \ VncPalette *palette) { \ uint##bpp##_t *data; \ uint##bpp##_t c0, c1, ci; \ int i, n0, n1; \ \ - data = (uint##bpp##_t *)vs->tight->tight.buffer; \ + data = (uint##bpp##_t *)tight->tight.buffer; \ \ c0 = data[0]; \ i = 1; \ @@ -417,15 +418,15 @@ DEFINE_FILL_PALETTE_FUNCTION(8) DEFINE_FILL_PALETTE_FUNCTION(16) DEFINE_FILL_PALETTE_FUNCTION(32) -static int tight_fill_palette(VncState *vs, int x, int y, +static int tight_fill_palette(VncState *vs, VncTight *tight, int x, int y, size_t count, uint32_t *bg, uint32_t *fg, VncPalette *palette) { int max; - max = count / tight_conf[vs->tight->compression].idx_max_colors_divisor; + max = count / tight_conf[tight->compression].idx_max_colors_divisor; if (max < 2 && - count >= tight_conf[vs->tight->compression].mono_min_rect_size) { + count >= tight_conf[tight->compression].mono_min_rect_size) { max = 2; } if (max >= 256) { @@ -434,12 +435,15 @@ static int tight_fill_palette(VncState *vs, int x, int y, switch (vs->client_pf.bytes_per_pixel) { case 4: - return tight_fill_palette32(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette32(vs, tight, x, y, max, count, bg, fg, + palette); case 2: - return tight_fill_palette16(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette16(vs, tight, x, y, max, count, bg, fg, + palette); default: max = 2; - return tight_fill_palette8(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette8(vs, tight, x, y, max, count, bg, fg, + palette); } return 0; } @@ -547,7 +551,8 @@ DEFINE_MONO_ENCODE_FUNCTION(32) */ static void -tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) +tight_filter_gradient24(VncState *vs, VncTight *tight, uint8_t *buf, + int w, int h) { uint32_t *buf32; uint32_t pix32; @@ -558,7 +563,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) int x, y, c; buf32 = (uint32_t *)buf; - memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); + memset(tight->gradient.buffer, 0, w * 3 * sizeof(int)); if (1 /* FIXME */) { shift[0] = vs->client_pf.rshift; @@ -575,7 +580,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) upper[c] = 0; here[c] = 0; } - prev = (int *)vs->tight->gradient.buffer; + prev = (int *)tight->gradient.buffer; for (x = 0; x < w; x++) { pix32 = *buf32++; for (c = 0; c < 3; c++) { @@ -605,8 +610,8 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) #define DEFINE_GRADIENT_FILTER_FUNCTION(bpp) \ \ static void \ - tight_filter_gradient##bpp(VncState *vs, uint##bpp##_t *buf, \ - int w, int h) { \ + tight_filter_gradient##bpp(VncState *vs, VncTight *tight, \ + uint##bpp##_t *buf, int w, int h) { \ uint##bpp##_t pix, diff; \ bool endian; \ int *prev; \ @@ -615,7 +620,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) int prediction; \ int x, y, c; \ \ - memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); \ + memset(tight->gradient.buffer, 0, w * 3 * sizeof(int)); \ \ endian = 0; /* FIXME */ \ \ @@ -631,7 +636,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) upper[c] = 0; \ here[c] = 0; \ } \ - prev = (int *)vs->tight->gradient.buffer; \ + prev = (int *)tight->gradient.buffer; \ for (x = 0; x < w; x++) { \ pix = *buf; \ if (endian) { \ @@ -782,10 +787,10 @@ static void extend_solid_area(VncState *vs, int x, int y, int w, int h, *w_ptr += cx - (*x_ptr + *w_ptr); } -static int tight_init_stream(VncState *vs, int stream_id, +static int tight_init_stream(VncState *vs, VncTight *tight, int stream_id, int level, int strategy) { - z_streamp zstream = &vs->tight->stream[stream_id]; + z_streamp zstream = &tight->stream[stream_id]; if (zstream->opaque == NULL) { int err; @@ -803,15 +808,15 @@ static int tight_init_stream(VncState *vs, int stream_id, return -1; } - vs->tight->levels[stream_id] = level; + tight->levels[stream_id] = level; zstream->opaque = vs; } - if (vs->tight->levels[stream_id] != level) { + if (tight->levels[stream_id] != level) { if (deflateParams(zstream, level, strategy) != Z_OK) { return -1; } - vs->tight->levels[stream_id] = level; + tight->levels[stream_id] = level; } return 0; } @@ -836,29 +841,29 @@ static void tight_send_compact_size(VncState *vs, size_t len) } } -static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, - int level, int strategy) +static int tight_compress_data(VncState *vs, VncTight *tight, int stream_id, + size_t bytes, int level, int strategy) { - z_streamp zstream = &vs->tight->stream[stream_id]; + z_streamp zstream = &tight->stream[stream_id]; int previous_out; if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) { - vnc_write(vs, vs->tight->tight.buffer, vs->tight->tight.offset); + vnc_write(vs, tight->tight.buffer, tight->tight.offset); return bytes; } - if (tight_init_stream(vs, stream_id, level, strategy)) { + if (tight_init_stream(vs, tight, stream_id, level, strategy)) { return -1; } /* reserve memory in output buffer */ - buffer_reserve(&vs->tight->zlib, bytes + 64); + buffer_reserve(&tight->zlib, bytes + 64); /* set pointers */ - zstream->next_in = vs->tight->tight.buffer; - zstream->avail_in = vs->tight->tight.offset; - zstream->next_out = vs->tight->zlib.buffer + vs->tight->zlib.offset; - zstream->avail_out = vs->tight->zlib.capacity - vs->tight->zlib.offset; + zstream->next_in = tight->tight.buffer; + zstream->avail_in = tight->tight.offset; + zstream->next_out = tight->zlib.buffer + tight->zlib.offset; + zstream->avail_out = tight->zlib.capacity - tight->zlib.offset; previous_out = zstream->avail_out; zstream->data_type = Z_BINARY; @@ -868,14 +873,14 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, return -1; } - vs->tight->zlib.offset = vs->tight->zlib.capacity - zstream->avail_out; + tight->zlib.offset = tight->zlib.capacity - zstream->avail_out; /* ...how much data has actually been produced by deflate() */ bytes = previous_out - zstream->avail_out; tight_send_compact_size(vs, bytes); - vnc_write(vs, vs->tight->zlib.buffer, bytes); + vnc_write(vs, tight->zlib.buffer, bytes); - buffer_reset(&vs->tight->zlib); + buffer_reset(&tight->zlib); return bytes; } @@ -914,67 +919,69 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret) } } -static int send_full_color_rect(VncState *vs, int x, int y, int w, int h) +static int send_full_color_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; + int level = tight_conf[tight->compression].raw_zlib_level; int stream = 0; ssize_t bytes; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { - return send_png_rect(vs, x, y, w, h, NULL); + if (tight_can_send_png_rect(vs, tight, w, h)) { + return send_png_rect(vs, worker, x, y, w, h, NULL); } #endif vnc_write_u8(vs, stream << 4); /* no flushing, no filter */ - if (vs->tight->pixel24) { - tight_pack24(vs, vs->tight->tight.buffer, w * h, - &vs->tight->tight.offset); + if (tight->pixel24) { + tight_pack24(vs, tight->tight.buffer, w * h, &tight->tight.offset); bytes = 3; } else { bytes = vs->client_pf.bytes_per_pixel; } - bytes = tight_compress_data(vs, stream, w * h * bytes, - tight_conf[vs->tight->compression].raw_zlib_level, + bytes = tight_compress_data(vs, tight, stream, w * h * bytes, level, Z_DEFAULT_STRATEGY); return (bytes >= 0); } -static int send_solid_rect(VncState *vs) +static int send_solid_rect(VncState *vs, VncWorker *worker) { + VncTight *tight = &worker->tight; size_t bytes; vnc_write_u8(vs, VNC_TIGHT_FILL << 4); /* no flushing, no filter */ - if (vs->tight->pixel24) { - tight_pack24(vs, vs->tight->tight.buffer, 1, &vs->tight->tight.offset); + if (tight->pixel24) { + tight_pack24(vs, tight->tight.buffer, 1, &tight->tight.offset); bytes = 3; } else { bytes = vs->client_pf.bytes_per_pixel; } - vnc_write(vs, vs->tight->tight.buffer, bytes); + vnc_write(vs, tight->tight.buffer, bytes); return 1; } -static int send_mono_rect(VncState *vs, int x, int y, +static int send_mono_rect(VncState *vs, VncWorker *worker, int x, int y, int w, int h, uint32_t bg, uint32_t fg) { ssize_t bytes; int stream = 1; - int level = tight_conf[vs->tight->compression].mono_zlib_level; + int level = tight_conf[worker->tight.compression].mono_zlib_level; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { + if (tight_can_send_png_rect(vs, &worker->tight, w, h)) { int ret; int bpp = vs->client_pf.bytes_per_pixel * 8; VncPalette *palette = palette_new(2, bpp); palette_put(palette, bg); palette_put(palette, fg); - ret = send_png_rect(vs, x, y, w, h, palette); + ret = send_png_rect(vs, worker, x, y, w, h, palette); palette_destroy(palette); return ret; } @@ -992,12 +999,12 @@ static int send_mono_rect(VncState *vs, int x, int y, uint32_t buf[2] = {bg, fg}; size_t ret = sizeof (buf); - if (vs->tight->pixel24) { + if (worker->tight.pixel24) { tight_pack24(vs, (unsigned char*)buf, 2, &ret); } vnc_write(vs, buf, ret); - tight_encode_mono_rect32(vs->tight->tight.buffer, w, h, bg, fg); + tight_encode_mono_rect32(worker->tight.tight.buffer, w, h, bg, fg); break; } case 2: @@ -1006,7 +1013,7 @@ static int send_mono_rect(VncState *vs, int x, int y, uint16_t fg16 = fg; vnc_write(vs, &bg16, 2); vnc_write(vs, &fg16, 2); - tight_encode_mono_rect16(vs->tight->tight.buffer, w, h, bg, fg); + tight_encode_mono_rect16(worker->tight.tight.buffer, w, h, bg, fg); break; } default: @@ -1015,18 +1022,20 @@ static int send_mono_rect(VncState *vs, int x, int y, uint8_t fg8 = fg; vnc_write_u8(vs, bg8); vnc_write_u8(vs, fg8); - tight_encode_mono_rect8(vs->tight->tight.buffer, w, h, bg, fg); + tight_encode_mono_rect8(worker->tight.tight.buffer, w, h, bg, fg); break; } } - vs->tight->tight.offset = bytes; + worker->tight.tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, level, Z_DEFAULT_STRATEGY); + bytes = tight_compress_data(vs, &worker->tight, stream, bytes, level, + Z_DEFAULT_STRATEGY); return (bytes >= 0); } struct palette_cb_priv { VncState *vs; + VncTight *tight; uint8_t *header; #ifdef CONFIG_PNG png_colorp png_palette; @@ -1046,53 +1055,58 @@ static void write_palette(int idx, uint32_t color, void *opaque) } } -static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h) +static bool send_gradient_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; int stream = 3; - int level = tight_conf[vs->tight->compression].gradient_zlib_level; + int level = tight_conf[tight->compression].gradient_zlib_level; ssize_t bytes; if (vs->client_pf.bytes_per_pixel == 1) { - return send_full_color_rect(vs, x, y, w, h); + return send_full_color_rect(vs, worker, x, y, w, h); } vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4); vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT); - buffer_reserve(&vs->tight->gradient, w * 3 * sizeof(int)); + buffer_reserve(&tight->gradient, w * 3 * sizeof(int)); - if (vs->tight->pixel24) { - tight_filter_gradient24(vs, vs->tight->tight.buffer, w, h); + if (tight->pixel24) { + tight_filter_gradient24(vs, tight, tight->tight.buffer, w, h); bytes = 3; } else if (vs->client_pf.bytes_per_pixel == 4) { - tight_filter_gradient32(vs, (uint32_t *)vs->tight->tight.buffer, w, h); + tight_filter_gradient32(vs, tight, (uint32_t *)tight->tight.buffer, + w, h); bytes = 4; } else { - tight_filter_gradient16(vs, (uint16_t *)vs->tight->tight.buffer, w, h); + tight_filter_gradient16(vs, tight, (uint16_t *)tight->tight.buffer, + w, h); bytes = 2; } - buffer_reset(&vs->tight->gradient); + buffer_reset(&tight->gradient); bytes = w * h * bytes; - vs->tight->tight.offset = bytes; + tight->tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, + bytes = tight_compress_data(vs, tight, stream, bytes, level, Z_FILTERED); return (bytes >= 0); } -static int send_palette_rect(VncState *vs, int x, int y, +static int send_palette_rect(VncState *vs, VncWorker *worker, int x, int y, int w, int h, VncPalette *palette) { + VncTight *tight = &worker->tight; int stream = 2; - int level = tight_conf[vs->tight->compression].idx_zlib_level; + int level = tight_conf[tight->compression].idx_zlib_level; int colors; ssize_t bytes; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { - return send_png_rect(vs, x, y, w, h, palette); + if (tight_can_send_png_rect(vs, tight, w, h)) { + return send_png_rect(vs, worker, x, y, w, h, palette); } #endif @@ -1107,38 +1121,38 @@ static int send_palette_rect(VncState *vs, int x, int y, { size_t old_offset, offset, palette_sz = palette_size(palette); g_autofree uint32_t *header = g_new(uint32_t, palette_sz); - struct palette_cb_priv priv = { vs, (uint8_t *)header }; + struct palette_cb_priv priv = { vs, tight, (uint8_t *)header }; old_offset = vs->output.offset; palette_iter(palette, write_palette, &priv); vnc_write(vs, header, palette_sz * sizeof(uint32_t)); - if (vs->tight->pixel24) { + if (tight->pixel24) { tight_pack24(vs, vs->output.buffer + old_offset, colors, &offset); vs->output.offset = old_offset + offset; } - tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, palette); + tight_encode_indexed_rect32(tight->tight.buffer, w * h, palette); break; } case 2: { size_t palette_sz = palette_size(palette); g_autofree uint16_t *header = g_new(uint16_t, palette_sz); - struct palette_cb_priv priv = { vs, (uint8_t *)header }; + struct palette_cb_priv priv = { vs, tight, (uint8_t *)header }; palette_iter(palette, write_palette, &priv); vnc_write(vs, header, palette_sz * sizeof(uint16_t)); - tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, palette); + tight_encode_indexed_rect16(tight->tight.buffer, w * h, palette); break; } default: return -1; /* No palette for 8bits colors */ } bytes = w * h; - vs->tight->tight.offset = bytes; + tight->tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, + bytes = tight_compress_data(vs, tight, stream, bytes, level, Z_DEFAULT_STRATEGY); return (bytes >= 0); } @@ -1154,8 +1168,8 @@ static int send_palette_rect(VncState *vs, int x, int y, /* This is called once per encoding */ static void jpeg_init_destination(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; cinfo->dest->next_output_byte = (JOCTET *)buffer->buffer + buffer->offset; cinfo->dest->free_in_buffer = (size_t)(buffer->capacity - buffer->offset); @@ -1164,8 +1178,8 @@ static void jpeg_init_destination(j_compress_ptr cinfo) /* This is called when we ran out of buffer (shouldn't happen!) */ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; buffer->offset = buffer->capacity; buffer_reserve(buffer, 2048); @@ -1176,13 +1190,14 @@ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo) /* This is called when we are done processing data */ static void jpeg_term_destination(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer; } -static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) +static int send_jpeg_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int quality) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; @@ -1193,15 +1208,15 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) int dy; if (surface_bytes_per_pixel(vs->vd->ds) == 1) { - return send_full_color_rect(vs, x, y, w, h); + return send_full_color_rect(vs, worker, x, y, w, h); } - buffer_reserve(&vs->tight->jpeg, 2048); + buffer_reserve(&worker->tight.jpeg, 2048); cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); - cinfo.client_data = vs; + cinfo.client_data = &worker->tight; cinfo.image_width = w; cinfo.image_height = h; cinfo.input_components = 3; @@ -1231,9 +1246,9 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) vnc_write_u8(vs, VNC_TIGHT_JPEG << 4); - tight_send_compact_size(vs, vs->tight->jpeg.offset); - vnc_write(vs, vs->tight->jpeg.buffer, vs->tight->jpeg.offset); - buffer_reset(&vs->tight->jpeg); + tight_send_compact_size(vs, worker->tight.jpeg.offset); + vnc_write(vs, worker->tight.jpeg.buffer, worker->tight.jpeg.offset); + buffer_reset(&worker->tight.jpeg); return 1; } @@ -1249,7 +1264,7 @@ static void write_png_palette(int idx, uint32_t pix, void *opaque) VncState *vs = priv->vs; png_colorp color = &priv->png_palette[idx]; - if (vs->tight->pixel24) + if (priv->tight->pixel24) { color->red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax; color->green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax; @@ -1274,12 +1289,12 @@ static void write_png_palette(int idx, uint32_t pix, void *opaque) static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { - VncState *vs = png_get_io_ptr(png_ptr); + VncWorker *worker = png_get_io_ptr(png_ptr); - buffer_reserve(&vs->tight->png, vs->tight->png.offset + length); - memcpy(vs->tight->png.buffer + vs->tight->png.offset, data, length); + buffer_reserve(&worker->tight.png, worker->tight.png.offset + length); + memcpy(worker->tight.png.buffer + worker->tight.png.offset, data, length); - vs->tight->png.offset += length; + worker->tight.png.offset += length; } static void png_flush_data(png_structp png_ptr) @@ -1296,16 +1311,16 @@ static void vnc_png_free(png_structp png_ptr, png_voidp ptr) g_free(ptr); } -static int send_png_rect(VncState *vs, int x, int y, int w, int h, - VncPalette *palette) +static int send_png_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, VncPalette *palette) { png_byte color_type; png_structp png_ptr; png_infop info_ptr; png_colorp png_palette = NULL; pixman_image_t *linebuf; - int level = tight_png_conf[vs->tight->compression].png_zlib_level; - int filters = tight_png_conf[vs->tight->compression].png_filters; + int level = tight_png_conf[worker->tight.compression].png_zlib_level; + int filters = tight_png_conf[worker->tight.compression].png_filters; uint8_t *buf; int dy; @@ -1322,7 +1337,7 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, return -1; } - png_set_write_fn(png_ptr, (void *) vs, png_write_data, png_flush_data); + png_set_write_fn(png_ptr, worker, png_write_data, png_flush_data); png_set_compression_level(png_ptr, level); png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters); @@ -1343,29 +1358,30 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, palette_size(palette)); priv.vs = vs; + priv.tight = &worker->tight; priv.png_palette = png_palette; palette_iter(palette, write_png_palette, &priv); png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette)); if (vs->client_pf.bytes_per_pixel == 4) { - tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, + tight_encode_indexed_rect32(worker->tight.tight.buffer, w * h, palette); } else { - tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, + tight_encode_indexed_rect16(worker->tight.tight.buffer, w * h, palette); } } png_write_info(png_ptr, info_ptr); - buffer_reserve(&vs->tight->png, 2048); + buffer_reserve(&worker->tight.png, 2048); linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w); buf = (uint8_t *)pixman_image_get_data(linebuf); for (dy = 0; dy < h; dy++) { if (color_type == PNG_COLOR_TYPE_PALETTE) { - memcpy(buf, vs->tight->tight.buffer + (dy * w), w); + memcpy(buf, worker->tight.tight.buffer + (dy * w), w); } else { qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy); } @@ -1383,46 +1399,47 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, vnc_write_u8(vs, VNC_TIGHT_PNG << 4); - tight_send_compact_size(vs, vs->tight->png.offset); - vnc_write(vs, vs->tight->png.buffer, vs->tight->png.offset); - buffer_reset(&vs->tight->png); + tight_send_compact_size(vs, worker->tight.png.offset); + vnc_write(vs, worker->tight.png.buffer, worker->tight.png.offset); + buffer_reset(&worker->tight.png); return 1; } #endif /* CONFIG_PNG */ -static void vnc_tight_start(VncState *vs) +static void vnc_tight_start(VncState *vs, VncTight *tight) { - buffer_reset(&vs->tight->tight); + buffer_reset(&tight->tight); // make the output buffer be the zlib buffer, so we can compress it later - vs->tight->tmp = vs->output; - vs->output = vs->tight->tight; + tight->tmp = vs->output; + vs->output = tight->tight; } -static void vnc_tight_stop(VncState *vs) +static void vnc_tight_stop(VncState *vs, VncTight *tight) { // switch back to normal output/zlib buffers - vs->tight->tight = vs->output; - vs->output = vs->tight->tmp; + tight->tight = vs->output; + vs->output = tight->tmp; } -static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h, +static int send_sub_rect_nojpeg(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int bg, int fg, int colors, VncPalette *palette) { int ret; if (colors == 0) { - if (tight_detect_smooth_image(vs, w, h)) { - ret = send_gradient_rect(vs, x, y, w, h); + if (tight_detect_smooth_image(vs, &worker->tight, w, h)) { + ret = send_gradient_rect(vs, worker, x, y, w, h); } else { - ret = send_full_color_rect(vs, x, y, w, h); + ret = send_full_color_rect(vs, worker, x, y, w, h); } } else if (colors == 1) { - ret = send_solid_rect(vs); + ret = send_solid_rect(vs, worker); } else if (colors == 2) { - ret = send_mono_rect(vs, x, y, w, h, bg, fg); + ret = send_mono_rect(vs, worker, x, y, w, h, bg, fg); } else if (colors <= 256) { - ret = send_palette_rect(vs, x, y, w, h, palette); + ret = send_palette_rect(vs, worker, x, y, w, h, palette); } else { ret = 0; } @@ -1430,34 +1447,35 @@ static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h, } #ifdef CONFIG_VNC_JPEG -static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h, +static int send_sub_rect_jpeg(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int bg, int fg, int colors, VncPalette *palette, bool force) { int ret; if (colors == 0) { - if (force || (tight_jpeg_conf[vs->tight->quality].jpeg_full && - tight_detect_smooth_image(vs, w, h))) { - int quality = tight_conf[vs->tight->quality].jpeg_quality; + if (force || (tight_jpeg_conf[worker->tight.quality].jpeg_full && + tight_detect_smooth_image(vs, &worker->tight, w, h))) { + int quality = tight_conf[worker->tight.quality].jpeg_quality; - ret = send_jpeg_rect(vs, x, y, w, h, quality); + ret = send_jpeg_rect(vs, worker, x, y, w, h, quality); } else { - ret = send_full_color_rect(vs, x, y, w, h); + ret = send_full_color_rect(vs, worker, x, y, w, h); } } else if (colors == 1) { - ret = send_solid_rect(vs); + ret = send_solid_rect(vs, worker); } else if (colors == 2) { - ret = send_mono_rect(vs, x, y, w, h, bg, fg); + ret = send_mono_rect(vs, worker, x, y, w, h, bg, fg); } else if (colors <= 256) { if (force || (colors > 96 && - tight_jpeg_conf[vs->tight->quality].jpeg_idx && - tight_detect_smooth_image(vs, w, h))) { - int quality = tight_conf[vs->tight->quality].jpeg_quality; + tight_jpeg_conf[worker->tight.quality].jpeg_idx && + tight_detect_smooth_image(vs, &worker->tight, w, h))) { + int quality = tight_conf[worker->tight.quality].jpeg_quality; - ret = send_jpeg_rect(vs, x, y, w, h, quality); + ret = send_jpeg_rect(vs, worker, x, y, w, h, quality); } else { - ret = send_palette_rect(vs, x, y, w, h, palette); + ret = send_palette_rect(vs, worker, x, y, w, h, palette); } } else { ret = 0; @@ -1475,8 +1493,10 @@ static void vnc_tight_cleanup(Notifier *n, void *value) color_count_palette = NULL; } -static int send_sub_rect(VncState *vs, int x, int y, int w, int h) +static int send_sub_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; uint32_t bg = 0, fg = 0; int colors; int ret = 0; @@ -1491,57 +1511,59 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h) qemu_thread_atexit_add(&vnc_tight_cleanup_notifier); } - vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type); + vnc_framebuffer_update(vs, x, y, w, h, tight->type); - vnc_tight_start(vs); + vnc_tight_start(vs, tight); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vnc_tight_stop(vs); + vnc_tight_stop(vs, tight); #ifdef CONFIG_VNC_JPEG - if (!vs->vd->non_adaptive && vs->tight->quality != (uint8_t)-1) { + if (!vs->vd->non_adaptive && tight->quality != (uint8_t)-1) { double freq = vnc_update_freq(vs, x, y, w, h); - if (freq < tight_jpeg_conf[vs->tight->quality].jpeg_freq_min) { + if (freq < tight_jpeg_conf[tight->quality].jpeg_freq_min) { allow_jpeg = false; } - if (freq >= tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) { + if (freq >= tight_jpeg_conf[tight->quality].jpeg_freq_threshold) { force_jpeg = true; - vnc_sent_lossy_rect(vs, x, y, w, h); + vnc_sent_lossy_rect(worker, x, y, w, h); } } #endif - colors = tight_fill_palette(vs, x, y, w * h, &bg, &fg, color_count_palette); + colors = tight_fill_palette(vs, tight, x, y, w * h, &bg, &fg, + color_count_palette); #ifdef CONFIG_VNC_JPEG - if (allow_jpeg && vs->tight->quality != (uint8_t)-1) { - ret = send_sub_rect_jpeg(vs, x, y, w, h, bg, fg, colors, + if (allow_jpeg && tight->quality != (uint8_t)-1) { + ret = send_sub_rect_jpeg(vs, worker, x, y, w, h, bg, fg, colors, color_count_palette, force_jpeg); } else { - ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, - color_count_palette); + ret = send_sub_rect_nojpeg(vs, worker, x, y, w, h, bg, fg, + colors, color_count_palette); } #else - ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, + ret = send_sub_rect_nojpeg(vs, worker, x, y, w, h, bg, fg, colors, color_count_palette); #endif return ret; } -static int send_sub_rect_solid(VncState *vs, int x, int y, int w, int h) +static int send_sub_rect_solid(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type); + vnc_framebuffer_update(vs, x, y, w, h, worker->tight.type); - vnc_tight_start(vs); + vnc_tight_start(vs, &worker->tight); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vnc_tight_stop(vs); + vnc_tight_stop(vs, &worker->tight); - return send_solid_rect(vs); + return send_solid_rect(vs, worker); } -static int send_rect_simple(VncState *vs, int x, int y, int w, int h, - bool split) +static int send_rect_simple(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, bool split) { int max_size, max_width; int max_sub_width, max_sub_height; @@ -1549,8 +1571,8 @@ static int send_rect_simple(VncState *vs, int x, int y, int w, int h, int rw, rh; int n = 0; - max_size = tight_conf[vs->tight->compression].max_rect_size; - max_width = tight_conf[vs->tight->compression].max_rect_width; + max_size = tight_conf[worker->tight.compression].max_rect_size; + max_width = tight_conf[worker->tight.compression].max_rect_width; if (split && (w > max_width || w * h > max_size)) { max_sub_width = (w > max_width) ? max_width : w; @@ -1560,18 +1582,18 @@ static int send_rect_simple(VncState *vs, int x, int y, int w, int h, for (dx = 0; dx < w; dx += max_width) { rw = MIN(max_sub_width, w - dx); rh = MIN(max_sub_height, h - dy); - n += send_sub_rect(vs, x+dx, y+dy, rw, rh); + n += send_sub_rect(vs, worker, x + dx, y + dy, rw, rh); } } } else { - n += send_sub_rect(vs, x, y, w, h); + n += send_sub_rect(vs, worker, x, y, w, h); } return n; } -static int find_large_solid_color_rect(VncState *vs, int x, int y, - int w, int h, int max_rows) +static int find_large_solid_color_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int max_rows) { int dx, dy, dw, dh; int n = 0; @@ -1583,7 +1605,7 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, /* If a rectangle becomes too large, send its upper part now. */ if (dy - y >= max_rows) { - n += send_rect_simple(vs, x, y, w, max_rows, true); + n += send_rect_simple(vs, worker, x, y, w, max_rows, true); y += max_rows; h -= max_rows; } @@ -1622,26 +1644,28 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, /* Send rectangles at top and left to solid-color area. */ if (y_best != y) { - n += send_rect_simple(vs, x, y, w, y_best-y, true); + n += send_rect_simple(vs, worker, x, y, w, y_best - y, true); } if (x_best != x) { - n += tight_send_framebuffer_update(vs, x, y_best, + n += tight_send_framebuffer_update(vs, worker, x, y_best, x_best-x, h_best); } /* Send solid-color rectangle. */ - n += send_sub_rect_solid(vs, x_best, y_best, w_best, h_best); + n += send_sub_rect_solid(vs, worker, + x_best, y_best, w_best, h_best); /* Send remaining rectangles (at right and bottom). */ if (x_best + w_best != x + w) { - n += tight_send_framebuffer_update(vs, x_best+w_best, + n += tight_send_framebuffer_update(vs, worker, x_best + w_best, y_best, w-(x_best-x)-w_best, h_best); } if (y_best + h_best != y + h) { - n += tight_send_framebuffer_update(vs, x, y_best+h_best, + n += tight_send_framebuffer_update(vs, worker, + x, y_best + h_best, w, h-(y_best-y)-h_best); } @@ -1649,73 +1673,73 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, return n; } } - return n + send_rect_simple(vs, x, y, w, h, true); + return n + send_rect_simple(vs, worker, x, y, w, h, true); } -static int tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +static int tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int max_rows; if (vs->client_pf.bytes_per_pixel == 4 && vs->client_pf.rmax == 0xFF && vs->client_pf.bmax == 0xFF && vs->client_pf.gmax == 0xFF) { - vs->tight->pixel24 = true; + worker->tight.pixel24 = true; } else { - vs->tight->pixel24 = false; + worker->tight.pixel24 = false; } #ifdef CONFIG_VNC_JPEG - if (vs->tight->quality != (uint8_t)-1) { + if (worker->tight.quality != (uint8_t)-1) { double freq = vnc_update_freq(vs, x, y, w, h); - if (freq > tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) { - return send_rect_simple(vs, x, y, w, h, false); + if (freq > tight_jpeg_conf[worker->tight.quality].jpeg_freq_threshold) { + return send_rect_simple(vs, worker, x, y, w, h, false); } } #endif if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE) { - return send_rect_simple(vs, x, y, w, h, true); + return send_rect_simple(vs, worker, x, y, w, h, true); } /* Calculate maximum number of rows in one non-solid rectangle. */ - max_rows = tight_conf[vs->tight->compression].max_rect_size; - max_rows /= MIN(tight_conf[vs->tight->compression].max_rect_width, w); + max_rows = tight_conf[worker->tight.compression].max_rect_size; + max_rows /= MIN(tight_conf[worker->tight.compression].max_rect_width, w); - return find_large_solid_color_rect(vs, x, y, w, h, max_rows); + return find_large_solid_color_rect(vs, worker, x, y, w, h, max_rows); } -int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +int vnc_tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->tight->type = VNC_ENCODING_TIGHT; - return tight_send_framebuffer_update(vs, x, y, w, h); + worker->tight.type = VNC_ENCODING_TIGHT; + return tight_send_framebuffer_update(vs, worker, x, y, w, h); } -int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +int vnc_tight_png_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->tight->type = VNC_ENCODING_TIGHT_PNG; - return tight_send_framebuffer_update(vs, x, y, w, h); + worker->tight.type = VNC_ENCODING_TIGHT_PNG; + return tight_send_framebuffer_update(vs, worker, x, y, w, h); } -void vnc_tight_clear(VncState *vs) +void vnc_tight_clear(VncWorker *worker) { int i; - for (i = 0; i < ARRAY_SIZE(vs->tight->stream); i++) { - if (vs->tight->stream[i].opaque) { - deflateEnd(&vs->tight->stream[i]); + for (i = 0; i < ARRAY_SIZE(worker->tight.stream); i++) { + if (worker->tight.stream[i].opaque) { + deflateEnd(&worker->tight.stream[i]); } } - buffer_free(&vs->tight->tight); - buffer_free(&vs->tight->zlib); - buffer_free(&vs->tight->gradient); + buffer_free(&worker->tight.tight); + buffer_free(&worker->tight.zlib); + buffer_free(&worker->tight.gradient); #ifdef CONFIG_VNC_JPEG - buffer_free(&vs->tight->jpeg); + buffer_free(&worker->tight.jpeg); #endif #ifdef CONFIG_PNG - buffer_free(&vs->tight->png); + buffer_free(&worker->tight.png); #endif } diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c index 900ae5b..a6d2871 100644 --- a/ui/vnc-enc-zlib.c +++ b/ui/vnc-enc-zlib.c @@ -46,23 +46,23 @@ void vnc_zlib_zfree(void *x, void *addr) g_free(addr); } -static void vnc_zlib_start(VncState *vs) +static void vnc_zlib_start(VncState *vs, VncWorker *worker) { - buffer_reset(&vs->zlib.zlib); + buffer_reset(&worker->zlib.zlib); // make the output buffer be the zlib buffer, so we can compress it later - vs->zlib.tmp = vs->output; - vs->output = vs->zlib.zlib; + worker->zlib.tmp = vs->output; + vs->output = worker->zlib.zlib; } -static int vnc_zlib_stop(VncState *vs) +static int vnc_zlib_stop(VncState *vs, VncWorker *worker) { - z_streamp zstream = &vs->zlib.stream; + z_streamp zstream = &worker->zlib.stream; int previous_out; // switch back to normal output/zlib buffers - vs->zlib.zlib = vs->output; - vs->output = vs->zlib.tmp; + worker->zlib.zlib = vs->output; + vs->output = worker->zlib.tmp; // compress the zlib buffer @@ -76,7 +76,7 @@ static int vnc_zlib_stop(VncState *vs) zstream->zalloc = vnc_zlib_zalloc; zstream->zfree = vnc_zlib_zfree; - err = deflateInit2(zstream, vs->tight->compression, Z_DEFLATED, + err = deflateInit2(zstream, worker->tight.compression, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); @@ -85,24 +85,24 @@ static int vnc_zlib_stop(VncState *vs) return -1; } - vs->zlib.level = vs->tight->compression; + worker->zlib.level = worker->tight.compression; zstream->opaque = vs; } - if (vs->tight->compression != vs->zlib.level) { - if (deflateParams(zstream, vs->tight->compression, + if (worker->tight.compression != worker->zlib.level) { + if (deflateParams(zstream, worker->tight.compression, Z_DEFAULT_STRATEGY) != Z_OK) { return -1; } - vs->zlib.level = vs->tight->compression; + worker->zlib.level = worker->tight.compression; } // reserve memory in output buffer - buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64); + buffer_reserve(&vs->output, worker->zlib.zlib.offset + 64); // set pointers - zstream->next_in = vs->zlib.zlib.buffer; - zstream->avail_in = vs->zlib.zlib.offset; + zstream->next_in = worker->zlib.zlib.buffer; + zstream->avail_in = worker->zlib.zlib.offset; zstream->next_out = vs->output.buffer + vs->output.offset; zstream->avail_out = vs->output.capacity - vs->output.offset; previous_out = zstream->avail_out; @@ -118,7 +118,8 @@ static int vnc_zlib_stop(VncState *vs) return previous_out - zstream->avail_out; } -int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int old_offset, new_offset, bytes_written; @@ -129,9 +130,9 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) vnc_write_s32(vs, 0); // compress the stream - vnc_zlib_start(vs); + vnc_zlib_start(vs, worker); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - bytes_written = vnc_zlib_stop(vs); + bytes_written = vnc_zlib_stop(vs, worker); if (bytes_written == -1) return 0; @@ -145,10 +146,10 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) return 1; } -void vnc_zlib_clear(VncState *vs) +void vnc_zlib_clear(VncWorker *worker) { - if (vs->zlib.stream.opaque) { - deflateEnd(&vs->zlib.stream); + if (worker->zlib.stream.opaque) { + deflateEnd(&worker->zlib.stream); } - buffer_free(&vs->zlib.zlib); + buffer_free(&worker->zlib.zlib); } diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c index 97ec6c7..7679014 100644 --- a/ui/vnc-enc-zrle.c +++ b/ui/vnc-enc-zrle.c @@ -35,45 +35,45 @@ static const int bits_per_packed_pixel[] = { }; -static void vnc_zrle_start(VncState *vs) +static void vnc_zrle_start(VncState *vs, VncZrle *zrle) { - buffer_reset(&vs->zrle->zrle); + buffer_reset(&zrle->zrle); /* make the output buffer be the zlib buffer, so we can compress it later */ - vs->zrle->tmp = vs->output; - vs->output = vs->zrle->zrle; + zrle->tmp = vs->output; + vs->output = zrle->zrle; } -static void vnc_zrle_stop(VncState *vs) +static void vnc_zrle_stop(VncState *vs, VncZrle *zrle) { /* switch back to normal output/zlib buffers */ - vs->zrle->zrle = vs->output; - vs->output = vs->zrle->tmp; + zrle->zrle = vs->output; + vs->output = zrle->tmp; } -static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h, - int bpp) +static void *zrle_convert_fb(VncState *vs, VncZrle *zrle, + int x, int y, int w, int h, int bpp) { Buffer tmp; - buffer_reset(&vs->zrle->fb); - buffer_reserve(&vs->zrle->fb, w * h * bpp + bpp); + buffer_reset(&zrle->fb); + buffer_reserve(&zrle->fb, w * h * bpp + bpp); tmp = vs->output; - vs->output = vs->zrle->fb; + vs->output = zrle->fb; vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vs->zrle->fb = vs->output; + zrle->fb = vs->output; vs->output = tmp; - return vs->zrle->fb.buffer; + return zrle->fb.buffer; } -static int zrle_compress_data(VncState *vs, int level) +static int zrle_compress_data(VncState *vs, VncZrle *zrle, int level) { - z_streamp zstream = &vs->zrle->stream; + z_streamp zstream = &zrle->stream; - buffer_reset(&vs->zrle->zlib); + buffer_reset(&zrle->zlib); if (zstream->opaque != vs) { int err; @@ -93,13 +93,13 @@ static int zrle_compress_data(VncState *vs, int level) } /* reserve memory in output buffer */ - buffer_reserve(&vs->zrle->zlib, vs->zrle->zrle.offset + 64); + buffer_reserve(&zrle->zlib, zrle->zrle.offset + 64); /* set pointers */ - zstream->next_in = vs->zrle->zrle.buffer; - zstream->avail_in = vs->zrle->zrle.offset; - zstream->next_out = vs->zrle->zlib.buffer; - zstream->avail_out = vs->zrle->zlib.capacity; + zstream->next_in = zrle->zrle.buffer; + zstream->avail_in = zrle->zrle.offset; + zstream->next_out = zrle->zlib.buffer; + zstream->avail_out = zrle->zlib.capacity; zstream->data_type = Z_BINARY; /* start encoding */ @@ -108,8 +108,8 @@ static int zrle_compress_data(VncState *vs, int level) return -1; } - vs->zrle->zlib.offset = vs->zrle->zlib.capacity - zstream->avail_out; - return vs->zrle->zlib.offset; + zrle->zlib.offset = zrle->zlib.capacity - zstream->avail_out; + return zrle->zlib.offset; } /* Try to work out whether to use RLE and/or a palette. We do this by @@ -252,21 +252,21 @@ static void zrle_write_u8(VncState *vs, uint8_t value) #undef ZRLE_COMPACT_PIXEL #undef ZRLE_BPP -static int zrle_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +static int zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { bool be = vs->client_endian == G_BIG_ENDIAN; size_t bytes; int zywrle_level; - if (vs->zrle->type == VNC_ENCODING_ZYWRLE) { - if (!vs->vd->lossy || vs->tight->quality == (uint8_t)-1 - || vs->tight->quality == 9) { + if (worker->zrle.type == VNC_ENCODING_ZYWRLE) { + if (!vs->vd->lossy || worker->tight.quality == (uint8_t)-1 + || worker->tight.quality == 9) { zywrle_level = 0; - vs->zrle->type = VNC_ENCODING_ZRLE; - } else if (vs->tight->quality < 3) { + worker->zrle.type = VNC_ENCODING_ZRLE; + } else if (worker->tight.quality < 3) { zywrle_level = 3; - } else if (vs->tight->quality < 6) { + } else if (worker->tight.quality < 6) { zywrle_level = 2; } else { zywrle_level = 1; @@ -275,25 +275,25 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y, zywrle_level = 0; } - vnc_zrle_start(vs); + vnc_zrle_start(vs, &worker->zrle); switch (vs->client_pf.bytes_per_pixel) { case 1: - zrle_encode_8ne(vs, x, y, w, h, zywrle_level); + zrle_encode_8ne(vs, &worker->zrle, x, y, w, h, zywrle_level); break; case 2: if (vs->client_pf.gmax > 0x1F) { if (be) { - zrle_encode_16be(vs, x, y, w, h, zywrle_level); + zrle_encode_16be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_16le(vs, x, y, w, h, zywrle_level); + zrle_encode_16le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else { if (be) { - zrle_encode_15be(vs, x, y, w, h, zywrle_level); + zrle_encode_15be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_15le(vs, x, y, w, h, zywrle_level); + zrle_encode_15le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } break; @@ -314,53 +314,55 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y, if ((fits_in_ls3bytes && !be) || (fits_in_ms3bytes && be)) { if (be) { - zrle_encode_24abe(vs, x, y, w, h, zywrle_level); + zrle_encode_24abe(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_24ale(vs, x, y, w, h, zywrle_level); + zrle_encode_24ale(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else if ((fits_in_ls3bytes && be) || (fits_in_ms3bytes && !be)) { if (be) { - zrle_encode_24bbe(vs, x, y, w, h, zywrle_level); + zrle_encode_24bbe(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_24ble(vs, x, y, w, h, zywrle_level); + zrle_encode_24ble(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else { if (be) { - zrle_encode_32be(vs, x, y, w, h, zywrle_level); + zrle_encode_32be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_32le(vs, x, y, w, h, zywrle_level); + zrle_encode_32le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } } break; } - vnc_zrle_stop(vs); - bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION); - vnc_framebuffer_update(vs, x, y, w, h, vs->zrle->type); + vnc_zrle_stop(vs, &worker->zrle); + bytes = zrle_compress_data(vs, &worker->zrle, Z_DEFAULT_COMPRESSION); + vnc_framebuffer_update(vs, x, y, w, h, worker->zrle.type); vnc_write_u32(vs, bytes); - vnc_write(vs, vs->zrle->zlib.buffer, vs->zrle->zlib.offset); + vnc_write(vs, worker->zrle.zlib.buffer, worker->zrle.zlib.offset); return 1; } -int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->zrle->type = VNC_ENCODING_ZRLE; - return zrle_send_framebuffer_update(vs, x, y, w, h); + worker->zrle.type = VNC_ENCODING_ZRLE; + return zrle_send_framebuffer_update(vs, worker, x, y, w, h); } -int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->zrle->type = VNC_ENCODING_ZYWRLE; - return zrle_send_framebuffer_update(vs, x, y, w, h); + worker->zrle.type = VNC_ENCODING_ZYWRLE; + return zrle_send_framebuffer_update(vs, worker, x, y, w, h); } -void vnc_zrle_clear(VncState *vs) +void vnc_zrle_clear(VncWorker *worker) { - if (vs->zrle->stream.opaque) { - deflateEnd(&vs->zrle->stream); + if (worker->zrle.stream.opaque) { + deflateEnd(&worker->zrle.stream); } - buffer_free(&vs->zrle->zrle); - buffer_free(&vs->zrle->fb); - buffer_free(&vs->zrle->zlib); + buffer_free(&worker->zrle.zrle); + buffer_free(&worker->zrle.fb); + buffer_free(&worker->zrle.zlib); } diff --git a/ui/vnc-enc-zrle.c.inc b/ui/vnc-enc-zrle.c.inc index 2ef7501..68d28f5 100644 --- a/ui/vnc-enc-zrle.c.inc +++ b/ui/vnc-enc-zrle.c.inc @@ -62,16 +62,16 @@ #define ZRLE_ENCODE_TILE ZRLE_CONCAT2(zrle_encode_tile, ZRLE_ENCODE_SUFFIX) #define ZRLE_WRITE_PALETTE ZRLE_CONCAT2(zrle_write_palette,ZRLE_ENCODE_SUFFIX) -static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, - int zywrle_level); +static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data, + int w, int h, int zywrle_level); #if ZRLE_BPP != 8 #include "vnc-enc-zywrle-template.c" #endif -static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h, - int zywrle_level) +static void ZRLE_ENCODE(VncState *vs, VncZrle *zrle, + int x, int y, int w, int h, int zywrle_level) { int ty; @@ -87,16 +87,16 @@ static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h, tw = MIN(VNC_ZRLE_TILE_WIDTH, x + w - tx); - buf = zrle_convert_fb(vs, tx, ty, tw, th, ZRLE_BPP); - ZRLE_ENCODE_TILE(vs, buf, tw, th, zywrle_level); + buf = zrle_convert_fb(vs, zrle, tx, ty, tw, th, ZRLE_BPP); + ZRLE_ENCODE_TILE(vs, zrle, buf, tw, th, zywrle_level); } } } -static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, - int zywrle_level) +static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data, + int w, int h, int zywrle_level) { - VncPalette *palette = &vs->zrle->palette; + VncPalette *palette = &zrle->palette; int runs = 0; int single_pixels = 0; @@ -236,7 +236,7 @@ static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, #if ZRLE_BPP != 8 if (zywrle_level > 0 && !(zywrle_level & 0x80)) { ZYWRLE_ANALYZE(data, data, w, h, w, zywrle_level, vs->zywrle.buf); - ZRLE_ENCODE_TILE(vs, data, w, h, zywrle_level | 0x80); + ZRLE_ENCODE_TILE(vs, zrle, data, w, h, zywrle_level | 0x80); } else #endif diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index d3486af..bed3395 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -185,14 +185,10 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local) local->vnc_encoding = orig->vnc_encoding; local->features = orig->features; local->vd = orig->vd; - local->lossy_rect = orig->lossy_rect; local->write_pixels = orig->write_pixels; local->client_pf = orig->client_pf; local->client_endian = orig->client_endian; - local->tight = orig->tight; - local->zlib = orig->zlib; local->hextile = orig->hextile; - local->zrle = orig->zrle; local->client_width = orig->client_width; local->client_height = orig->client_height; } @@ -200,11 +196,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local) static void vnc_async_encoding_end(VncState *orig, VncState *local) { buffer_free(&local->output); - orig->tight = local->tight; - orig->zlib = local->zlib; orig->hextile = local->hextile; - orig->zrle = local->zrle; - orig->lossy_rect = local->lossy_rect; } static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect) @@ -237,6 +229,7 @@ static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect) static int vnc_worker_thread_loop(VncJobQueue *queue) { + VncConnection *vc; VncJob *job; VncRectEntry *entry, *tmp; VncState vs = {}; @@ -256,6 +249,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) } assert(job->vs->magic == VNC_MAGIC); + vc = container_of(job->vs, VncConnection, vs); vnc_lock_output(job->vs); if (job->vs->ioc == NULL || job->vs->abort == true) { @@ -295,7 +289,8 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) } if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) { - n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y, + n = vnc_send_framebuffer_update(&vs, &vc->worker, + entry->rect.x, entry->rect.y, entry->rect.w, entry->rect.h); if (n >= 0) { @@ -946,29 +946,30 @@ int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) return 1; } -int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int n = 0; switch(vs->vnc_encoding) { case VNC_ENCODING_ZLIB: - n = vnc_zlib_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zlib_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_HEXTILE: vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE); n = vnc_hextile_send_framebuffer_update(vs, x, y, w, h); break; case VNC_ENCODING_TIGHT: - n = vnc_tight_send_framebuffer_update(vs, x, y, w, h); + n = vnc_tight_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_TIGHT_PNG: - n = vnc_tight_png_send_framebuffer_update(vs, x, y, w, h); + n = vnc_tight_png_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_ZRLE: - n = vnc_zrle_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zrle_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_ZYWRLE: - n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zywrle_send_framebuffer_update(vs, worker, x, y, w, h); break; default: vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW); @@ -1306,7 +1307,7 @@ static void vnc_disconnect_start(VncState *vs) void vnc_disconnect_finish(VncState *vs) { - int i; + VncConnection *vc = container_of(vs, VncConnection, vs); trace_vnc_client_disconnect_finish(vs, vs->ioc); @@ -1320,9 +1321,9 @@ void vnc_disconnect_finish(VncState *vs) qapi_free_VncClientInfo(vs->info); - vnc_zlib_clear(vs); - vnc_tight_clear(vs); - vnc_zrle_clear(vs); + vnc_zlib_clear(&vc->worker); + vnc_tight_clear(&vc->worker); + vnc_zrle_clear(&vc->worker); #ifdef CONFIG_VNC_SASL vnc_sasl_client_cleanup(vs); @@ -1350,19 +1351,12 @@ void vnc_disconnect_finish(VncState *vs) } buffer_free(&vs->jobs_buffer); - for (i = 0; i < VNC_STAT_ROWS; ++i) { - g_free(vs->lossy_rect[i]); - } - g_free(vs->lossy_rect); - object_unref(OBJECT(vs->ioc)); vs->ioc = NULL; object_unref(OBJECT(vs->sioc)); vs->sioc = NULL; vs->magic = 0; - g_free(vs->zrle); - g_free(vs->tight); - g_free(vs); + g_free(vc); } size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err) @@ -2126,13 +2120,14 @@ static void send_xvp_message(VncState *vs, int code) static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) { + VncConnection *vc = container_of(vs, VncConnection, vs); int i; unsigned int enc = 0; vs->features = 0; vs->vnc_encoding = 0; - vs->tight->compression = 9; - vs->tight->quality = -1; /* Lossless by default */ + vc->worker.tight.compression = 9; + vc->worker.tight.quality = -1; /* Lossless by default */ vs->absolute = -1; /* @@ -2220,11 +2215,11 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) vnc_server_cut_text_caps(vs); break; case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9: - vs->tight->compression = (enc & 0x0F); + vc->worker.tight.compression = (enc & 0x0F); break; case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9: if (vs->vd->lossy) { - vs->tight->quality = (enc & 0x0F); + vc->worker.tight.quality = (enc & 0x0F); } break; default: @@ -2953,7 +2948,7 @@ static VncRectStat *vnc_stat_rect(VncDisplay *vd, int x, int y) return &vs->stats[y / VNC_STAT_RECT][x / VNC_STAT_RECT]; } -void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h) +void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h) { int i, j; @@ -2964,7 +2959,7 @@ void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h) for (j = y; j <= h; j++) { for (i = x; i <= w; i++) { - vs->lossy_rect[j][i] = 1; + worker->lossy_rect[j][i] = 1; } } } @@ -2980,6 +2975,7 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT); QTAILQ_FOREACH(vs, &vd->clients, next) { + VncConnection *vc = container_of(vs, VncConnection, vs); int j; /* kernel send buffers are full -> refresh later */ @@ -2987,11 +2983,11 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) continue; } - if (!vs->lossy_rect[sty][stx]) { + if (!vc->worker.lossy_rect[sty][stx]) { continue; } - vs->lossy_rect[sty][stx] = 0; + vc->worker.lossy_rect[sty][stx] = 0; for (j = 0; j < VNC_STAT_RECT; ++j) { bitmap_set(vs->dirty[y + j], x / VNC_DIRTY_PIXELS_PER_BIT, @@ -3241,13 +3237,11 @@ static void vnc_refresh(DisplayChangeListener *dcl) static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, bool skipauth, bool websocket) { - VncState *vs = g_new0(VncState, 1); + VncConnection *vc = g_new0(VncConnection, 1); + VncState *vs = &vc->vs; bool first_client = QTAILQ_EMPTY(&vd->clients); - int i; trace_vnc_client_connect(vs, sioc); - vs->zrle = g_new0(VncZrle, 1); - vs->tight = g_new0(VncTight, 1); vs->magic = VNC_MAGIC; vs->sioc = sioc; object_ref(OBJECT(vs->sioc)); @@ -3255,23 +3249,23 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, object_ref(OBJECT(vs->ioc)); vs->vd = vd; - buffer_init(&vs->input, "vnc-input/%p", sioc); - buffer_init(&vs->output, "vnc-output/%p", sioc); - buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); + buffer_init(&vs->input, "vnc-input/%p", sioc); + buffer_init(&vs->output, "vnc-output/%p", sioc); + buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); - buffer_init(&vs->tight->tight, "vnc-tight/%p", sioc); - buffer_init(&vs->tight->zlib, "vnc-tight-zlib/%p", sioc); - buffer_init(&vs->tight->gradient, "vnc-tight-gradient/%p", sioc); + buffer_init(&vc->worker.tight.tight, "vnc-tight/%p", sioc); + buffer_init(&vc->worker.tight.zlib, "vnc-tight-zlib/%p", sioc); + buffer_init(&vc->worker.tight.gradient, "vnc-tight-gradient/%p", sioc); #ifdef CONFIG_VNC_JPEG - buffer_init(&vs->tight->jpeg, "vnc-tight-jpeg/%p", sioc); + buffer_init(&vc->worker.tight.jpeg, "vnc-tight-jpeg/%p", sioc); #endif #ifdef CONFIG_PNG - buffer_init(&vs->tight->png, "vnc-tight-png/%p", sioc); + buffer_init(&vc->worker.tight.png, "vnc-tight-png/%p", sioc); #endif - buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc); - buffer_init(&vs->zrle->zrle, "vnc-zrle/%p", sioc); - buffer_init(&vs->zrle->fb, "vnc-zrle-fb/%p", sioc); - buffer_init(&vs->zrle->zlib, "vnc-zrle-zlib/%p", sioc); + buffer_init(&vc->worker.zlib.zlib, "vnc-zlib/%p", sioc); + buffer_init(&vc->worker.zrle.zrle, "vnc-zrle/%p", sioc); + buffer_init(&vc->worker.zrle.fb, "vnc-zrle-fb/%p", sioc); + buffer_init(&vc->worker.zrle.zlib, "vnc-zrle-zlib/%p", sioc); if (skipauth) { vs->auth = VNC_AUTH_NONE; @@ -3288,11 +3282,6 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n", sioc, websocket, vs->auth, vs->subauth); - vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect)); - for (i = 0; i < VNC_STAT_ROWS; ++i) { - vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS); - } - VNC_DEBUG("New client on socket %p\n", vs->sioc); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); qio_channel_set_blocking(vs->ioc, false, NULL); @@ -272,8 +272,6 @@ struct VncState gboolean disconnecting; DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS); - uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in - * vnc-jobs-async.c */ VncDisplay *vd; VncStateUpdate update; /* Most recent pending request from client */ @@ -341,10 +339,7 @@ struct VncState /* Encoding specific, if you add something here, don't forget to * update vnc_async_encoding_start() */ - VncTight *tight; - VncZlib zlib; VncHextile hextile; - VncZrle *zrle; VncZywrle zywrle; Notifier mouse_mode_notifier; @@ -356,6 +351,19 @@ struct VncState QTAILQ_ENTRY(VncState) next; }; +typedef struct VncWorker { + uint8_t lossy_rect[VNC_STAT_ROWS][VNC_STAT_COLS]; + + VncTight tight; + VncZlib zlib; + VncZrle zrle; +} VncWorker; + +typedef struct VncConnection { + VncState vs; + VncWorker worker; +} VncConnection; + /***************************************************************************** * @@ -602,10 +610,11 @@ int vnc_server_fb_stride(VncDisplay *vd); void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v); double vnc_update_freq(VncState *vs, int x, int y, int w, int h); -void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h); +void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h); /* Encodings */ -int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); @@ -615,17 +624,21 @@ void vnc_hextile_set_pixel_conversion(VncState *vs, int generic); void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size); void vnc_zlib_zfree(void *x, void *addr); -int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -void vnc_zlib_clear(VncState *vs); - -int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h); -void vnc_tight_clear(VncState *vs); - -int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -void vnc_zrle_clear(VncState *vs); +int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_zlib_clear(VncWorker *worker); + +int vnc_tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +int vnc_tight_png_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_tight_clear(VncWorker *worker); + +int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_zrle_clear(VncWorker *worker); /* vnc-clipboard.c */ void vnc_server_cut_text_caps(VncState *vs); @@ -558,3 +558,15 @@ void qemu_print_log_usage(FILE *f) fprintf(f, "\nUse \"-d trace:help\" to get a list of trace events.\n\n"); #endif } + +#ifdef CONFIG_HAVE_RUST +ssize_t rust_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + /* + * Same as fwrite, but return -errno because Rust libc does not provide + * portable access to errno. :( + */ + int ret = fwrite(ptr, size, nmemb, stream); + return ret < 0 ? -errno : 0; +} +#endif |