:p
atchew
Login
The following changes since commit ac0cc20ad2fe0b8df2e5d9458e90a095ac711ab1: Merge tag 'for-upstream' of https://gitlab.com/bonzini/qemu into staging (2026-05-01 14:41:49 -0400) are available in the Git repository at: https://gitlab.com/peterx/qemu.git tags/next-pull-request for you to fetch changes up to 348c67cb16ecb479994b0c957699f906bd40a6c2: tests/qtest/migration: Fix A-B file build (2026-05-05 12:35:25 -0400) ---------------------------------------------------------------- Migration and mem pull request - Fabiano's fix on migrate_set_parameter crash with multifd & zerocopy - Pranav's fix on postcopy stucking at device state when ack lost - Samuel's new migration parameter x-rdma-chunk-size for RDMA - PeterX's vfio/migration series to report remaining data and fix downtime calc - PeterM's MemoryRegionOps .impl cleanup series - Fabiano's fix to build a-b migration bootfiles for all archs ---------------------------------------------------------------- CJ Chen (2): hw/riscv: iommu-trap: remove .impl.unaligned = true system/memory: assert on invalid MemoryRegionOps .unaligned combo Fabiano Rosas (2): migration: Use QAPI_CLONE_MEMBERS in migrate_params_test_apply tests/qtest/migration: Fix A-B file build Peter Maydell (2): hw/npcm7xx_fiu: Specify .impl for npcm7xx_fiu_flash_ops hw/xtensa/mx_pic: Specify xtensa_mx_pic_ops .impl settings Peter Xu (15): migration: Fix low possibility downtime violation migration/qapi: Rename MigrationStats to MigrationRAMStats vfio/migration: Cache stop size in VFIOMigration migration/treewide: Merge @state_pending_{exact|estimate} APIs migration: Use the new save_query_pending() API directly migration: Introduce stopcopy_bytes in save_query_pending() vfio/migration: Fix incorrect reporting for VFIO pending data migration: Move iteration counter out of RAM migration: Introduce a helper to return switchover bw estimate migration: Calculate expected downtime on demand migration: Fix calculation of expected_downtime to take VFIO info migration: Remember total dirty bytes in mig_stats migration/qapi: Introduce system-wide "remaining" reports migration/qapi: Update unit for avail-switchover-bandwidth vfio/migration: Add tracepoints for precopy/stopcopy query ioctls Pranav Tyagi (1): migration: Fix blocking in POSTCOPY_DEVICE during package load Samuel Zhang (1): migration/rdma: add x-rdma-chunk-size parameter docs/about/removed-features.rst | 2 +- docs/devel/migration/main.rst | 9 +- docs/devel/migration/vfio.rst | 9 +- qapi/migration.json | 45 +- hw/vfio/vfio-migration-internal.h | 8 + include/migration/register.h | 59 +- migration/migration-stats.h | 20 +- migration/migration.h | 3 +- migration/options.h | 1 + migration/savevm.h | 7 +- tests/qtest/migration/aarch64/a-b-kernel.h | 7 +- tests/qtest/migration/bootfile.h | 11 + tests/qtest/migration/i386/a-b-bootblock.h | 8 +- tests/qtest/migration/ppc64/a-b-kernel.h | 8 +- tests/qtest/migration/s390x/a-b-bios.h | 611 +++++++++++++-------- hw/riscv/riscv-iommu.c | 1 - hw/s390x/s390-stattrib.c | 9 +- hw/ssi/npcm7xx_fiu.c | 5 + hw/vfio/migration.c | 125 +++-- hw/xtensa/mx_pic.c | 27 + migration/block-dirty-bitmap.c | 10 +- migration/migration-hmp-cmds.c | 16 + migration/migration.c | 225 +++++--- migration/options.c | 59 +- migration/ram.c | 40 +- migration/rdma.c | 30 +- migration/savevm.c | 42 +- system/memory.c | 1 + tests/qtest/migration/s390x/a-b-bios.c | 66 ++- hw/vfio/trace-events | 5 +- migration/trace-events | 3 +- tests/qtest/migration/Makefile | 11 +- tests/qtest/migration/aarch64/Makefile | 4 +- tests/qtest/migration/aarch64/a-b-kernel.S | 2 +- tests/qtest/migration/i386/Makefile | 4 +- tests/qtest/migration/i386/a-b-bootblock.S | 2 +- tests/qtest/migration/ppc64/Makefile | 6 +- tests/qtest/migration/ppc64/a-b-kernel.S | 2 +- tests/qtest/migration/s390x/Makefile | 8 +- 39 files changed, 957 insertions(+), 554 deletions(-) -- 2.53.0
From: Pranav Tyagi <prtyagi@redhat.com> The package_loaded event is not set in case MIG_RP_MSG_PONG does not arrive on the source from the destination in the return path thread. The migration thread would then be blocked waiting for package_loaded event indefinitely in POSTCOPY_DEVICE state. Where as, in such a condition the source VM can safely resume as the destination has not yet started. The pong message can get lost in case of a network failure or destination crash before sending the pong. This patch removes the package_loaded event and uses rp_sem, instead of kicking multiple events. The error is detected in case of network failure or destination crash and rp_sem is set in the out path of the return path thread. This will kick the migration thread out from a condition of indefinitely waiting for rp_sem. The migration thread then fails early and breaks from the migration loop to resume the vm on the source side. Fixes: 7b842fe354c6 ("migration: Introduce POSTCOPY_DEVICE state") Signed-off-by: Pranav Tyagi <prtyagi@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Peter Xu <peterx@redhat.com> Link: https://lore.kernel.org/r/20260423094438.43556-1-prtyagi@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration.h | 1 - migration/migration.c | 48 ++++++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/migration/migration.h b/migration/migration.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -XXX,XX +XXX,XX @@ struct MigrationState { bool rdma_migration; bool postcopy_package_loaded; - QemuEvent postcopy_package_loaded_event; GSource *hup_source; diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ int migrate_init(MigrationState *s, Error **errp) migration_reset_vfio_bytes_transferred(); s->postcopy_package_loaded = false; - qemu_event_reset(&s->postcopy_package_loaded_event); return 0; } @@ -XXX,XX +XXX,XX @@ static void *source_return_path_thread(void *opaque) if (tmp32 == QEMU_VM_PING_PACKAGED_LOADED) { trace_source_return_path_thread_postcopy_package_loaded(); ms->postcopy_package_loaded = true; - qemu_event_set(&ms->postcopy_package_loaded_event); + migration_rp_kick(ms); } break; @@ -XXX,XX +XXX,XX @@ out: trace_source_return_path_thread_bad_end(); } - if (ms->state == MIGRATION_STATUS_POSTCOPY_RECOVER) { + if (ms->state == MIGRATION_STATUS_POSTCOPY_RECOVER || + ms->state == MIGRATION_STATUS_POSTCOPY_DEVICE) { /* - * this will be extremely unlikely: that we got yet another network - * issue during recovering of the 1st network failure.. during this - * period the main migration thread can be waiting on rp_sem for - * this thread to sync with the other side. + * The migration thread can get stuck waiting for rp_sem if the + * return path fails to sync with the destination. This handles + * two specific cases: * - * When this happens, explicitly kick the migration thread out of - * RECOVER stage and back to PAUSED, so the admin can try - * everything again. + * POSTCOPY_RECOVER: A failure occurs during a recovery attempt. + * We kick the migration thread back to PAUSED so the admin can + * retry. + * + * POSTCOPY_DEVICE: The MIG_RP_MSG_PONG is lost due to a + * network failure or destination crash. We kick the migration + * thread out of its wait so it can fail the migration and safely + * resume the VM on the source. */ migration_rp_kick(ms); } @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) if (s->state == MIGRATION_STATUS_POSTCOPY_DEVICE && (s->postcopy_package_loaded || complete_ready)) { /* - * If package has been loaded, the event is set and we will - * immediatelly transition to POSTCOPY_ACTIVE. If we are ready for - * completion, we need to wait for destination to load the postcopy - * package before actually completing. + * We will immediately transition to POSTCOPY_ACTIVE. + * If we are ready for completion, we need to wait for + * destination to load the postcopy package before actually + * completing. */ - qemu_event_wait(&s->postcopy_package_loaded_event); + while (!s->postcopy_package_loaded) { + if (migration_rp_wait(s)) { + /* + * Error happened. Migration thread was stuck waiting in + * POSTCOPY_DEVICE for rp_sem which was never set. + */ + migrate_set_state(&s->state, + MIGRATION_STATUS_POSTCOPY_DEVICE, + MIGRATION_STATUS_FAILING); + return MIG_ITERATE_BREAK; + } + } + /* Acknowledgement received from the destination */ migrate_set_state(&s->state, MIGRATION_STATUS_POSTCOPY_DEVICE, MIGRATION_STATUS_POSTCOPY_ACTIVE); } @@ -XXX,XX +XXX,XX @@ static void migration_instance_finalize(Object *obj) qemu_sem_destroy(&ms->rp_state.rp_pong_acks); qemu_sem_destroy(&ms->postcopy_qemufile_src_sem); error_free(ms->error); - qemu_event_destroy(&ms->postcopy_package_loaded_event); } static void migration_instance_init(Object *obj) @@ -XXX,XX +XXX,XX @@ static void migration_instance_init(Object *obj) qemu_sem_init(&ms->wait_unplug_sem, 0); qemu_sem_init(&ms->postcopy_qemufile_src_sem, 0); qemu_mutex_init(&ms->qemu_file_lock); - qemu_event_init(&ms->postcopy_package_loaded_event, 0); } /* -- 2.53.0
From: Fabiano Rosas <farosas@suse.de> Use QAPI_CLONE_MEMBERS instead of making an assignment. The QAPI method makes the handling of the TLS strings more intuitive because it clones them as well. This also fixes a segfault when a NULL TLS option is accessed as part of a validation check for another option (e.g. in the zero-copy + multifd compression case). Details follow: Currently, after copying s->parameters to the temporary MigrationParameters object before migrate_params_check(), the references in temporary object to the TLS options are dropped, either because: a) the user set a new option, in which case that's fine as s->parameters still holds the reference to the old memory or, b) the user did not set a new option, in which case keeping the references in the temporary object would later cause them to be freed along with it, leading to double-free when s->parameters is also freed later on. In this second case, it was overlooked that the TLS options can be accessed already during migrate_params_check() as part of validation of another option. Those pointers should not have been cleared. Using QAPI_CLONE_MEMBERS fixes the issue because the temporary object is not stealing a reference from s->parameters anymore. Cc: qemu-stable <qemu-stable@nongnu.org> Fixes: aed97f0563 ("migration: Normalize tls arguments") Reported-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name> Link: https://lore.kernel.org/r/a65a1049-9f19-460a-8e27-a62bb30d2727@maciej.szmigiero.name Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Fabiano Rosas <farosas@suse.de> Tested-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com> Link: https://lore.kernel.org/r/20260414223718.23965-1-farosas@suse.de Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/options.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/migration/options.c b/migration/options.c index XXXXXXX..XXXXXXX 100644 --- a/migration/options.c +++ b/migration/options.c @@ -XXX,XX +XXX,XX @@ bool migrate_params_check(MigrationParameters *params, Error **errp) static void migrate_params_test_apply(MigrationParameters *params, MigrationParameters *dest) { - *dest = migrate_get_current()->parameters; + MigrationState *s = migrate_get_current(); - /* TODO use QAPI_CLONE() instead of duplicating it inline */ + QAPI_CLONE_MEMBERS(MigrationParameters, dest, &s->parameters); if (params->has_throttle_trigger_threshold) { dest->throttle_trigger_threshold = params->throttle_trigger_threshold; @@ -XXX,XX +XXX,XX @@ static void migrate_params_test_apply(MigrationParameters *params, } if (params->tls_creds) { + qapi_free_StrOrNull(dest->tls_creds); dest->tls_creds = QAPI_CLONE(StrOrNull, params->tls_creds); - } else { - /* clear the reference, it's owned by s->parameters */ - dest->tls_creds = NULL; } if (params->tls_hostname) { + qapi_free_StrOrNull(dest->tls_hostname); dest->tls_hostname = QAPI_CLONE(StrOrNull, params->tls_hostname); - } else { - /* clear the reference, it's owned by s->parameters */ - dest->tls_hostname = NULL; } if (params->tls_authz) { + qapi_free_StrOrNull(dest->tls_authz); dest->tls_authz = QAPI_CLONE(StrOrNull, params->tls_authz); - } else { - /* clear the reference, it's owned by s->parameters */ - dest->tls_authz = NULL; } if (params->has_max_bandwidth) { @@ -XXX,XX +XXX,XX @@ static void migrate_params_test_apply(MigrationParameters *params, } if (params->has_block_bitmap_mapping) { - dest->has_block_bitmap_mapping = true; - dest->block_bitmap_mapping = params->block_bitmap_mapping; + qapi_free_BitmapMigrationNodeAliasList(dest->block_bitmap_mapping); + dest->block_bitmap_mapping = QAPI_CLONE(BitmapMigrationNodeAliasList, + params->block_bitmap_mapping); } if (params->has_x_vcpu_dirty_limit_period) { @@ -XXX,XX +XXX,XX @@ static void migrate_params_test_apply(MigrationParameters *params, } if (params->has_cpr_exec_command) { - dest->cpr_exec_command = params->cpr_exec_command; + qapi_free_strList(dest->cpr_exec_command); + dest->cpr_exec_command = QAPI_CLONE(strList, params->cpr_exec_command); } } @@ -XXX,XX +XXX,XX @@ void qmp_migrate_set_parameters(MigrationParameters *params, Error **errp) } migrate_tls_opts_free(&tmp); + qapi_free_BitmapMigrationNodeAliasList(tmp.block_bitmap_mapping); + qapi_free_strList(tmp.cpr_exec_command); } -- 2.53.0
From: Samuel Zhang <guoqing.zhang@amd.com> The default 1MB RDMA chunk size causes slow live migration because each chunk triggers a write_flush (ibv_post_send). For 8GB RAM, 1MB chunk size produces ~15000 flushes vs ~3700 with 1024MB chunk size. Add x-rdma-chunk-size parameter to configure the RDMA chunk size for faster migration. Usage: `migrate_set_parameter x-rdma-chunk-size 1024M` Performance with RDMA live migration of 8GB RAM VM: | x-rdma-chunk-size (B) | time (s) | throughput (MB/s) | |-----------------------|----------|-------------------| | 1M (default) | 37.915 | 1,007 | | 32M | 17.880 | 2,260 | | 1024M | 4.368 | 17,529 | Signed-off-by: Samuel Zhang <guoqing.zhang@amd.com> Acked-by: Markus Armbruster <armbru@redhat.com> Acked-by: Li Zhijian <lizhijian@fujitsu.com> Tested-by: Li Zhijian <lizhijian@fujitsu.com> Acked-by: Fabiano Rosas <farosas@suse.de> Acked-by: Peter Xu <peterx@redhat.com> Link: https://lore.kernel.org/r/20260427031401.3895523-1-guoqing.zhang@amd.com Signed-off-by: Peter Xu <peterx@redhat.com> --- qapi/migration.json | 13 +++++++++++-- migration/options.h | 1 + migration/migration-hmp-cmds.c | 11 +++++++++++ migration/options.c | 33 ++++++++++++++++++++++++++++++++- migration/rdma.c | 30 ++++++++++++++++-------------- 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/qapi/migration.json b/qapi/migration.json index XXXXXXX..XXXXXXX 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -XXX,XX +XXX,XX @@ # # Features: # -# @unstable: Members @x-checkpoint-delay and +# @unstable: Members @x-checkpoint-delay, @x-rdma-chunk-size, and # @x-vcpu-dirty-limit-period are experimental. # # Since: 2.4 @@ -XXX,XX +XXX,XX @@ 'mode', 'zero-page-detection', 'direct-io', + { 'name': 'x-rdma-chunk-size', 'features': [ 'unstable' ] }, 'cpr-exec-command'] } ## @@ -XXX,XX +XXX,XX @@ # is @cpr-exec. The first list element is the program's filename, # the remainder its arguments. (Since 10.2) # +# @x-rdma-chunk-size: RDMA memory registration chunk size in bytes. +# Default is 1MiB. Must be a power of 2 in the range +# [1MiB, 1024MiB]. Only applies when migrating via RDMA. +# Must be set to the same value on both source and destination +# before migration starts. (Since 11.1) +# # Features: # -# @unstable: Members @x-checkpoint-delay and +# @unstable: Members @x-checkpoint-delay, @x-rdma-chunk-size, and # @x-vcpu-dirty-limit-period are experimental. # # Since: 2.4 @@ -XXX,XX +XXX,XX @@ '*mode': 'MigMode', '*zero-page-detection': 'ZeroPageDetection', '*direct-io': 'bool', + '*x-rdma-chunk-size': { 'type': 'uint64', + 'features': [ 'unstable' ] }, '*cpr-exec-command': [ 'str' ]} } ## diff --git a/migration/options.h b/migration/options.h index XXXXXXX..XXXXXXX 100644 --- a/migration/options.h +++ b/migration/options.h @@ -XXX,XX +XXX,XX @@ const char *migrate_tls_creds(void); const char *migrate_tls_hostname(void); uint64_t migrate_xbzrle_cache_size(void); ZeroPageDetection migrate_zero_page_detection(void); +uint64_t migrate_rdma_chunk_size(void); /* parameters helpers */ diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -XXX,XX +XXX,XX @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict) params->direct_io ? "on" : "off"); } + if (params->has_x_rdma_chunk_size) { + monitor_printf(mon, "%s: %" PRIu64 " bytes\n", + MigrationParameter_str( + MIGRATION_PARAMETER_X_RDMA_CHUNK_SIZE), + params->x_rdma_chunk_size); + } + assert(params->has_cpr_exec_command); monitor_print_cpr_exec_command(mon, params->cpr_exec_command); } @@ -XXX,XX +XXX,XX @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict) p->has_direct_io = true; visit_type_bool(v, param, &p->direct_io, &err); break; + case MIGRATION_PARAMETER_X_RDMA_CHUNK_SIZE: + p->has_x_rdma_chunk_size = true; + visit_type_size(v, param, &p->x_rdma_chunk_size, &err); + break; case MIGRATION_PARAMETER_CPR_EXEC_COMMAND: { /* * NOTE: g_autofree will only auto g_free() the strv array when diff --git a/migration/options.c b/migration/options.c index XXXXXXX..XXXXXXX 100644 --- a/migration/options.c +++ b/migration/options.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qemu/units.h" #include "exec/target_page.h" #include "qapi/clone-visitor.h" #include "qapi/error.h" @@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_StrOrNull; #define DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT_PERIOD 1000 /* milliseconds */ #define DEFAULT_MIGRATE_VCPU_DIRTY_LIMIT 1 /* MB/s */ +#define DEFAULT_MIGRATE_X_RDMA_CHUNK_SIZE MiB const Property migration_properties[] = { DEFINE_PROP_BOOL("store-global-state", MigrationState, @@ -XXX,XX +XXX,XX @@ const Property migration_properties[] = { DEFINE_PROP_ZERO_PAGE_DETECTION("zero-page-detection", MigrationState, parameters.zero_page_detection, ZERO_PAGE_DETECTION_MULTIFD), + DEFINE_PROP_UINT64("x-rdma-chunk-size", MigrationState, + parameters.x_rdma_chunk_size, + DEFAULT_MIGRATE_X_RDMA_CHUNK_SIZE), /* Migration capabilities */ DEFINE_PROP_MIG_CAP("x-xbzrle", MIGRATION_CAPABILITY_XBZRLE), @@ -XXX,XX +XXX,XX @@ ZeroPageDetection migrate_zero_page_detection(void) return s->parameters.zero_page_detection; } +uint64_t migrate_rdma_chunk_size(void) +{ + MigrationState *s = migrate_get_current(); + uint64_t size = s->parameters.x_rdma_chunk_size; + + assert(MiB <= size && size <= GiB && is_power_of_2(size)); + return size; +} + /* parameters helpers */ AnnounceParameters *migrate_announce_params(void) @@ -XXX,XX +XXX,XX @@ static void migrate_mark_all_params_present(MigrationParameters *p) &p->has_announce_step, &p->has_block_bitmap_mapping, &p->has_x_vcpu_dirty_limit_period, &p->has_vcpu_dirty_limit, &p->has_mode, &p->has_zero_page_detection, &p->has_direct_io, - &p->has_cpr_exec_command, + &p->has_x_rdma_chunk_size, &p->has_cpr_exec_command, }; len = ARRAY_SIZE(has_fields); @@ -XXX,XX +XXX,XX @@ bool migrate_params_check(MigrationParameters *params, Error **errp) return false; } + if (params->has_x_rdma_chunk_size && + (params->x_rdma_chunk_size < MiB || + params->x_rdma_chunk_size > GiB || + !is_power_of_2(params->x_rdma_chunk_size))) { + error_setg(errp, "Option x_rdma_chunk_size expects " + "a power of 2 in the range 1MiB to 1024MiB"); + return false; + } + return true; } @@ -XXX,XX +XXX,XX @@ static void migrate_params_test_apply(MigrationParameters *params, dest->direct_io = params->direct_io; } + if (params->has_x_rdma_chunk_size) { + dest->x_rdma_chunk_size = params->x_rdma_chunk_size; + } + if (params->has_cpr_exec_command) { qapi_free_strList(dest->cpr_exec_command); dest->cpr_exec_command = QAPI_CLONE(strList, params->cpr_exec_command); @@ -XXX,XX +XXX,XX @@ static void migrate_params_apply(MigrationParameters *params) s->parameters.direct_io = params->direct_io; } + if (params->has_x_rdma_chunk_size) { + s->parameters.x_rdma_chunk_size = params->x_rdma_chunk_size; + } + if (params->has_cpr_exec_command) { qapi_free_strList(s->parameters.cpr_exec_command); s->parameters.cpr_exec_command = diff --git a/migration/rdma.c b/migration/rdma.c index XXXXXXX..XXXXXXX 100644 --- a/migration/rdma.c +++ b/migration/rdma.c @@ -XXX,XX +XXX,XX @@ #define RDMA_RESOLVE_TIMEOUT_MS 10000 /* Do not merge data if larger than this. */ -#define RDMA_MERGE_MAX (2 * 1024 * 1024) -#define RDMA_SIGNALED_SEND_MAX (RDMA_MERGE_MAX / 4096) +static inline uint64_t rdma_merge_max(void) +{ + return migrate_rdma_chunk_size() * 2; +} -#define RDMA_REG_CHUNK_SHIFT 20 /* 1 MB */ +#define RDMA_SIGNALED_SEND_MAX 512 /* * This is only for non-live state being migrated. @@ -XXX,XX +XXX,XX @@ static int qemu_rdma_exchange_send(RDMAContext *rdma, RDMAControlHeader *head, static inline uint64_t ram_chunk_index(const uint8_t *start, const uint8_t *host) { - return ((uintptr_t) host - (uintptr_t) start) >> RDMA_REG_CHUNK_SHIFT; + return ((uintptr_t) host - (uintptr_t) start) / migrate_rdma_chunk_size(); } static inline uint8_t *ram_chunk_start(const RDMALocalBlock *rdma_ram_block, uint64_t i) { return (uint8_t *)(uintptr_t)(rdma_ram_block->local_host_addr + - (i << RDMA_REG_CHUNK_SHIFT)); + (i * migrate_rdma_chunk_size())); } static inline uint8_t *ram_chunk_end(const RDMALocalBlock *rdma_ram_block, uint64_t i) { uint8_t *result = ram_chunk_start(rdma_ram_block, i) + - (1UL << RDMA_REG_CHUNK_SHIFT); + migrate_rdma_chunk_size(); if (result > (rdma_ram_block->local_host_addr + rdma_ram_block->length)) { result = rdma_ram_block->local_host_addr + rdma_ram_block->length; @@ -XXX,XX +XXX,XX @@ static int qemu_rdma_write_one(RDMAContext *rdma, struct ibv_send_wr *bad_wr; int reg_result_idx, ret, count = 0; uint64_t chunk, chunks; + uint64_t chunk_size = migrate_rdma_chunk_size(); uint8_t *chunk_start, *chunk_end; RDMALocalBlock *block = &(rdma->local_ram_blocks.block[current_index]); RDMARegister reg; @@ -XXX,XX +XXX,XX @@ retry: chunk_start = ram_chunk_start(block, chunk); if (block->is_ram_block) { - chunks = length / (1UL << RDMA_REG_CHUNK_SHIFT); + chunks = length / chunk_size; - if (chunks && ((length % (1UL << RDMA_REG_CHUNK_SHIFT)) == 0)) { + if (chunks && ((length % chunk_size) == 0)) { chunks--; } } else { - chunks = block->length / (1UL << RDMA_REG_CHUNK_SHIFT); + chunks = block->length / chunk_size; - if (chunks && ((block->length % (1UL << RDMA_REG_CHUNK_SHIFT)) == 0)) { + if (chunks && ((block->length % chunk_size) == 0)) { chunks--; } } trace_qemu_rdma_write_one_top(chunks + 1, - (chunks + 1) * - (1UL << RDMA_REG_CHUNK_SHIFT) / 1024 / 1024); + (chunks + 1) * chunk_size / 1024 / 1024); chunk_end = ram_chunk_end(block, chunk + chunks); @@ -XXX,XX +XXX,XX @@ static int qemu_rdma_write(RDMAContext *rdma, rdma->current_length += len; /* flush it if buffer is too large */ - if (rdma->current_length >= RDMA_MERGE_MAX) { + if (rdma->current_length >= rdma_merge_max()) { return qemu_rdma_write_flush(rdma, errp); } @@ -XXX,XX +XXX,XX @@ int rdma_registration_handle(QEMUFile *f) } else { chunk = reg->key.chunk; host_addr = block->local_host_addr + - (reg->key.chunk * (1UL << RDMA_REG_CHUNK_SHIFT)); + (reg->key.chunk * migrate_rdma_chunk_size()); /* Check for particularly bad chunk value */ if (host_addr < (void *)block->local_host_addr) { error_report("rdma: bad chunk for block %s" -- 2.53.0
When QEMU queried the estimated version of pending data and thinks it's ready to converge, it'll send another accurate query to make sure of it. It is needed to make sure we collect the latest reports and that equation still holds true. However we missed one tiny little difference here on "<" v.s. "<=" when comparing pending_size (A) to threshold_size (B).. QEMU src only re-query if A<B, but will kickoff switchover if A<=B. I think it means it is possible to happen if A (as an estimate only so far) accidentally equals to B, then re-query won't happen and switchover will proceed without considering new dirtied data. It turns out it was an accident in my commit 7aaa1fc072 when refactoring the code around. Fix this by using the same equation in both places. Fixes: 7aaa1fc072 ("migration: Rewrite the migration complete detect logic") Cc: qemu-stable@nongnu.org Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-3-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) * postcopy started, so ESTIMATE should always match with EXACT * during postcopy phase. */ - if (pending_size < s->threshold_size) { + if (pending_size <= s->threshold_size) { qemu_savevm_state_pending_exact(&must_precopy, &can_postcopy); pending_size = must_precopy + can_postcopy; trace_migrate_pending_exact(pending_size, must_precopy, -- 2.53.0
This stats is only about RAM, make it accurate. This paves way for statistics for all devices. Thanks to Markus, who pointed out that docs/devel/qapi-code-gen.rst has a section "Compatibility considerations" stated: Since type names are not visible in the Client JSON Protocol, types may be freely renamed. Even certain refactorings are invisible, such as splitting members from one type into a common base type. Hence this change is not ABI violation according to the document. While at it, touch up the lines to make it read better, correct the restriction on migration status being 'active' or 'completed': over time we grew too many new status that will also report "ram" section. Cc: Daniel P. Berrangé <berrange@redhat.com> Cc: devel@lists.libvirt.org Reviewed-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Michal Privoznik <mprivozn@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-4-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- docs/about/removed-features.rst | 2 +- qapi/migration.json | 10 +++++----- migration/migration-stats.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/about/removed-features.rst b/docs/about/removed-features.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/about/removed-features.rst +++ b/docs/about/removed-features.rst @@ -XXX,XX +XXX,XX @@ was superseded by ``sections``. ``query-migrate`` return value member ``skipped`` (removed in 9.1) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -Member ``skipped`` of the ``MigrationStats`` struct hasn't been used +Member ``skipped`` of the ``MigrationRAMStats`` struct hasn't been used for more than 10 years. Removed with no replacement. ``migrate`` command option ``inc`` (removed in 9.1) diff --git a/qapi/migration.json b/qapi/migration.json index XXXXXXX..XXXXXXX 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -XXX,XX +XXX,XX @@ { 'include': 'sockets.json' } ## -# @MigrationStats: +# @MigrationRAMStats: # # Detailed migration status. # @@ -XXX,XX +XXX,XX @@ # # Since: 0.14 ## -{ 'struct': 'MigrationStats', +{ 'struct': 'MigrationRAMStats', 'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' , 'duplicate': 'int', 'normal': 'int', @@ -XXX,XX +XXX,XX @@ # If this field is not returned, no migration process has been # initiated # -# @ram: `MigrationStats` containing detailed migration status, only -# returned if status is 'active' or 'completed'(since 1.2) +# @ram: Detailed migration RAM statistics, only returned if migration +# is in progress or completed (since 1.2) # # @xbzrle-cache: `XBZRLECacheStats` containing detailed XBZRLE # migration statistics, only returned if XBZRLE feature is on and @@ -XXX,XX +XXX,XX @@ # Since: 0.14 ## { 'struct': 'MigrationInfo', - 'data': {'*status': 'MigrationStatus', '*ram': 'MigrationStats', + 'data': {'*status': 'MigrationStatus', '*ram': 'MigrationRAMStats', '*vfio': 'VfioStats', '*xbzrle-cache': 'XBZRLECacheStats', '*total-time': 'int', diff --git a/migration/migration-stats.h b/migration/migration-stats.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-stats.h +++ b/migration/migration-stats.h @@ -XXX,XX +XXX,XX @@ /* * These are the ram migration statistic counters. It is loosely - * based on MigrationStats. + * based on MigrationRAMStats. */ typedef struct { /* -- 2.53.0
Add a field to cache stop size. Note that there's an initial value change in vfio_save_setup for the stop size default, but it shouldn't matter if it is followed with a math of MIN() against VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE. Document that all the three sizes we read from VFIO's uAPI on dirty or stop sizes are estimates, so QEMU needs to always remember they can be anything. Reviewed-by: Avihai Horon <avihaih@nvidia.com> Link: https://lore.kernel.org/r/20260421202110.306051-5-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/vfio/vfio-migration-internal.h | 8 +++++ hw/vfio/migration.c | 50 ++++++++++++++++++------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/hw/vfio/vfio-migration-internal.h b/hw/vfio/vfio-migration-internal.h index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/vfio-migration-internal.h +++ b/hw/vfio/vfio-migration-internal.h @@ -XXX,XX +XXX,XX @@ typedef struct VFIOMigration { void *data_buffer; size_t data_buffer_size; uint64_t mig_flags; + /* + * NOTE: all three sizes cached are reported from VFIO's uAPI, which + * are defined as estimate only. QEMU should not trust these values + * but only use them to do best-effort estimates. Always be prepared + * that these sizes may either grow or even shrink in reality while + * read()ing from the VFIO fds. + */ uint64_t precopy_init_size; uint64_t precopy_dirty_size; + uint64_t stopcopy_size; bool multifd_transfer; VFIOMultifd *multifd; bool initial_data_sent; diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -XXX,XX +XXX,XX @@ */ #define VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE (1 * MiB) +/* + * Migration size of VFIO devices can be as little as a few KBs or as big as + * many GBs. This value should be big enough to cover the worst case. + */ +#define VFIO_MIG_STOP_COPY_SIZE (100 * GiB) + static unsigned long bytes_transferred; static const char *mig_state_to_str(enum vfio_device_mig_state state) @@ -XXX,XX +XXX,XX @@ static void vfio_migration_cleanup(VFIODevice *vbasedev) migration->data_fd = -1; } -static int vfio_query_stop_copy_size(VFIODevice *vbasedev, - uint64_t *stop_copy_size) +static int vfio_query_stop_copy_size(VFIODevice *vbasedev) { uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature) + sizeof(struct vfio_device_feature_mig_data_size), @@ -XXX,XX +XXX,XX @@ static int vfio_query_stop_copy_size(VFIODevice *vbasedev, struct vfio_device_feature *feature = (struct vfio_device_feature *)buf; struct vfio_device_feature_mig_data_size *mig_data_size = (struct vfio_device_feature_mig_data_size *)feature->data; + VFIOMigration *migration = vbasedev->migration; feature->argsz = sizeof(buf); feature->flags = VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_MIG_DATA_SIZE; if (ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature)) { + /* + * If getting pending migration size fails, VFIO_MIG_STOP_COPY_SIZE + * is reported so downtime limit won't be violated. + */ + migration->stopcopy_size = VFIO_MIG_STOP_COPY_SIZE; return -errno; } - *stop_copy_size = mig_data_size->stop_copy_length; + migration->stopcopy_size = mig_data_size->stop_copy_length; return 0; } @@ -XXX,XX +XXX,XX @@ static void vfio_update_estimated_pending_data(VFIOMigration *migration, return; } + /* + * The total size remaining requires separate accounting. Do not trust + * the counter, so what we have read() may be more than what reported. + */ + if (migration->stopcopy_size > data_size) { + migration->stopcopy_size -= data_size; + } else { + migration->stopcopy_size = 0; + } + if (migration->precopy_init_size) { uint64_t init_size = MIN(migration->precopy_init_size, data_size); @@ -XXX,XX +XXX,XX @@ static int vfio_save_setup(QEMUFile *f, void *opaque, Error **errp) { VFIODevice *vbasedev = opaque; VFIOMigration *migration = vbasedev->migration; - uint64_t stop_copy_size = VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE; int ret; if (!vfio_multifd_setup(vbasedev, false, errp)) { @@ -XXX,XX +XXX,XX @@ static int vfio_save_setup(QEMUFile *f, void *opaque, Error **errp) qemu_put_be64(f, VFIO_MIG_FLAG_DEV_SETUP_STATE); - vfio_query_stop_copy_size(vbasedev, &stop_copy_size); + vfio_query_stop_copy_size(vbasedev); migration->data_buffer_size = MIN(VFIO_MIG_DEFAULT_DATA_BUFFER_SIZE, - stop_copy_size); + migration->stopcopy_size); migration->data_buffer = g_try_malloc0(migration->data_buffer_size); if (!migration->data_buffer) { error_setg(errp, "%s: Failed to allocate migration data buffer", @@ -XXX,XX +XXX,XX @@ static void vfio_state_pending_estimate(void *opaque, uint64_t *must_precopy, migration->precopy_dirty_size); } -/* - * Migration size of VFIO devices can be as little as a few KBs or as big as - * many GBs. This value should be big enough to cover the worst case. - */ -#define VFIO_MIG_STOP_COPY_SIZE (100 * GiB) - static void vfio_state_pending_exact(void *opaque, uint64_t *must_precopy, uint64_t *can_postcopy) { VFIODevice *vbasedev = opaque; VFIOMigration *migration = vbasedev->migration; - uint64_t stop_copy_size = VFIO_MIG_STOP_COPY_SIZE; - /* - * If getting pending migration size fails, VFIO_MIG_STOP_COPY_SIZE is - * reported so downtime limit won't be violated. - */ - vfio_query_stop_copy_size(vbasedev, &stop_copy_size); - *must_precopy += stop_copy_size; + vfio_query_stop_copy_size(vbasedev); + *must_precopy += migration->stopcopy_size; if (vfio_device_state_is_precopy(vbasedev)) { vfio_query_precopy_size(migration); } trace_vfio_state_pending_exact(vbasedev->name, *must_precopy, *can_postcopy, - stop_copy_size, migration->precopy_init_size, + migration->stopcopy_size, + migration->precopy_init_size, migration->precopy_dirty_size); } -- 2.53.0
These two APIs are a slight duplication. For example, there're a few users that directly pass in the same function. It might also be error prone to provide two hooks, so that it's easier to happen one module report different things via the two hooks. In reality, they should always report the same thing, only about whether we should use a fast-path when the slow path might be too slow, as QEMU may query these information quite frequently during migration process. Merge it into one API, provide a bool showing if the query is an exact query or not. No functional change intended. Export qemu_savevm_query_pending(). We should use the new API here provided when there're new users to do the query. This will happen very soon. Cc: Halil Pasic <pasic@linux.ibm.com> Cc: Christian Borntraeger <borntraeger@linux.ibm.com> Cc: Eric Farman <farman@linux.ibm.com> Cc: Matthew Rosato <mjrosato@linux.ibm.com> Cc: Richard Henderson <richard.henderson@linaro.org> Cc: Ilya Leoshkevich <iii@linux.ibm.com> Cc: David Hildenbrand <david@kernel.org> Cc: Cornelia Huck <cohuck@redhat.com> Cc: Eric Blake <eblake@redhat.com> Cc: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Cc: John Snow <jsnow@redhat.com> Reviewed-by: Jason J. Herne <jjherne@linux.ibm.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Avihai Horon <avihaih@nvidia.com> Acked-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Link: https://lore.kernel.org/r/20260421202110.306051-6-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- docs/devel/migration/main.rst | 9 ++---- docs/devel/migration/vfio.rst | 9 ++---- include/migration/register.h | 52 ++++++++++++---------------------- migration/savevm.h | 3 ++ hw/s390x/s390-stattrib.c | 9 +++--- hw/vfio/migration.c | 48 ++++++++++++++----------------- migration/block-dirty-bitmap.c | 10 +++---- migration/ram.c | 33 +++++++-------------- migration/savevm.c | 42 +++++++++++++-------------- hw/vfio/trace-events | 3 +- 10 files changed, 86 insertions(+), 132 deletions(-) diff --git a/docs/devel/migration/main.rst b/docs/devel/migration/main.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/devel/migration/main.rst +++ b/docs/devel/migration/main.rst @@ -XXX,XX +XXX,XX @@ An iterative device must provide: - A ``load_setup`` function that initialises the data structures on the destination. - - A ``state_pending_exact`` function that indicates how much more - data we must save. The core migration code will use this to - determine when to pause the CPUs and complete the migration. - - - A ``state_pending_estimate`` function that indicates how much more - data we must save. When the estimated amount is smaller than the - threshold, we call ``state_pending_exact``. + - A ``save_query_pending`` function that indicates how much more + data we must save. - A ``save_live_iterate`` function should send a chunk of data until the point that stream bandwidth limits tell it to stop. Each call diff --git a/docs/devel/migration/vfio.rst b/docs/devel/migration/vfio.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/devel/migration/vfio.rst +++ b/docs/devel/migration/vfio.rst @@ -XXX,XX +XXX,XX @@ VFIO implements the device hooks for the iterative approach as follows: * A ``load_setup`` function that sets the VFIO device on the destination in _RESUMING state. -* A ``state_pending_estimate`` function that reports an estimate of the - remaining pre-copy data that the vendor driver has yet to save for the VFIO - device. - -* A ``state_pending_exact`` function that reads pending_bytes from the vendor - driver, which indicates the amount of data that the vendor driver has yet to - save for the VFIO device. +* A ``save_query_pending`` function that reports the remaining data that + the vendor driver has yet to save for the VFIO device. * An ``is_active_iterate`` function that indicates ``save_live_iterate`` is active only when the VFIO device is in pre-copy states. diff --git a/include/migration/register.h b/include/migration/register.h index XXXXXXX..XXXXXXX 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -XXX,XX +XXX,XX @@ #include "hw/core/vmstate-if.h" +typedef struct MigPendingData { + /* Amount of pending bytes can be transferred in precopy or stopcopy */ + uint64_t precopy_bytes; + /* Amount of pending bytes can be transferred in postcopy */ + uint64_t postcopy_bytes; +} MigPendingData; + /** * struct SaveVMHandlers: handler structure to finely control * migration of complex subsystems and devices, such as RAM, block and @@ -XXX,XX +XXX,XX @@ typedef struct SaveVMHandlers { bool (*save_postcopy_prepare)(QEMUFile *f, void *opaque, Error **errp); /** - * @state_pending_estimate - * - * This estimates the remaining data to transfer + * @save_query_pending * - * Sum of @can_postcopy and @must_postcopy is the whole amount of - * pending data. - * - * @opaque: data pointer passed to register_savevm_live() - * @must_precopy: amount of data that must be migrated in precopy - * or in stopped state, i.e. that must be migrated - * before target start. - * @can_postcopy: amount of data that can be migrated in postcopy - * or in stopped state, i.e. after target start. - * Some can also be migrated during precopy (RAM). - * Some must be migrated after source stops - * (block-dirty-bitmap) - */ - void (*state_pending_estimate)(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy); - - /** - * @state_pending_exact + * This estimates the remaining data to transfer on the source side. * - * This calculates the exact remaining data to transfer + * When @exact is true, a module must report accurate results. When + * @exact is false, a module may report estimates. * - * Sum of @can_postcopy and @must_postcopy is the whole amount of - * pending data. + * It's highly recommended that modules implement a faster version of + * the query path (for example, by proper caching on the counters) if + * an accurate query will be time-consuming. * * @opaque: data pointer passed to register_savevm_live() - * @must_precopy: amount of data that must be migrated in precopy - * or in stopped state, i.e. that must be migrated - * before target start. - * @can_postcopy: amount of data that can be migrated in postcopy - * or in stopped state, i.e. after target start. - * Some can also be migrated during precopy (RAM). - * Some must be migrated after source stops - * (block-dirty-bitmap) + * @pending: pointer to a MigPendingData struct + * @exact: set to true for an accurate (slow) query */ - void (*state_pending_exact)(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy); + void (*save_query_pending)(void *opaque, MigPendingData *pending, + bool exact); /** * @load_state diff --git a/migration/savevm.h b/migration/savevm.h index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -XXX,XX +XXX,XX @@ #ifndef MIGRATION_SAVEVM_H #define MIGRATION_SAVEVM_H +#include "migration/register.h" + #define QEMU_VM_FILE_MAGIC 0x5145564d #define QEMU_VM_FILE_VERSION_COMPAT 0x00000002 #define QEMU_VM_FILE_VERSION 0x00000003 @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_iterate(QEMUFile *f, bool postcopy); void qemu_savevm_state_cleanup(void); void qemu_savevm_state_complete_postcopy(QEMUFile *f); int qemu_savevm_state_complete_precopy(MigrationState *s); +void qemu_savevm_query_pending(MigPendingData *pending, bool exact); void qemu_savevm_state_pending_exact(uint64_t *must_precopy, uint64_t *can_postcopy); void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, diff --git a/hw/s390x/s390-stattrib.c b/hw/s390x/s390-stattrib.c index XXXXXXX..XXXXXXX 100644 --- a/hw/s390x/s390-stattrib.c +++ b/hw/s390x/s390-stattrib.c @@ -XXX,XX +XXX,XX @@ static int cmma_save_setup(QEMUFile *f, void *opaque, Error **errp) return 0; } -static void cmma_state_pending(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy) +static void cmma_state_pending(void *opaque, MigPendingData *pending, + bool exact) { S390StAttribState *sas = S390_STATTRIB(opaque); S390StAttribClass *sac = S390_STATTRIB_GET_CLASS(sas); long long res = sac->get_dirtycount(sas); if (res >= 0) { - *must_precopy += res; + pending->precopy_bytes += res; } } @@ -XXX,XX +XXX,XX @@ static SaveVMHandlers savevm_s390_stattrib_handlers = { .save_setup = cmma_save_setup, .save_live_iterate = cmma_save_iterate, .save_complete = cmma_save_complete, - .state_pending_exact = cmma_state_pending, - .state_pending_estimate = cmma_state_pending, + .save_query_pending = cmma_state_pending, .save_cleanup = cmma_save_cleanup, .load_state = cmma_load, .is_active = cmma_active, diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -XXX,XX +XXX,XX @@ static void vfio_save_cleanup(void *opaque) trace_vfio_save_cleanup(vbasedev->name); } -static void vfio_state_pending_estimate(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy) +static void vfio_state_pending_sync(VFIODevice *vbasedev) { - VFIODevice *vbasedev = opaque; VFIOMigration *migration = vbasedev->migration; - if (!vfio_device_state_is_precopy(vbasedev)) { - return; - } - - *must_precopy += - migration->precopy_init_size + migration->precopy_dirty_size; + vfio_query_stop_copy_size(vbasedev); - trace_vfio_state_pending_estimate(vbasedev->name, *must_precopy, - *can_postcopy, - migration->precopy_init_size, - migration->precopy_dirty_size); + if (vfio_device_state_is_precopy(vbasedev)) { + vfio_query_precopy_size(migration); + } } -static void vfio_state_pending_exact(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy) +static void vfio_state_pending(void *opaque, MigPendingData *pending, + bool exact) { VFIODevice *vbasedev = opaque; VFIOMigration *migration = vbasedev->migration; + uint64_t remain; - vfio_query_stop_copy_size(vbasedev); - *must_precopy += migration->stopcopy_size; - - if (vfio_device_state_is_precopy(vbasedev)) { - vfio_query_precopy_size(migration); + if (exact) { + vfio_state_pending_sync(vbasedev); + remain = migration->stopcopy_size; + } else { + if (!vfio_device_state_is_precopy(vbasedev)) { + return; + } + remain = migration->precopy_init_size + migration->precopy_dirty_size; } - trace_vfio_state_pending_exact(vbasedev->name, *must_precopy, *can_postcopy, - migration->stopcopy_size, - migration->precopy_init_size, - migration->precopy_dirty_size); + pending->precopy_bytes += remain; + + trace_vfio_state_pending(vbasedev->name, migration->stopcopy_size, + migration->precopy_init_size, + migration->precopy_dirty_size, exact); } static bool vfio_is_active_iterate(void *opaque) @@ -XXX,XX +XXX,XX @@ static const SaveVMHandlers savevm_vfio_handlers = { .save_prepare = vfio_save_prepare, .save_setup = vfio_save_setup, .save_cleanup = vfio_save_cleanup, - .state_pending_estimate = vfio_state_pending_estimate, - .state_pending_exact = vfio_state_pending_exact, + .save_query_pending = vfio_state_pending, .is_active_iterate = vfio_is_active_iterate, .save_live_iterate = vfio_save_iterate, .save_complete = vfio_save_complete_precopy, diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c index XXXXXXX..XXXXXXX 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c @@ -XXX,XX +XXX,XX @@ static int dirty_bitmap_save_complete(QEMUFile *f, void *opaque) return 0; } -static void dirty_bitmap_state_pending(void *opaque, - uint64_t *must_precopy, - uint64_t *can_postcopy) +static void dirty_bitmap_state_pending(void *opaque, MigPendingData *data, + bool exact) { DBMSaveState *s = &((DBMState *)opaque)->save; SaveBitmapState *dbms; @@ -XXX,XX +XXX,XX @@ static void dirty_bitmap_state_pending(void *opaque, trace_dirty_bitmap_state_pending(pending); - *can_postcopy += pending; + data->postcopy_bytes += pending; } /* First occurrence of this bitmap. It should be created if doesn't exist */ @@ -XXX,XX +XXX,XX @@ static SaveVMHandlers savevm_dirty_bitmap_handlers = { .save_setup = dirty_bitmap_save_setup, .save_complete = dirty_bitmap_save_complete, .has_postcopy = dirty_bitmap_has_postcopy, - .state_pending_exact = dirty_bitmap_state_pending, - .state_pending_estimate = dirty_bitmap_state_pending, + .save_query_pending = dirty_bitmap_state_pending, .save_live_iterate = dirty_bitmap_save_iterate, .is_active_iterate = dirty_bitmap_is_active_iterate, .load_state = dirty_bitmap_load, diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static int ram_save_complete(QEMUFile *f, void *opaque) return qemu_fflush(f); } -static void ram_state_pending_estimate(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy) -{ - RAMState **temp = opaque; - RAMState *rs = *temp; - - uint64_t remaining_size = rs->migration_dirty_pages * TARGET_PAGE_SIZE; - - if (migrate_postcopy_ram()) { - /* We can do postcopy, and all the data is postcopiable */ - *can_postcopy += remaining_size; - } else { - *must_precopy += remaining_size; - } -} - -static void ram_state_pending_exact(void *opaque, uint64_t *must_precopy, - uint64_t *can_postcopy) +static void ram_state_pending(void *opaque, MigPendingData *pending, + bool exact) { RAMState **temp = opaque; RAMState *rs = *temp; uint64_t remaining_size; - if (!migration_in_postcopy()) { + /* + * Sync is not needed either with: (1) a fast query, or (2) after + * postcopy has started (no new dirty will generate anymore). + */ + if (exact && !migration_in_postcopy()) { bql_lock(); WITH_RCU_READ_LOCK_GUARD() { migration_bitmap_sync_precopy(false); @@ -XXX,XX +XXX,XX @@ static void ram_state_pending_exact(void *opaque, uint64_t *must_precopy, if (migrate_postcopy_ram()) { /* We can do postcopy, and all the data is postcopiable */ - *can_postcopy += remaining_size; + pending->postcopy_bytes += remaining_size; } else { - *must_precopy += remaining_size; + pending->precopy_bytes += remaining_size; } } @@ -XXX,XX +XXX,XX @@ static SaveVMHandlers savevm_ram_handlers = { .save_live_iterate = ram_save_iterate, .save_complete = ram_save_complete, .has_postcopy = ram_has_postcopy, - .state_pending_exact = ram_state_pending_exact, - .state_pending_estimate = ram_state_pending_estimate, + .save_query_pending = ram_state_pending, .load_state = ram_load, .save_cleanup = ram_save_cleanup, .load_setup = ram_load_setup, diff --git a/migration/savevm.c b/migration/savevm.c index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_complete_precopy(MigrationState *s) return qemu_fflush(f); } -/* Give an estimate of the amount left to be transferred, - * the result is split into the amount for units that can and - * for units that can't do postcopy. - */ -void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, - uint64_t *can_postcopy) +void qemu_savevm_query_pending(MigPendingData *pending, bool exact) { SaveStateEntry *se; - *must_precopy = 0; - *can_postcopy = 0; + pending->precopy_bytes = 0; + pending->postcopy_bytes = 0; QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { - if (!se->ops || !se->ops->state_pending_estimate) { + if (!se->ops || !se->ops->save_query_pending) { continue; } if (!qemu_savevm_state_active(se)) { continue; } - se->ops->state_pending_estimate(se->opaque, must_precopy, can_postcopy); + se->ops->save_query_pending(se->opaque, pending, exact); } } +void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, + uint64_t *can_postcopy) +{ + MigPendingData pending; + + qemu_savevm_query_pending(&pending, false); + + *must_precopy = pending.precopy_bytes; + *can_postcopy = pending.postcopy_bytes; +} + void qemu_savevm_state_pending_exact(uint64_t *must_precopy, uint64_t *can_postcopy) { - SaveStateEntry *se; + MigPendingData pending; - *must_precopy = 0; - *can_postcopy = 0; + qemu_savevm_query_pending(&pending, true); - QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { - if (!se->ops || !se->ops->state_pending_exact) { - continue; - } - if (!qemu_savevm_state_active(se)) { - continue; - } - se->ops->state_pending_exact(se->opaque, must_precopy, can_postcopy); - } + *must_precopy = pending.precopy_bytes; + *can_postcopy = pending.postcopy_bytes; } void qemu_savevm_state_cleanup(void) diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -XXX,XX +XXX,XX @@ vfio_save_device_config_state(const char *name) " (%s)" vfio_save_iterate(const char *name, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy initial size %"PRIu64" precopy dirty size %"PRIu64 vfio_save_iterate_start(const char *name) " (%s)" vfio_save_setup(const char *name, uint64_t data_buffer_size) " (%s) data buffer size %"PRIu64 -vfio_state_pending_estimate(const char *name, uint64_t precopy, uint64_t postcopy, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy %"PRIu64" postcopy %"PRIu64" precopy initial size %"PRIu64" precopy dirty size %"PRIu64 -vfio_state_pending_exact(const char *name, uint64_t precopy, uint64_t postcopy, uint64_t stopcopy_size, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy %"PRIu64" postcopy %"PRIu64" stopcopy size %"PRIu64" precopy initial size %"PRIu64" precopy dirty size %"PRIu64 +vfio_state_pending(const char *name, uint64_t stopcopy_size, uint64_t precopy_init_size, uint64_t precopy_dirty_size, bool exact) " (%s) stopcopy size %"PRIu64" precopy initial size %"PRIu64" precopy dirty size %"PRIu64 " exact %d" vfio_vmstate_change(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s" vfio_vmstate_change_prepare(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s" -- 2.53.0
It's easier to use the new API directly in the migration iterations. This also paves way for follow up patches to add new data to report directly to the iterator function. When at it, merge the tracepoints too into one. No functional change intended. Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Avihai Horon <avihaih@nvidia.com> Link: https://lore.kernel.org/r/20260421202110.306051-7-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/savevm.h | 4 ---- migration/migration.c | 16 +++++++--------- migration/savevm.c | 23 ++--------------------- migration/trace-events | 3 +-- 4 files changed, 10 insertions(+), 36 deletions(-) diff --git a/migration/savevm.h b/migration/savevm.h index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.h +++ b/migration/savevm.h @@ -XXX,XX +XXX,XX @@ void qemu_savevm_state_cleanup(void); void qemu_savevm_state_complete_postcopy(QEMUFile *f); int qemu_savevm_state_complete_precopy(MigrationState *s); void qemu_savevm_query_pending(MigPendingData *pending, bool exact); -void qemu_savevm_state_pending_exact(uint64_t *must_precopy, - uint64_t *can_postcopy); -void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, - uint64_t *can_postcopy); int qemu_savevm_state_complete_precopy_iterable(QEMUFile *f, bool in_postcopy); bool qemu_savevm_state_postcopy_prepare(QEMUFile *f, Error **errp); void qemu_savevm_state_end(QEMUFile *f); diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ typedef enum { */ static MigIterateState migration_iteration_run(MigrationState *s) { - uint64_t must_precopy, can_postcopy, pending_size; Error *local_err = NULL; bool in_postcopy = (s->state == MIGRATION_STATUS_POSTCOPY_DEVICE || s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE); bool can_switchover = migration_can_switchover(s); + MigPendingData pending = { }; + uint64_t pending_size; bool complete_ready; /* Fast path - get the estimated amount of pending data */ - qemu_savevm_state_pending_estimate(&must_precopy, &can_postcopy); - pending_size = must_precopy + can_postcopy; - trace_migrate_pending_estimate(pending_size, must_precopy, can_postcopy); + qemu_savevm_query_pending(&pending, false); + pending_size = pending.precopy_bytes + pending.postcopy_bytes; if (in_postcopy) { /* @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) * during postcopy phase. */ if (pending_size <= s->threshold_size) { - qemu_savevm_state_pending_exact(&must_precopy, &can_postcopy); - pending_size = must_precopy + can_postcopy; - trace_migrate_pending_exact(pending_size, must_precopy, - can_postcopy); + qemu_savevm_query_pending(&pending, true); + pending_size = pending.precopy_bytes + pending.postcopy_bytes; } /* Should we switch to postcopy now? */ - if (must_precopy <= s->threshold_size && + if (pending.precopy_bytes <= s->threshold_size && can_switchover && qatomic_read(&s->start_postcopy)) { if (postcopy_start(s, &local_err)) { migrate_error_propagate(s, error_copy(local_err)); diff --git a/migration/savevm.c b/migration/savevm.c index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -XXX,XX +XXX,XX @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact) } se->ops->save_query_pending(se->opaque, pending, exact); } -} - -void qemu_savevm_state_pending_estimate(uint64_t *must_precopy, - uint64_t *can_postcopy) -{ - MigPendingData pending; - - qemu_savevm_query_pending(&pending, false); - - *must_precopy = pending.precopy_bytes; - *can_postcopy = pending.postcopy_bytes; -} - -void qemu_savevm_state_pending_exact(uint64_t *must_precopy, - uint64_t *can_postcopy) -{ - MigPendingData pending; - - qemu_savevm_query_pending(&pending, true); - *must_precopy = pending.precopy_bytes; - *can_postcopy = pending.postcopy_bytes; + trace_qemu_savevm_query_pending(exact, pending->precopy_bytes, + pending->postcopy_bytes); } void qemu_savevm_state_cleanup(void) diff --git a/migration/trace-events b/migration/trace-events index XXXXXXX..XXXXXXX 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -XXX,XX +XXX,XX @@ qemu_loadvm_state_section_partend(uint32_t section_id) "%u" qemu_loadvm_state_post_main(int ret) "%d" qemu_loadvm_state_section_startfull(uint32_t section_id, const char *idstr, uint32_t instance_id, uint32_t version_id) "%u(%s) %u %u" qemu_savevm_send_packaged(void) "" +qemu_savevm_query_pending(bool exact, uint64_t precopy, uint64_t postcopy) "exact=%d, precopy=%"PRIu64", postcopy=%"PRIu64 loadvm_state_switchover_ack_needed(unsigned int switchover_ack_pending_num) "Switchover ack pending num=%u" loadvm_state_setup(void) "" loadvm_state_cleanup(void) "" @@ -XXX,XX +XXX,XX @@ migration_cleanup(void) "" migrate_error(const char *error_desc) "error=%s" migration_cancel(void) "" migrate_handle_rp_req_pages(const char *rbname, size_t start, size_t len) "in %s at 0x%zx len 0x%zx" -migrate_pending_exact(uint64_t size, uint64_t pre, uint64_t post) "exact pending size %" PRIu64 " (pre = %" PRIu64 " post=%" PRIu64 ")" -migrate_pending_estimate(uint64_t size, uint64_t pre, uint64_t post) "estimate pending size %" PRIu64 " (pre = %" PRIu64 " post=%" PRIu64 ")" migrate_send_rp_message(int msg_type, uint16_t len) "%d: len %d" migrate_send_rp_recv_bitmap(char *name, int64_t size) "block '%s' size 0x%"PRIi64 migration_completion_file_err(void) "" -- 2.53.0
Allow modules to report data that can only be migrated after VM is stopped. When this concept is introduced, we will need to account stopcopy size to be part of pending_size as before. However, when there're data only can be migrated in stopcopy phase, it means the old "pending_size" may not always be able to reach low enough to kickoff an slow version of query sync. It used to be almost guaranteed to happen as all prior iterative modules doesn't have stopcopy only data. VFIO may change that fact by having some data that must be copied during stop phase. So we need to make sure QEMU will kickoff a synchronized version of query pending when all precopy data is migrated. This might be important to VFIO to keep making progress even if the downtime cannot yet be satisfied. So far, this patch should introduce no functional change, as no module yet report stopcopy size. This paves way for VFIO to properly report its pending data sizes, which will start to include stop-only data. Reviewed-by: Avihai Horon <avihaih@nvidia.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-8-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/migration/register.h | 7 ++++ migration/migration.c | 65 ++++++++++++++++++++++++++++++------ migration/savevm.c | 10 ++++-- migration/trace-events | 2 +- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/include/migration/register.h b/include/migration/register.h index XXXXXXX..XXXXXXX 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -XXX,XX +XXX,XX @@ typedef struct MigPendingData { uint64_t precopy_bytes; /* Amount of pending bytes can be transferred in postcopy */ uint64_t postcopy_bytes; + /* Amount of pending bytes can be transferred only in stopcopy */ + uint64_t stopcopy_bytes; + /* + * Total pending data, modules do not need to update this field, it + * will be automatically calculated by migration core API. + */ + uint64_t total_bytes; } MigPendingData; /** diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ typedef enum { MIG_ITERATE_BREAK, /* Break the loop */ } MigIterateState; +/* Are we ready to move to the next iteration phase? */ +static bool migration_iteration_next_ready(MigrationState *s, + MigPendingData *pending) +{ + /* + * If the estimated values already suggest us to switchover, mark this + * iteration finished, time to do a slow sync. + */ + if (pending->total_bytes <= s->threshold_size) { + return true; + } + + /* + * Since we may have modules reporting stop-only data, we also want to + * re-query with slow mode if all precopy data is moved over. This + * will also mark the current iteration done. + * + * This could happen when e.g. a module (like, VFIO) reports stopcopy + * size too large so it will never yet satisfy the downtime with the + * current setup (above check). Here, slow version of re-query helps + * because we keep trying the best to move whatever we have. + */ + if (pending->precopy_bytes == 0) { + return true; + } + + return false; +} + +static void migration_iteration_go_next(MigPendingData *pending) +{ + /* + * Do a slow sync will achieve this. TODO: move RAM iteration code + * into the core layer. + */ + qemu_savevm_query_pending(pending, true); +} + +static bool postcopy_should_start(MigrationState *s, MigPendingData *pending) +{ + /* If postcopy's switchver will violate user specified downtime, stop */ + if (pending->precopy_bytes + pending->stopcopy_bytes > s->threshold_size) { + return false; + } + + return qatomic_read(&s->start_postcopy); +} + /* * Return true if continue to the next iteration directly, false * otherwise. @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) s->state == MIGRATION_STATUS_POSTCOPY_ACTIVE); bool can_switchover = migration_can_switchover(s); MigPendingData pending = { }; - uint64_t pending_size; bool complete_ready; /* Fast path - get the estimated amount of pending data */ qemu_savevm_query_pending(&pending, false); - pending_size = pending.precopy_bytes + pending.postcopy_bytes; if (in_postcopy) { /* @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) * postcopy completion doesn't rely on can_switchover, because when * POSTCOPY_ACTIVE it means switchover already happened. */ - complete_ready = !pending_size; + complete_ready = !pending.total_bytes; if (s->state == MIGRATION_STATUS_POSTCOPY_DEVICE && (s->postcopy_package_loaded || complete_ready)) { /* @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) * postcopy started, so ESTIMATE should always match with EXACT * during postcopy phase. */ - if (pending_size <= s->threshold_size) { - qemu_savevm_query_pending(&pending, true); - pending_size = pending.precopy_bytes + pending.postcopy_bytes; + if (migration_iteration_next_ready(s, &pending)) { + migration_iteration_go_next(&pending); } /* Should we switch to postcopy now? */ - if (pending.precopy_bytes <= s->threshold_size && - can_switchover && qatomic_read(&s->start_postcopy)) { + if (can_switchover && postcopy_should_start(s, &pending)) { if (postcopy_start(s, &local_err)) { migrate_error_propagate(s, error_copy(local_err)); error_report_err(local_err); @@ -XXX,XX +XXX,XX @@ static MigIterateState migration_iteration_run(MigrationState *s) * (2) Pending size is no more than the threshold specified * (which was calculated from expected downtime) */ - complete_ready = can_switchover && (pending_size <= s->threshold_size); + complete_ready = can_switchover && + (pending.total_bytes <= s->threshold_size); } if (complete_ready) { - trace_migration_thread_low_pending(pending_size); + trace_migration_thread_low_pending(pending.total_bytes); migration_completion(s); return MIG_ITERATE_BREAK; } diff --git a/migration/savevm.c b/migration/savevm.c index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -XXX,XX +XXX,XX @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact) { SaveStateEntry *se; - pending->precopy_bytes = 0; - pending->postcopy_bytes = 0; + memset(pending, 0, sizeof(*pending)); QTAILQ_FOREACH(se, &savevm_state.handlers, entry) { if (!se->ops || !se->ops->save_query_pending) { @@ -XXX,XX +XXX,XX @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact) se->ops->save_query_pending(se->opaque, pending, exact); } + pending->total_bytes = pending->precopy_bytes + + pending->stopcopy_bytes + pending->postcopy_bytes; + trace_qemu_savevm_query_pending(exact, pending->precopy_bytes, - pending->postcopy_bytes); + pending->stopcopy_bytes, + pending->postcopy_bytes, + pending->total_bytes); } void qemu_savevm_state_cleanup(void) diff --git a/migration/trace-events b/migration/trace-events index XXXXXXX..XXXXXXX 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -XXX,XX +XXX,XX @@ qemu_loadvm_state_section_partend(uint32_t section_id) "%u" qemu_loadvm_state_post_main(int ret) "%d" qemu_loadvm_state_section_startfull(uint32_t section_id, const char *idstr, uint32_t instance_id, uint32_t version_id) "%u(%s) %u %u" qemu_savevm_send_packaged(void) "" -qemu_savevm_query_pending(bool exact, uint64_t precopy, uint64_t postcopy) "exact=%d, precopy=%"PRIu64", postcopy=%"PRIu64 +qemu_savevm_query_pending(bool exact, uint64_t precopy, uint64_t stopcopy, uint64_t postcopy, uint64_t total) "exact=%d, precopy=%"PRIu64", stopcopy=%"PRIu64", postcopy=%"PRIu64", total=%"PRIu64 loadvm_state_switchover_ack_needed(unsigned int switchover_ack_pending_num) "Switchover ack pending num=%u" loadvm_state_setup(void) "" loadvm_state_cleanup(void) "" -- 2.53.0
VFIO reports different things in its fast/slow version of query pending results. It was because it wants to make sure precopy data can reach 0, which is needed to make sure sync queries will happen periodically over time. Now with stopcopy size reporting facility it doesn't need this hack anymore. Fix this by reporting the same values in fast/slow versions of query pending request, except that the slow version will do a slow sync with the hardwares. When at it, removing the special casing for vfio_device_state_is_precopy() which may reporting nothing in a fast query. Then ther reporting will be consistent to VFIO devices that do not support precopy phase. Copy stable might be too much; just skip it and skip the Fixes. Reviewed-by: Avihai Horon <avihaih@nvidia.com> Tested-by: Avihai Horon <avihaih@nvidia.com> Link: https://lore.kernel.org/r/20260421202110.306051-9-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/vfio/migration.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -XXX,XX +XXX,XX @@ static void vfio_state_pending(void *opaque, MigPendingData *pending, { VFIODevice *vbasedev = opaque; VFIOMigration *migration = vbasedev->migration; - uint64_t remain; + uint64_t precopy_size, stopcopy_size; if (exact) { vfio_state_pending_sync(vbasedev); - remain = migration->stopcopy_size; + } + + precopy_size = + migration->precopy_init_size + migration->precopy_dirty_size; + + if (migration->stopcopy_size > precopy_size) { + stopcopy_size = migration->stopcopy_size - precopy_size; } else { - if (!vfio_device_state_is_precopy(vbasedev)) { - return; - } - remain = migration->precopy_init_size + migration->precopy_dirty_size; + stopcopy_size = 0; } - pending->precopy_bytes += remain; + pending->precopy_bytes += precopy_size; + pending->stopcopy_bytes += stopcopy_size; trace_vfio_state_pending(vbasedev->name, migration->stopcopy_size, migration->precopy_init_size, -- 2.53.0
It used to hide in RAM dirty sync path. Now with more modules being able to slow sync on dirty information, keeping it there may not be good anymore because it's not RAM's own concept for iterations: all modules should follow. More importantly, mgmt may try to query dirty info (to make policy decisions like adjusting downtime) by listening to iteration count changes via QMP events. So we must make sure the boost of iterations only happens _after_ the dirty sync operations with whatever form (RAM's dirty bitmap sync, or VFIO's different ioctls to fetch latest dirty info from kernel). Move this to core migration path to manage, together with the event generation, so that it can be well ordered with the sync operations for all modules. This brings a good side effect that we should have an old issue regarding to cpu_throttle_dirty_sync_timer_tick() which can randomly boost iteration counts (because it invokes sync ops). Now it won't, which is actually the right behavior. Said that, we have code (not only QEMU, but likely mgmt too) assuming the 1st iteration will always shows dirty count to 1. Make it initialized with 1 this time, because we'll miss the dirty sync for setup() on boosting this counter now. Reviewed-by: Hyman Huang <yong.huang@smartx.com> Reviewed-by: Prasad Pandit <pjp@fedoraproject.org> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-10-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration-stats.h | 3 ++- migration/migration.c | 29 ++++++++++++++++++++++++++--- migration/ram.c | 6 ------ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/migration/migration-stats.h b/migration/migration-stats.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-stats.h +++ b/migration/migration-stats.h @@ -XXX,XX +XXX,XX @@ typedef struct { */ uint64_t dirty_pages_rate; /* - * Number of times we have synchronized guest bitmaps. + * Number of times we have synchronized guest bitmaps. This always + * starts from 1 for the 1st iteration. */ uint64_t dirty_sync_count; /* diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ int migrate_init(MigrationState *s, Error **errp) s->threshold_size = 0; s->switchover_acked = false; s->rdma_migration = false; + /* - * set mig_stats memory to zero for a new migration + * set mig_stats memory to zero for a new migration.. except the + * iteration counter, which we want to make sure it returns 1 for the + * first iteration. */ memset(&mig_stats, 0, sizeof(mig_stats)); + mig_stats.dirty_sync_count = 1; + migration_reset_vfio_bytes_transferred(); s->postcopy_package_loaded = false; @@ -XXX,XX +XXX,XX @@ static bool migration_iteration_next_ready(MigrationState *s, static void migration_iteration_go_next(MigPendingData *pending) { /* - * Do a slow sync will achieve this. TODO: move RAM iteration code - * into the core layer. + * Do a slow sync first before boosting the iteration count. */ qemu_savevm_query_pending(pending, true); + + /* + * Boost dirty sync count to reflect we finished one iteration. + * + * NOTE: we need to make sure when this happens (together with the + * event sent below) all modules have slow-synced the pending data + * above. That means a write mem barrier, but qatomic_add() should be + * enough. + * + * It's because a mgmt could wait on the iteration event to query again + * on pending data for policy changes (e.g. downtime adjustments). The + * ordering will make sure the query will fetch the latest results from + * all the modules. + */ + qatomic_add(&mig_stats.dirty_sync_count, 1); + + if (migrate_events()) { + qapi_event_send_migration_pass(mig_stats.dirty_sync_count); + } } static bool postcopy_should_start(MigrationState *s, MigPendingData *pending) diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static void migration_bitmap_sync(RAMState *rs, bool last_stage) RAMBlock *block; int64_t end_time; - qatomic_add(&mig_stats.dirty_sync_count, 1); - if (!rs->time_last_bitmap_sync) { rs->time_last_bitmap_sync = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); } @@ -XXX,XX +XXX,XX @@ static void migration_bitmap_sync(RAMState *rs, bool last_stage) rs->num_dirty_pages_period = 0; rs->bytes_xfer_prev = migration_transferred_bytes(); } - if (migrate_events()) { - uint64_t generation = qatomic_read(&mig_stats.dirty_sync_count); - qapi_event_send_migration_pass(generation); - } } void migration_bitmap_sync_precopy(bool last_stage) -- 2.53.0
Add a helper migration_get_switchover_bw() to return an estimate of switchover bandwidth. Use it to simplify the current code. This will be used in later to remove expected_downtime. When at it, remove two qatomic_read() to shrink the lines because atomic ops are not needed when it's always the same thread who does the updates. Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-11-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration.c | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ void migrate_send_rp_resume_ack(MigrationIncomingState *mis, uint32_t value) migrate_send_rp_message(mis, MIG_RP_MSG_RESUME_ACK, sizeof(buf), &buf); } +/* + * Returns the estimated switchover bandwidth (unit: bytes / seconds) + */ +static double migration_get_switchover_bw(MigrationState *s) +{ + uint64_t switchover_bw = migrate_avail_switchover_bandwidth(); + + if (switchover_bw) { + /* If user specified, prioritize this value and don't estimate */ + return (double)switchover_bw; + } + + return s->mbps / 8 * 1000 * 1000; +} + bool migration_is_running(void) { MigrationState *s = current_migration; @@ -XXX,XX +XXX,XX @@ static void migration_update_counters(MigrationState *s, { uint64_t transferred, transferred_pages, time_spent; uint64_t current_bytes; /* bytes transferred since the beginning */ - uint64_t switchover_bw; - /* Expected bandwidth when switching over to destination QEMU */ - double expected_bw_per_ms; - double bandwidth; + double switchover_bw_per_ms; if (current_time < s->iteration_start_time + BUFFER_DELAY) { return; } - switchover_bw = migrate_avail_switchover_bandwidth(); current_bytes = migration_transferred_bytes(); transferred = current_bytes - s->iteration_initial_bytes; time_spent = current_time - s->iteration_start_time; - bandwidth = (double)transferred / time_spent; - - if (switchover_bw) { - /* - * If the user specified a switchover bandwidth, let's trust the - * user so that can be more accurate than what we estimated. - */ - expected_bw_per_ms = (double)switchover_bw / 1000; - } else { - /* If the user doesn't specify bandwidth, we use the estimated */ - expected_bw_per_ms = bandwidth; - } - - s->threshold_size = expected_bw_per_ms * migrate_downtime_limit(); - s->mbps = (((double) transferred * 8.0) / ((double) time_spent / 1000.0)) / 1000.0 / 1000.0; + /* NOTE: only update this after bandwidth (s->mbps) updated */ + switchover_bw_per_ms = migration_get_switchover_bw(s) / 1000; + s->threshold_size = switchover_bw_per_ms * migrate_downtime_limit(); + transferred_pages = ram_get_total_transferred_pages() - s->iteration_initial_pages; s->pages_per_second = (double) transferred_pages / @@ -XXX,XX +XXX,XX @@ static void migration_update_counters(MigrationState *s, * if we haven't sent anything, we don't want to * recalculate. 10000 is a small enough number for our purposes */ - if (qatomic_read(&mig_stats.dirty_pages_rate) && - transferred > 10000) { + if (mig_stats.dirty_pages_rate && transferred > 10000) { s->expected_downtime = - qatomic_read(&mig_stats.dirty_bytes_last_sync) / expected_bw_per_ms; + mig_stats.dirty_bytes_last_sync / switchover_bw_per_ms; } migration_rate_reset(); @@ -XXX,XX +XXX,XX @@ static void migration_update_counters(MigrationState *s, trace_migrate_transferred(transferred, time_spent, /* Both in unit bytes/ms */ - bandwidth, switchover_bw / 1000, + (uint64_t)s->mbps, + (uint64_t)switchover_bw_per_ms, s->threshold_size); } -- 2.53.0
This value does not need to be calculated as frequent. Only calculate it on demand when query-migrate happened. With that we can remove the variable in MigrationState. This paves way for fixing this value to include all modules (not only RAM but others too). Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-12-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration.h | 2 +- migration/migration.c | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/migration/migration.h b/migration/migration.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -XXX,XX +XXX,XX @@ struct MigrationState { /* Timestamp when VM is down (ms) to migrate the last stuff */ int64_t downtime_start; int64_t downtime; - int64_t expected_downtime; bool capabilities[MIGRATION_CAPABILITY__MAX]; int64_t setup_time; @@ -XXX,XX +XXX,XX @@ void migration_cancel(void); void migration_populate_vfio_info(MigrationInfo *info); void migration_reset_vfio_bytes_transferred(void); void postcopy_temp_page_reset(PostcopyTmpPage *tmp_page); +int64_t migration_downtime_calc_expected(MigrationState *s); /* * Migration thread waiting for return path thread. Return non-zero if an diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ static bool migrate_show_downtime(MigrationState *s) return (s->state == MIGRATION_STATUS_COMPLETED) || migration_in_postcopy(); } +/* Return expected downtime (unit: milliseconds) */ +int64_t migration_downtime_calc_expected(MigrationState *s) +{ + if (mig_stats.dirty_sync_count <= 1) { + return migrate_downtime_limit(); + } + + return mig_stats.dirty_bytes_last_sync / + migration_get_switchover_bw(s) * 1000; +} + static void populate_time_info(MigrationInfo *info, MigrationState *s) { info->has_status = true; @@ -XXX,XX +XXX,XX @@ static void populate_time_info(MigrationInfo *info, MigrationState *s) info->downtime = s->downtime; } else { info->has_expected_downtime = true; - info->expected_downtime = s->expected_downtime; + info->expected_downtime = migration_downtime_calc_expected(s); } } @@ -XXX,XX +XXX,XX @@ int migrate_init(MigrationState *s, Error **errp) s->mbps = 0.0; s->pages_per_second = 0.0; s->downtime = 0; - s->expected_downtime = 0; s->setup_time = 0; s->start_postcopy = false; s->migration_thread_running = false; @@ -XXX,XX +XXX,XX @@ static void migration_update_counters(MigrationState *s, s->pages_per_second = (double) transferred_pages / (((double) time_spent / 1000.0)); - /* - * if we haven't sent anything, we don't want to - * recalculate. 10000 is a small enough number for our purposes - */ - if (mig_stats.dirty_pages_rate && transferred > 10000) { - s->expected_downtime = - mig_stats.dirty_bytes_last_sync / switchover_bw_per_ms; - } - migration_rate_reset(); update_iteration_initial_status(s); @@ -XXX,XX +XXX,XX @@ void migration_start_outgoing(MigrationState *s) bool resume = (s->state == MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP); int ret; - s->expected_downtime = migrate_downtime_limit(); - if (resume) { /* This is a resumed migration */ rate_limit = migrate_max_postcopy_bandwidth(); -- 2.53.0
QEMU will provide an expected downtime for the whole system during migration, by remembering the total dirty RAM that we synced the last time, divides the estimated switchover bandwidth. That was flawed when VFIO is taking into account: consider there is a VFIO GPU device that contains GBs of data to migrate during stop phase. Those will not be accounted in this math. Fix it by updating dirty_bytes_last_sync properly only when we go to the next iteration, rather than hide this update in the RAM code. Meanwhile, fetch the total (rather than RAM-only) portion of dirty bytes, so as to include GPU device states too. Update the comment of the field to reflect its new meaning. Now after this change, the expected-downtime to be read from query-migrate should be very accurate even with VFIO devices involved. Tested-by: Cédric Le Goater <clg@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-13-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration-stats.h | 8 +++----- migration/migration.c | 11 ++++++++--- migration/ram.c | 1 - 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/migration/migration-stats.h b/migration/migration-stats.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-stats.h +++ b/migration/migration-stats.h @@ -XXX,XX +XXX,XX @@ */ typedef struct { /* - * Number of bytes that were dirty last time that we synced with - * the guest memory. We use that to calculate the downtime. As - * the remaining dirty amounts to what we know that is still dirty - * since last iteration, not counting what the guest has dirtied - * since we synchronized bitmaps. + * Number of bytes that were reported dirty after the latest + * system-wise synchronization of dirty information. It is used to do + * best-effort estimation on expected downtime. */ uint64_t dirty_bytes_last_sync; /* diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ static void migration_iteration_go_next(MigPendingData *pending) */ qemu_savevm_query_pending(pending, true); + /* + * Update the dirty information for the whole system for this + * iteration. This value is used to calculate expected downtime. + */ + qatomic_set(&mig_stats.dirty_bytes_last_sync, pending->total_bytes); + /* * Boost dirty sync count to reflect we finished one iteration. * * NOTE: we need to make sure when this happens (together with the * event sent below) all modules have slow-synced the pending data - * above. That means a write mem barrier, but qatomic_add() should be - * enough. + * above and updated corresponding fields (e.g. dirty_bytes_last_sync). * * It's because a mgmt could wait on the iteration event to query again * on pending data for policy changes (e.g. downtime adjustments). The * ordering will make sure the query will fetch the latest results from - * all the modules. + * all the modules on everything. */ qatomic_add(&mig_stats.dirty_sync_count, 1); diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static void migration_bitmap_sync(RAMState *rs, bool last_stage) RAMBLOCK_FOREACH_NOT_IGNORED(block) { ramblock_sync_dirty_bitmap(rs, block); } - qatomic_set(&mig_stats.dirty_bytes_last_sync, ram_bytes_remaining()); } } -- 2.53.0
Introduce this new counter to remember the total dirty bytes for the whole system. It will be used for query-migrate command to fetch system-wise remaining data. A prior attempt was made to not use this counter but query directly from all the modules in a QMP handler, but it exposed some complexity not only on migration state machine race conditions (where the query may be invoked anytime of the state machine), or on locking implications (where some of the query hooks may take BQL, which is illegal at least in a QMP handler). For more information, see: https://lore.kernel.org/r/aeZMtxqrKWAMKzdN@x1.local This oneliner will resolve everything, except that it is not as accurate. The hope is it is a worthwhile trade-off solution, after knowing above challenges. Now, there is one more reason we should make each invocation of save_live_iterate() to be lightweight, because this counter will only get updated once for each loop over all save_live_iterate() hooks when present. But that's always the goal. Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-14-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration-stats.h | 7 +++++++ migration/savevm.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/migration/migration-stats.h b/migration/migration-stats.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-stats.h +++ b/migration/migration-stats.h @@ -XXX,XX +XXX,XX @@ typedef struct { * best-effort estimation on expected downtime. */ uint64_t dirty_bytes_last_sync; + /* + * Number of bytes that were reported dirty now. This is an estimate + * value and will be updated every time migration thread queries from + * modules in an iteration loop. It is used to provide best-effort + * estimation on total remaining data. + */ + uint64_t dirty_bytes_total; /* * Number of pages dirtied per second. */ diff --git a/migration/savevm.c b/migration/savevm.c index XXXXXXX..XXXXXXX 100644 --- a/migration/savevm.c +++ b/migration/savevm.c @@ -XXX,XX +XXX,XX @@ void qemu_savevm_query_pending(MigPendingData *pending, bool exact) pending->total_bytes = pending->precopy_bytes + pending->stopcopy_bytes + pending->postcopy_bytes; + /* + * Update system remaining dirty bytes whenever QEMU queries. It will + * make the value to be not as accurate, but should still be pretty + * close to reality when this got invoked frequently while iterating. + */ + mig_stats.dirty_bytes_total = pending->total_bytes; + trace_qemu_savevm_query_pending(exact, pending->precopy_bytes, pending->stopcopy_bytes, pending->postcopy_bytes, -- 2.53.0
Currently, mgmt can only query for remaining RAM using QMP command "query-migrate" and monitor the "ram" section. There's no way to report system-wide remaining data including VFIO devices. It was not a problem before, because for a very long time RAM was the only part that matters. After VFIO migrations landed upstream, it may not be enough. There can be GPU devices that contain GBs of device states. Mgmt may want to know how much remaining for special devices like VFIO, because all of them will be accounted as VM data to migrate and will contribute to downtime in the switchover phase. Add a new "remaining" field in query-migrate results on the top level, reflecting system-wide remaining data, which will include everything like VFIO devices. This information will be useful for mgmt to implement generic way of stall detection that covers all system resources. For example, when system-wide remaining data (especially, if sampled right after each migration iteration) does not decrease anymore for a relatively long period of time, then it may imply there is a challenge of converging, mgmt can react based on how this value changes over time. Before this patch, "expected_downtime" almost played this role. For example, by monitoring "expected_downtime" at the beginning of each iteration can in most cases also reflect the progress of migration system-wide. Said that, "expected_downtime" was always calculated based on a bandwidth value that can fluctuate if avail-switchover-bandwidth is not used. This new "remaining" field will remove that part of uncertainty for mgmt no matter if avail-switchover-bandwidth is used by the mgmt. With the new field, HMP "info migrate" now reports this: (qemu) info migrate Status: active Time (ms): total=12080, setup=14, exp_down=300 Remaining: 1.36 GiB <--- this is the new line RAM info: Throughput (Mbps): 840.50 Sizes: pagesize=4 KiB, total=4.02 GiB Transfers: transferred=1.18 GiB, remain=1.36 GiB Channels: precopy=1.18 GiB, multifd=0 B, postcopy=0 B Page Types: normal=307923, zero=388148 Page Rates (pps): transfer=25660 Others: dirty_syncs=1 When VFIO is not involved, the value reported in the new field should be approximately the same as reported in the "remaining" field of the RAM section. It is only approximately because the system-wide remaining data is a cached value, which gets frequently updated by migration core. OTOH, the RAM's remaining data is accurate. When VFIO is involved, the new value reported should normally be larger, because it will include the size of VFIO remaining data too. Cc: Aseef Imran <aimran@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Dr. David Alan Gilbert <dave@treblig.org> Acked-by: Markus Armbruster <armbru@redhat.com> # QAPI schema Link: https://lore.kernel.org/r/20260421202110.306051-15-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- qapi/migration.json | 4 ++++ migration/migration-hmp-cmds.c | 5 +++++ migration/migration.c | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/qapi/migration.json b/qapi/migration.json index XXXXXXX..XXXXXXX 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -XXX,XX +XXX,XX @@ # average memory load of the virtual CPU indirectly. Note that # zero means guest doesn't dirty memory. (Since 8.1) # +# @remaining: amount of bytes remaining to be migrated system-wide, +# includes both RAM and all devices (like VFIO). (Since 11.1) +# # Features: # # @unstable: Members @postcopy-latency, @postcopy-vcpu-latency, @@ -XXX,XX +XXX,XX @@ ## { 'struct': 'MigrationInfo', 'data': {'*status': 'MigrationStatus', '*ram': 'MigrationRAMStats', + '*remaining': 'size', '*vfio': 'VfioStats', '*xbzrle-cache': 'XBZRLECacheStats', '*total-time': 'int', diff --git a/migration/migration-hmp-cmds.c b/migration/migration-hmp-cmds.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration-hmp-cmds.c +++ b/migration/migration-hmp-cmds.c @@ -XXX,XX +XXX,XX @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict) } } + if (info->has_remaining) { + g_autofree char *remaining = size_to_str(info->remaining); + monitor_printf(mon, "Remaining: \t\t%s\n", remaining); + } + if (info->has_socket_address) { SocketAddressList *addr; diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ static void populate_time_info(MigrationInfo *info, MigrationState *s) } } +static void populate_global_info(MigrationInfo *info, MigrationState *s) +{ + info->has_remaining = true; + info->remaining = qatomic_read(&mig_stats.dirty_bytes_total); +} + static void populate_ram_info(MigrationInfo *info, MigrationState *s) { size_t page_size = qemu_target_page_size(); @@ -XXX,XX +XXX,XX @@ static void fill_source_migration_info(MigrationInfo *info) /* TODO add some postcopy stats */ populate_time_info(info, s); populate_ram_info(info, s); + populate_global_info(info, s); migration_populate_vfio_info(info); break; case MIGRATION_STATUS_COLO: -- 2.53.0
Add ", in bytes per second". Unfortunately indentations need to be updated completely, but no change on the rest. Cc: Markus Armbruster <armbru@redhat.com> Suggested-by: Juraj Marcin <jmarcin@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Acked-by: Markus Armbruster <armbru@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-16-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- qapi/migration.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/qapi/migration.json b/qapi/migration.json index XXXXXXX..XXXXXXX 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -XXX,XX +XXX,XX @@ # (Since 2.8) # # @avail-switchover-bandwidth: to set the available bandwidth that -# migration can use during switchover phase. **Note:** this does -# not limit the bandwidth during switchover, but only for -# calculations when making decisions to switchover. By default, -# this value is zero, which means QEMU will estimate the bandwidth -# automatically. This can be set when the estimated value is not -# accurate, while the user is able to guarantee such bandwidth is -# available when switching over. When specified correctly, this -# can make the switchover decision much more accurate. -# (Since 8.2) +# migration can use during switchover phase, in bytes per +# second. **Note:** this does not limit the bandwidth during +# switchover, but only for calculations when making decisions to +# switchover. By default, this value is zero, which means QEMU +# will estimate the bandwidth automatically. This can be set +# when the estimated value is not accurate, while the user is +# able to guarantee such bandwidth is available when switching +# over. When specified correctly, this can make the switchover +# decision much more accurate. (Since 8.2) # # @downtime-limit: set maximum tolerated downtime for migration. # maximum downtime in milliseconds (Since 2.8) -- 2.53.0
Add two tracepoints for both precopy and stopcopy query ioctls. When at it, add one warn_report_once() for each of them when it fails. Reviewed-by: Avihai Horon <avihaih@nvidia.com> Tested-by: Cédric Le Goater <clg@redhat.com> Reviewed-by: Cédric Le Goater <clg@redhat.com> Link: https://lore.kernel.org/r/20260421202110.306051-17-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/vfio/migration.c | 35 +++++++++++++++++++++++++---------- hw/vfio/trace-events | 2 ++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/hw/vfio/migration.c b/hw/vfio/migration.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/migration.c +++ b/hw/vfio/migration.c @@ -XXX,XX +XXX,XX @@ static int vfio_query_stop_copy_size(VFIODevice *vbasedev) struct vfio_device_feature_mig_data_size *mig_data_size = (struct vfio_device_feature_mig_data_size *)feature->data; VFIOMigration *migration = vbasedev->migration; + int ret; feature->argsz = sizeof(buf); feature->flags = @@ -XXX,XX +XXX,XX @@ static int vfio_query_stop_copy_size(VFIODevice *vbasedev) * is reported so downtime limit won't be violated. */ migration->stopcopy_size = VFIO_MIG_STOP_COPY_SIZE; - return -errno; + ret = -errno; + warn_report_once("VFIO device %s ioctl(VFIO_DEVICE_FEATURE) on " + "VFIO_DEVICE_FEATURE_MIG_DATA_SIZE failed (%d)", + vbasedev->name, ret); + } else { + migration->stopcopy_size = mig_data_size->stop_copy_length; + ret = 0; } - migration->stopcopy_size = mig_data_size->stop_copy_length; + trace_vfio_query_stop_copy_size(vbasedev->name, + migration->stopcopy_size, ret); - return 0; + return ret; } static int vfio_query_precopy_size(VFIOMigration *migration) @@ -XXX,XX +XXX,XX @@ static int vfio_query_precopy_size(VFIOMigration *migration) struct vfio_precopy_info precopy = { .argsz = sizeof(precopy), }; - - migration->precopy_init_size = 0; - migration->precopy_dirty_size = 0; + int ret; if (ioctl(migration->data_fd, VFIO_MIG_GET_PRECOPY_INFO, &precopy)) { - return -errno; + migration->precopy_init_size = 0; + migration->precopy_dirty_size = 0; + ret = -errno; + warn_report_once("VFIO device %s ioctl(VFIO_MIG_GET_PRECOPY_INFO) " + "failed (%d)", migration->vbasedev->name, ret); + } else { + migration->precopy_init_size = precopy.initial_bytes; + migration->precopy_dirty_size = precopy.dirty_bytes; + ret = 0; } - migration->precopy_init_size = precopy.initial_bytes; - migration->precopy_dirty_size = precopy.dirty_bytes; + trace_vfio_query_precopy_size(migration->vbasedev->name, + migration->precopy_init_size, + migration->precopy_dirty_size, ret); - return 0; + return ret; } /* Returns the size of saved data on success and -errno on error */ diff --git a/hw/vfio/trace-events b/hw/vfio/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/trace-events +++ b/hw/vfio/trace-events @@ -XXX,XX +XXX,XX @@ vfio_migration_realize(const char *name) " (%s)" vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s" vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s" vfio_migration_state_notifier(const char *name, int state) " (%s) state %d" +vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, int ret) " (%s) init %"PRIu64" dirty %"PRIu64" ret %d" +vfio_query_stop_copy_size(const char *name, uint64_t size, int ret) " (%s) stopcopy size %"PRIu64" ret %d" vfio_save_block(const char *name, int data_size) " (%s) data_size %d" vfio_save_block_precopy_empty_hit(const char *name) " (%s)" vfio_save_cleanup(const char *name) " (%s)" -- 2.53.0
From: CJ Chen <cjchen@igel.co.jp> The riscv_iommu_trap_ops MemoryRegionOps specifies that unaligned accesses are not valid for this device but that it does implement them. This doesn't make much sense, and we want to add an assertion that registered MRs don't specify this invalid combination of settings. Drop .impl.unaligned = true, with no behaviour change. Signed-off-by: CJ Chen <cjchen@igel.co.jp> Acked-by: Tomoyuki Hirose <hrstmyk811m@gmail.com> Reported-by: Tomoyuki Hirose <hrstmyk811m@gmail.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> [PMM: reworded commit message] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Link: https://lore.kernel.org/r/20260428093339.2087081-2-peter.maydell@linaro.org Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/riscv/riscv-iommu.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index XXXXXXX..XXXXXXX 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps riscv_iommu_trap_ops = { .impl = { .min_access_size = 4, .max_access_size = 8, - .unaligned = true, }, .valid = { .min_access_size = 4, -- 2.53.0
From: Peter Maydell <peter.maydell@linaro.org> Currently npcm7xx_fiu_flash_ops provides no .impl substruct; this means that it gets the default of "implements 1, 2 and 4 byte aligned accesses". This is more constrained than the device permits in its .valid substruct, and also narrower than the functions are written to handle. Add a .impl substruct matching the .valid substruct; this means that all guest accesses are handled directly by the read and write functions, and are never synthesized by the memory subsystem performing multiple accesses to the device (which would not behave correctly, as these read and write fucntions have side effects). Based-on-a-patch-by: CJ Chen <cjchen@igel.co.jp> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Link: https://lore.kernel.org/r/20260428093339.2087081-3-peter.maydell@linaro.org Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/ssi/npcm7xx_fiu.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hw/ssi/npcm7xx_fiu.c b/hw/ssi/npcm7xx_fiu.c index XXXXXXX..XXXXXXX 100644 --- a/hw/ssi/npcm7xx_fiu.c +++ b/hw/ssi/npcm7xx_fiu.c @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps npcm7xx_fiu_flash_ops = { .read = npcm7xx_fiu_flash_read, .write = npcm7xx_fiu_flash_write, .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, .valid = { .min_access_size = 1, .max_access_size = 8, -- 2.53.0
From: Peter Maydell <peter.maydell@linaro.org> The xtensa mx-pic interrupt controller has a rather odd register setup, where some registers are 32 bits but are decoded at offsets only one apart from each other. The QEMU implementation handles this correctly, but it did not set .impl.unaligned = true. This has worked up til now because QEMU has entirely ignored .impl.unaligned, and just allowed through unaligned accesses when .valid.unaligned is set. To allow the possibility of properly implementing synthesis of unaligned accesses by the memory subsystem when they are valid but the device doesn't implement them, and for clarity of intention, state explicitly that this MR's read and write functions directly handle unaligned accesses, by setting .impl.unaligned = true. While we are adjusting the MemoryRegionOps, we set also the minimum and maximum allowed access sizes. Since the only way to get at this device is via the CPU's RER and WER instructions, which always operate at 32-bit sizes (see the HELPER(rer) and HELPER(wer) functions in target/xtensa/op_helper.c), we know we will always get 32-bit accesses. Specify explicitly that that is what is valid and implemented for the MR. Add a comment to clarify that the hardware behaviour here is not "true memory-mapped registers", so the odd-looking implementation is correct. Based-on-a-patch-by: CJ Chen <cjchen@igel.co.jp> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Max Filippov <jcmvbkbc@gmail.com> Link: https://lore.kernel.org/r/20260428093339.2087081-4-peter.maydell@linaro.org Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/xtensa/mx_pic.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/hw/xtensa/mx_pic.c b/hw/xtensa/mx_pic.c index XXXXXXX..XXXXXXX 100644 --- a/hw/xtensa/mx_pic.c +++ b/hw/xtensa/mx_pic.c @@ -XXX,XX +XXX,XX @@ struct XtensaMxPic { } cpu[MX_MAX_CPU]; }; +/* + * Note that decode for these registers is rather strange by the usual + * MMIO standards -- the MIROUT and MIPICAUSE areas can be read and + * written at 32-bit length, returning different values for each byte + * offset, because the low bits of the address are treated as selecting + * an IRQ or a processor: + * + * 00nn 0...0p..p Interrupt Routing, route IRQ n to processor p + * 01pp 0...0d..d 16 bits (d) 'ored' as single IPI to processor p + * + * This is because (like x86 IO port in/out accesses) the offset is + * not a memory-mapped address but is really a register number, + * accessed via the Xtensa RER/WER "external register" instructions. + * + * We set .valid and .impl to both allow unaligned = true to permit + * these byte-offsets. Because this device is not a true memory mapped + * device but is accessible only via the Xtensa RER/WER "external + * register" interface, all accesses are guaranteed 32 bits. + */ + static uint64_t xtensa_mx_pic_ext_reg_read(void *opaque, hwaddr offset, unsigned size) { @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps xtensa_mx_pic_ops = { .read = xtensa_mx_pic_ext_reg_read, .write = xtensa_mx_pic_ext_reg_write, .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = true, + }, .valid = { + .min_access_size = 4, + .max_access_size = 4, .unaligned = true, }, }; -- 2.53.0
From: CJ Chen <cjchen@igel.co.jp> When it comes to this pattern: .valid.unaligned = false and impl.unaligned = true, is effectlvely contradictory. The .valid structure indicates that unaligned access should be rejected at the access validation phase, yet .impl suggests the underlying device implementation can handle unaligned operations. As a result, the upper-layer code will never even reach the .impl logic. Add an assertion that the MemoryRegionOps doesn't specify this invalid combination. Signed-off-by: CJ Chen <cjchen@igel.co.jp> Tested-by: CJ Chen <cjchen@igel.co.jp> Suggested-by: Peter Xu <peterx@redhat.com> Acked-by: Tomoyuki Hirose <hrstmyk811m@gmail.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> [PMM: tweaked commit message] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Link: https://lore.kernel.org/r/20260428093339.2087081-5-peter.maydell@linaro.org Signed-off-by: Peter Xu <peterx@redhat.com> --- system/memory.c | 1 + 1 file changed, 1 insertion(+) diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ void memory_region_init_io(MemoryRegion *mr, Object *owner, const MemoryRegionOps *ops, void *opaque, const char *name, uint64_t size) { + g_assert(!ops || !(ops->impl.unaligned && !ops->valid.unaligned)); memory_region_init(mr, owner, name, size); memory_region_set_ops(mr, ops, opaque); } -- 2.53.0
From: Fabiano Rosas <farosas@suse.de> The generation of the guest workloads for migration-test is all broken: 1) We moved code around and forgot to update the includes from the guest workloads at qtest/migration/<ARCH>/. 2) Since this code doesn't build by default due to the need for cross compilation, we also broke the s390x code entirely by removing the libc implementation from sclp.c. 3) The build adds a blank line at the end of the header files due to how the sed command used to remove extra variable declarations is written. 4) Must be too long since we last built this thing that powerpc compilers would output BE by default. Nowadays we need -mbig-endian to force it. 5) Format of comments needs fixing to please checkpatch. Fix everything. Most changes are trivial, except for s390x where I had to reimplement some of the routines from sclp.c. Compiling that object file in doesn't work because of its various includes that this standalone code can't provide. Testing this change (assuming x86 host): $ cd build $ pushd tests/qtest/migration $ make clean $ make i386 $ make CROSS_PREFIX=your-favorite-cross-compiler- s390x $ make CROSS_PREFIX=your-favorite-cross-compiler- aarch64 $ make CROSS_PREFIX=your-favorite-cross-compiler- ppc64 $ popd $ make -j$(nproc) # must build the tests again $ QTEST_QEMU_BINARY=./qemu-system-<ARCH> migration-test Fixes: 9f4278837d ("pc-bios/s390-ccw: Use the libc from SLOF and remove sclp prints") Fixes: e1803dabdc ("tests/qtest/migration: Move common test code") Signed-off-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260429231025.30818-1-farosas@suse.de Signed-off-by: Peter Xu <peterx@redhat.com> --- tests/qtest/migration/aarch64/a-b-kernel.h | 7 +- tests/qtest/migration/bootfile.h | 11 + tests/qtest/migration/i386/a-b-bootblock.h | 8 +- tests/qtest/migration/ppc64/a-b-kernel.h | 8 +- tests/qtest/migration/s390x/a-b-bios.h | 611 +++++++++++++-------- tests/qtest/migration/s390x/a-b-bios.c | 66 ++- tests/qtest/migration/Makefile | 11 +- tests/qtest/migration/aarch64/Makefile | 4 +- tests/qtest/migration/aarch64/a-b-kernel.S | 2 +- tests/qtest/migration/i386/Makefile | 4 +- tests/qtest/migration/i386/a-b-bootblock.S | 2 +- tests/qtest/migration/ppc64/Makefile | 6 +- tests/qtest/migration/ppc64/a-b-kernel.S | 2 +- tests/qtest/migration/s390x/Makefile | 8 +- 14 files changed, 492 insertions(+), 258 deletions(-) diff --git a/tests/qtest/migration/aarch64/a-b-kernel.h b/tests/qtest/migration/aarch64/a-b-kernel.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/aarch64/a-b-kernel.h +++ b/tests/qtest/migration/aarch64/a-b-kernel.h @@ -XXX,XX +XXX,XX @@ -/* This file is automatically generated from the assembly file in - * tests/migration/aarch64. Edit that file and then run "make all" - * inside tests/migration to update, and then remember to send both +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/aarch64. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both * the header and the assembler differences in your patch submission. */ unsigned char aarch64_kernel[] = { diff --git a/tests/qtest/migration/bootfile.h b/tests/qtest/migration/bootfile.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/bootfile.h +++ b/tests/qtest/migration/bootfile.h @@ -XXX,XX +XXX,XX @@ #ifndef BOOTFILE_H #define BOOTFILE_H +/* + * This file is included from qtest, but also from the assembly code + * that generates the header file containing the guest workload. Don't + * expose the function declarations and non-standard types to it. + */ +#ifdef __ASSEMBLER__ +#define MIGRATION_GUEST_CODE +#endif + /* Common */ #define TEST_MEM_PAGE_SIZE 4096 @@ -XXX,XX +XXX,XX @@ */ #define ARM_TEST_MAX_KERNEL_SIZE (512 * 1024) +#ifndef MIGRATION_GUEST_CODE void bootfile_delete(void); char *bootfile_create(const char *arch, const char *dir, bool suspend_me); char *bootfile_get(void); +#endif #endif /* BOOTFILE_H */ diff --git a/tests/qtest/migration/i386/a-b-bootblock.h b/tests/qtest/migration/i386/a-b-bootblock.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/i386/a-b-bootblock.h +++ b/tests/qtest/migration/i386/a-b-bootblock.h @@ -XXX,XX +XXX,XX @@ -/* This file is automatically generated from the assembly file in - * tests/migration/i386. Edit that file and then run "make all" - * inside tests/migration to update, and then remember to send both +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/i386. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both * the header and the assembler differences in your patch submission. */ unsigned char x86_bootsect[] = { @@ -XXX,XX +XXX,XX @@ unsigned char x86_bootsect[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa }; - #define SYM_do_zero 0x00007c3d #define SYM_gdt 0x00007ca0 #define SYM_gdtdesc 0x00007cb8 diff --git a/tests/qtest/migration/ppc64/a-b-kernel.h b/tests/qtest/migration/ppc64/a-b-kernel.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/ppc64/a-b-kernel.h +++ b/tests/qtest/migration/ppc64/a-b-kernel.h @@ -XXX,XX +XXX,XX @@ -/* This file is automatically generated from the assembly file in - * tests/migration/ppc64. Edit that file and then run "make all" - * inside tests/migration to update, and then remember to send both +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/ppc64. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both * the header and the assembler differences in your patch submission. */ unsigned char ppc64_kernel[] = { @@ -XXX,XX +XXX,XX @@ unsigned char ppc64_kernel[] = { 0x38, 0xa0, 0x00, 0x01, 0x38, 0xc0, 0x00, 0x42, 0x78, 0xc6, 0xc1, 0xc6, 0x44, 0x00, 0x00, 0x22, 0x4b, 0xff, 0xff, 0xcc }; - diff --git a/tests/qtest/migration/s390x/a-b-bios.h b/tests/qtest/migration/s390x/a-b-bios.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/s390x/a-b-bios.h +++ b/tests/qtest/migration/s390x/a-b-bios.h @@ -XXX,XX +XXX,XX @@ -/* This file is automatically generated from the a-b-bios.c file in - * tests/migration/s390x. Edit that file and then run "make all" - * inside tests/migration to update, and then remember to send both +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/s390x. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both * the header and the assembler differences in your patch submission. */ unsigned char s390x_elf[] = { 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xa8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x07, 0x00, 0x40, - 0x00, 0x0d, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x10, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xac, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xac, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x17, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xb8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x18, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xb8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, + 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x64, 0x74, 0xe5, 0x51, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x74, 0xe5, 0x52, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x17, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xb8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x6c, 0x64, 0x36, 0x34, 0x2e, 0x73, 0x6f, - 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf0, 0xeb, 0xef, 0xf0, 0x70, - 0x00, 0x24, 0xa7, 0xfb, 0xff, 0x60, 0xc0, 0xe5, 0x00, 0x00, 0x01, 0x5f, - 0xc0, 0x20, 0x00, 0x00, 0x02, 0xa8, 0xc0, 0xe5, 0x00, 0x00, 0x01, 0x75, - 0xa5, 0x2e, 0x00, 0x10, 0xa7, 0x19, 0x63, 0x00, 0x92, 0x00, 0x20, 0x00, - 0xa7, 0x2b, 0x10, 0x00, 0xa7, 0x17, 0xff, 0xfc, 0xa5, 0x1e, 0x00, 0x10, - 0xa7, 0x29, 0x63, 0x00, 0xe3, 0x30, 0x10, 0x00, 0x00, 0x90, 0xa7, 0x3a, - 0x00, 0x01, 0x42, 0x30, 0x10, 0x00, 0xa7, 0x1b, 0x10, 0x00, 0xa7, 0x27, - 0xff, 0xf7, 0xc0, 0x20, 0x00, 0x00, 0x02, 0x8a, 0xc0, 0xe5, 0x00, 0x00, - 0x01, 0x56, 0xa7, 0xf4, 0xff, 0xeb, 0x07, 0x07, 0xc0, 0xf0, 0x00, 0x00, - 0x4e, 0x5c, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x7d, 0xe3, 0x20, 0x20, 0x00, - 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x96, 0xa3, 0xb9, 0x0b, 0x00, 0x32, - 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, 0xa7, 0x3b, 0xff, 0xff, - 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, 0x00, 0x44, 0xb9, 0x04, - 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, 0x10, 0x00, 0x10, 0x00, - 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, 0xc0, 0x20, 0x00, 0x00, - 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x5b, - 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, 0xff, 0xa1, 0xd7, 0x00, - 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x50, 0xb2, 0xb2, - 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, - 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, - 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, 0x00, 0x24, 0xc0, 0x10, - 0x00, 0x00, 0x00, 0x4b, 0xd2, 0x07, 0x01, 0xb0, 0x10, 0x00, 0xc0, 0x10, - 0x00, 0x00, 0x00, 0x3d, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x66, 0xf0, 0x00, - 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, - 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, 0x01, 0xf8, 0x00, 0x24, - 0xc0, 0x10, 0x00, 0x00, 0x00, 0x36, 0xd2, 0x07, 0x01, 0xf0, 0x10, 0x00, - 0xc0, 0x10, 0x00, 0x00, 0x00, 0x24, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x00, - 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, - 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x94, 0x00, - 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0x07, 0x07, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf0, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0xeb, 0xdf, 0xf0, 0x68, 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x05, + 0xa7, 0xfb, 0xff, 0x60, 0xc0, 0x10, 0x00, 0x00, 0x96, 0xc0, 0xa7, 0x28, + 0x00, 0x1c, 0x40, 0x20, 0x10, 0x00, 0xa5, 0x2c, 0x00, 0x04, 0xe3, 0x20, + 0x10, 0x0a, 0x00, 0x24, 0xa7, 0x28, 0x00, 0x40, 0x40, 0x20, 0x10, 0x12, + 0x58, 0x20, 0xd0, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, + 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0x63, 0xa7, 0x29, + 0x00, 0x41, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xbf, 0xa5, 0x2e, 0x00, 0x10, + 0xa7, 0x19, 0x63, 0x00, 0x92, 0x00, 0x20, 0x00, 0xa7, 0x2b, 0x10, 0x00, + 0xa7, 0x17, 0xff, 0xfc, 0xa5, 0x1e, 0x00, 0x10, 0xa7, 0x29, 0x63, 0x00, + 0xe3, 0x30, 0x10, 0x00, 0x00, 0x90, 0xa7, 0x3a, 0x00, 0x01, 0x42, 0x30, + 0x10, 0x00, 0xa7, 0x1b, 0x10, 0x00, 0xa7, 0x27, 0xff, 0xf7, 0xa7, 0x29, + 0x00, 0x42, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xa1, 0xa7, 0xf4, 0xff, 0xec, + 0xc0, 0xf0, 0x00, 0x00, 0x56, 0x30, 0xc0, 0x20, 0x00, 0x00, 0x0e, 0x7d, + 0xe3, 0x20, 0x20, 0x00, 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x9e, 0x77, + 0xb9, 0x0b, 0x00, 0x32, 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, + 0xa7, 0x3b, 0xff, 0xff, 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, + 0x00, 0x44, 0xb9, 0x04, 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, + 0x10, 0x00, 0x10, 0x00, 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, + 0xc0, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, + 0x00, 0x00, 0x00, 0x57, 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, + 0xff, 0x89, 0xd7, 0x00, 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, + 0x00, 0x4c, 0xb2, 0xb2, 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, + 0xf0, 0x00, 0x00, 0x25, 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, + 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, + 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x47, 0xd2, 0x07, 0x01, 0xb0, + 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x39, 0xb2, 0xb2, 0x10, 0x00, + 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, + 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, + 0x01, 0xf8, 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x32, 0xd2, 0x07, + 0x01, 0xf0, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x20, 0xb2, 0xb2, + 0x10, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, + 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, + 0x00, 0x25, 0x94, 0x00, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, + 0x07, 0xfe, 0x07, 0x07, 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0xeb, 0xbf, 0xf0, 0x58, - 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x4e, 0x0d, 0xa7, 0xfb, 0xff, 0x60, - 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0xb0, 0x88, 0xb0, 0x00, 0x1c, - 0xc0, 0xe5, 0xff, 0xff, 0xff, 0x91, 0xa7, 0xbe, 0x00, 0x03, 0xa7, 0x84, - 0x00, 0x13, 0xa7, 0xbe, 0x00, 0x02, 0xa7, 0x28, 0x00, 0x00, 0xa7, 0x74, - 0x00, 0x04, 0xa7, 0x28, 0xff, 0xfe, 0xe3, 0x40, 0xf1, 0x10, 0x00, 0x04, - 0xb9, 0x14, 0x00, 0x22, 0xeb, 0xbf, 0xf0, 0xf8, 0x00, 0x04, 0x07, 0xf4, - 0xa7, 0x28, 0xff, 0xff, 0xa7, 0xf4, 0xff, 0xf5, 0x07, 0x07, 0x07, 0x07, - 0xeb, 0xbf, 0xf0, 0x58, 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x25, - 0xa7, 0xfb, 0xff, 0x60, 0xa7, 0xb9, 0x00, 0x00, 0xa7, 0x19, 0x00, 0x00, - 0xc0, 0x40, 0x00, 0x00, 0x4d, 0xd8, 0xa7, 0x3b, 0x00, 0x01, 0xa7, 0x37, - 0x00, 0x23, 0xc0, 0x20, 0x00, 0x00, 0x4d, 0xd1, 0x18, 0x31, 0xa7, 0x1a, - 0x00, 0x06, 0x40, 0x10, 0x20, 0x08, 0xa7, 0x3a, 0x00, 0x0e, 0xa7, 0x18, - 0x1a, 0x00, 0x40, 0x30, 0x20, 0x00, 0x92, 0x00, 0x20, 0x02, 0x40, 0x10, - 0x20, 0x0a, 0xe3, 0x20, 0xd0, 0x00, 0x00, 0x04, 0xc0, 0xe5, 0xff, 0xff, - 0xff, 0xac, 0xe3, 0x40, 0xf1, 0x10, 0x00, 0x04, 0xb9, 0x04, 0x00, 0x2b, - 0xeb, 0xbf, 0xf0, 0xf8, 0x00, 0x04, 0x07, 0xf4, 0xb9, 0x04, 0x00, 0x51, - 0xa7, 0x5b, 0x00, 0x01, 0xa7, 0x09, 0x0f, 0xf7, 0xb9, 0x21, 0x00, 0x50, - 0xa7, 0x24, 0xff, 0xd7, 0x41, 0xeb, 0x20, 0x00, 0x95, 0x0a, 0xe0, 0x00, - 0xa7, 0x74, 0x00, 0x08, 0x41, 0x11, 0x40, 0x0e, 0x92, 0x0d, 0x10, 0x00, - 0xb9, 0x04, 0x00, 0x15, 0x43, 0x5b, 0x20, 0x00, 0x42, 0x51, 0x40, 0x0e, - 0xa7, 0xbb, 0x00, 0x01, 0x41, 0x10, 0x10, 0x01, 0xa7, 0xf4, 0xff, 0xbf, - 0xc0, 0x50, 0x00, 0x00, 0x00, 0xd8, 0xc0, 0x10, 0x00, 0x00, 0x4d, 0x8d, - 0xa7, 0x48, 0x00, 0x1c, 0x40, 0x40, 0x10, 0x00, 0x50, 0x20, 0x10, 0x0c, - 0xa7, 0x48, 0x00, 0x04, 0xe3, 0x20, 0x50, 0x00, 0x00, 0x04, 0x40, 0x40, - 0x10, 0x0a, 0x50, 0x30, 0x10, 0x10, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x6b, - 0xa7, 0x39, 0x00, 0x40, 0xa7, 0x29, 0x00, 0x00, 0xc0, 0xf4, 0xff, 0xff, - 0xff, 0xe4, 0x07, 0x07, 0xb9, 0x04, 0x00, 0x13, 0xa7, 0x2a, 0xff, 0xff, - 0xb9, 0x04, 0x00, 0x34, 0xa7, 0x48, 0x00, 0x01, 0x15, 0x24, 0xa7, 0x24, - 0x00, 0x07, 0xb9, 0x04, 0x00, 0x21, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x7f, - 0xa7, 0x29, 0xff, 0xff, 0x07, 0xfe, 0x07, 0x07, 0xa7, 0x39, 0x00, 0x00, - 0x41, 0x13, 0x20, 0x00, 0x95, 0x00, 0x10, 0x00, 0xa7, 0x74, 0x00, 0x05, - 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x70, 0xa7, 0x3b, 0x00, 0x01, 0xa7, 0xf4, - 0xff, 0xf5, 0x07, 0x07, 0xeb, 0xbf, 0xf0, 0x58, 0x00, 0x24, 0xc0, 0xd0, - 0x00, 0x00, 0x00, 0x95, 0xa7, 0xfb, 0xff, 0x60, 0xb9, 0x04, 0x00, 0xb2, - 0xa7, 0x19, 0x00, 0x20, 0xc0, 0x20, 0x00, 0x00, 0x4d, 0x40, 0x92, 0x00, - 0x20, 0x00, 0xa7, 0x2b, 0x00, 0x01, 0xa7, 0x17, 0xff, 0xfc, 0xc0, 0x10, - 0x00, 0x00, 0x4d, 0x37, 0xa7, 0x28, 0x10, 0x00, 0x40, 0x20, 0x10, 0x00, - 0xe3, 0x20, 0xd0, 0x00, 0x00, 0x04, 0xc0, 0xe5, 0xff, 0xff, 0xff, 0x1d, - 0x12, 0x22, 0xa7, 0x74, 0x00, 0x19, 0xa7, 0x19, 0x00, 0x00, 0xc0, 0x40, - 0x00, 0x00, 0x00, 0x79, 0xa7, 0x39, 0x00, 0x08, 0xc0, 0x20, 0x00, 0x00, - 0x4d, 0x2c, 0x41, 0x21, 0x20, 0x00, 0xe3, 0x20, 0x20, 0x00, 0x00, 0x90, - 0x43, 0x22, 0x40, 0x00, 0x42, 0x21, 0xb0, 0x00, 0xa7, 0x1b, 0x00, 0x01, - 0xa7, 0x37, 0xff, 0xf2, 0xe3, 0x40, 0xf1, 0x10, 0x00, 0x04, 0xeb, 0xbf, - 0xf0, 0xf8, 0x00, 0x04, 0x07, 0xf4, 0x07, 0x07, 0xeb, 0xaf, 0xf0, 0x50, - 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x00, 0x55, 0xa7, 0xfb, 0xff, 0x60, - 0xa7, 0x19, 0x0f, 0xf8, 0xb9, 0x21, 0x00, 0x31, 0xb9, 0x04, 0x00, 0xa2, - 0xa7, 0xc4, 0x00, 0x2a, 0xa7, 0xb9, 0x0f, 0xf8, 0xc0, 0x10, 0x00, 0x00, - 0x4c, 0xf6, 0xa7, 0x28, 0x10, 0x00, 0x40, 0x20, 0x10, 0x00, 0x92, 0x00, - 0x10, 0x02, 0xe3, 0x20, 0xd0, 0x00, 0x00, 0x04, 0xc0, 0xe5, 0xff, 0xff, - 0xfe, 0xda, 0xa7, 0xbb, 0x00, 0x01, 0xa7, 0x19, 0x00, 0x00, 0xa7, 0xb7, - 0x00, 0x17, 0xc0, 0x10, 0x00, 0x00, 0x4c, 0xe1, 0xe3, 0x40, 0xf1, 0x10, - 0x00, 0x04, 0xe3, 0x20, 0x10, 0x08, 0x00, 0x91, 0xa7, 0x2a, 0xff, 0xf9, - 0xb9, 0x14, 0x00, 0x22, 0xeb, 0xaf, 0xf0, 0xf0, 0x00, 0x04, 0x07, 0xf4, - 0xb9, 0x04, 0x00, 0xb3, 0xa7, 0xf4, 0xff, 0xd8, 0xc0, 0x20, 0x00, 0x00, - 0x4c, 0xcc, 0x41, 0x31, 0xa0, 0x00, 0x41, 0x21, 0x20, 0x00, 0xa7, 0x1b, - 0x00, 0x01, 0xd2, 0x00, 0x30, 0x00, 0x20, 0x0f, 0xa7, 0xf4, 0xff, 0xdd, - 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x05, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x20, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x3c, 0x28, 0x2b, 0x7c, 0x26, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x2e, - 0x2d, 0x2f, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2c, - 0x25, 0x5f, 0x3e, 0x3f, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22, 0x2e, 0x61, 0x62, 0x63, - 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x79, 0x7a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x2e, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, - 0x51, 0x52, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x2e, - 0x2e, 0x2e, 0x2e, 0x2e, 0x41, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x6f, 0xff, 0xfe, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xfb, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x6f, 0xff, 0xff, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x80, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0xc0, 0x50, 0x00, 0x00, 0x00, 0x20, 0xc0, 0x10, 0x00, 0x00, 0x95, 0xe1, + 0x42, 0x20, 0x10, 0x0e, 0xa7, 0x28, 0x00, 0x0f, 0x40, 0x20, 0x10, 0x00, + 0x58, 0x20, 0x50, 0x04, 0x50, 0x20, 0x10, 0x08, 0x92, 0x00, 0x10, 0x02, + 0x58, 0x20, 0x50, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, + 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x85, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x00, 0x76, 0x00, 0x05, 0x00, 0x07, 0x1a, 0x00, + 0x00, 0x78, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x17, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x43, 0x43, 0x3a, - 0x20, 0x28, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x31, 0x31, 0x2e, - 0x34, 0x2e, 0x30, 0x2d, 0x31, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x31, - 0x7e, 0x32, 0x32, 0x2e, 0x30, 0x34, 0x29, 0x20, 0x31, 0x31, 0x2e, 0x34, - 0x2e, 0x30, 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, - 0x62, 0x00, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, 0x67, - 0x6e, 0x75, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, 0x6e, - 0x73, 0x79, 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, - 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, 0x74, - 0x65, 0x78, 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x00, - 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, 0x6f, - 0x74, 0x00, 0x2e, 0x62, 0x73, 0x73, 0x00, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xfe, 0xf5, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xfb, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x6f, 0xff, 0xff, 0xf6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x47, 0x43, 0x43, 0x3a, 0x20, 0x28, 0x53, 0x55, 0x53, 0x45, 0x20, 0x4c, + 0x69, 0x6e, 0x75, 0x78, 0x29, 0x20, 0x31, 0x32, 0x2e, 0x33, 0x2e, 0x30, + 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, 0x67, 0x6e, 0x75, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x79, + 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x2e, 0x72, + 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, 0x74, 0x65, 0x78, + 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x2e, 0x65, + 0x68, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x00, 0x2e, 0x64, 0x79, 0x6e, + 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, 0x6f, 0x74, 0x00, 0x2e, 0x64, + 0x61, 0x74, 0x61, 0x00, 0x2e, 0x62, 0x73, 0x73, 0x00, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, - 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, - 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x13, 0x6f, 0xff, 0xff, 0xf6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x37, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x88, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x88, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x17, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xb8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x08, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x09, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; diff --git a/tests/qtest/migration/s390x/a-b-bios.c b/tests/qtest/migration/s390x/a-b-bios.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/s390x/a-b-bios.c +++ b/tests/qtest/migration/s390x/a-b-bios.c @@ -XXX,XX +XXX,XX @@ * option) any later version. */ -#define LOADPARM_LEN 8 /* Needed for sclp.h */ +/* for sclp.h */ +#define LOADPARM_LEN 8 +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef unsigned char uint8_t; -#include <libc.h> -#include <s390-ccw.h> #include <sclp.h> -char stack[0x8000] __attribute__((aligned(4096))); - #define START_ADDRESS (1024 * 1024) #define END_ADDRESS (100 * 1024 * 1024) +/* at pc-bios/s390-ccw/start.S */ + +void __attribute__((weak)) consume_sclp_int(void) +{ +} + +static char _sccb[4096] __attribute__((__aligned__(4096))); +char stack[0x8000] __attribute__((aligned(4096))); + +static void sclp_service_call(unsigned int command) +{ + int cc; + + asm volatile( + " .insn rre,0xb2200000,%1,%2\n" /* servc %1,%2 */ + " ipm %0\n" + " srl %0,28" + : "=&d" (cc) : "d" (command), "a" (__pa(_sccb)) + : "cc", "memory"); + consume_sclp_int(); +} + +static void sclp_setup(void) +{ + WriteEventMask *sccb = (void *)_sccb; + + sccb->h.length = sizeof(WriteEventMask); + + sccb->mask_length = sizeof(unsigned int); + sccb->cp_receive_mask = 0; + sccb->cp_send_mask = SCLP_EVENT_MASK_MSG_ASCII; + + sclp_service_call(SCLP_CMD_WRITE_EVENT_MASK); +} + +static void putc(const char c) +{ + WriteEventData *sccb = (void *)_sccb; + int len = 1; + + sccb->data[0] = c; + + sccb->h.length = sizeof(WriteEventData) + len; + sccb->h.function_code = SCLP_FC_NORMAL_WRITE; + + sccb->ebh.length = sizeof(EventBufferHeader) + len; + sccb->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA; + sccb->ebh.flags = 0; + + sclp_service_call(SCLP_CMD_WRITE_EVENT_DATA); +} + void main(void) { unsigned long addr; sclp_setup(); - sclp_print("A"); + putc('A'); /* * Make sure all of the pages have consistent contents before incrementing @@ -XXX,XX +XXX,XX @@ void main(void) for (addr = START_ADDRESS; addr < END_ADDRESS; addr += 4096) { *(volatile char *)addr += 1; /* Change pages */ } - sclp_print("B"); + putc('B'); } } diff --git a/tests/qtest/migration/Makefile b/tests/qtest/migration/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/Makefile +++ b/tests/qtest/migration/Makefile @@ -XXX,XX +XXX,XX @@ TARGET_LIST = i386 aarch64 s390x ppc64 -SRC_PATH = ../.. +SRC_PATH = ../../../.. .PHONY: help $(TARGET_LIST) help: @@ -XXX,XX +XXX,XX @@ help: @echo " Possible targets are: $(TARGET_LIST)" override define __note -/* This file is automatically generated from the assembly file in - * tests/migration/$@. Edit that file and then run "make all" - * inside tests/migration to update, and then remember to send both +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/$@. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both * the header and the assembler differences in your patch submission. */ endef export __note $(TARGET_LIST): - $(MAKE) CROSS_PREFIX=$(CROSS_PREFIX) -C $@ + $(MAKE) SRC_PATH=$(SRC_PATH) CROSS_PREFIX=$(CROSS_PREFIX) -C $@ clean: for target in $(TARGET_LIST); do \ diff --git a/tests/qtest/migration/aarch64/Makefile b/tests/qtest/migration/aarch64/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/aarch64/Makefile +++ b/tests/qtest/migration/aarch64/Makefile @@ -XXX,XX +XXX,XX @@ all: a-b-kernel.h a-b-kernel.h: aarch64.kernel echo "$$__note" > $@ - xxd -i $< | sed -e 's/.*int.*//' >> $@ + xxd -i $< | sed -e '/.*int.*/d' >> $@ aarch64.kernel: aarch64.elf $(CROSS_PREFIX)objcopy -O binary $< $@ @@ -XXX,XX +XXX,XX @@ aarch64.elf: a-b-kernel.S $(CROSS_PREFIX)gcc -o $@ -nostdlib -Wl,--build-id=none $< clean: - $(RM) *.kernel *.elf + $(RM) *.kernel *.elf *.h diff --git a/tests/qtest/migration/aarch64/a-b-kernel.S b/tests/qtest/migration/aarch64/a-b-kernel.S index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/aarch64/a-b-kernel.S +++ b/tests/qtest/migration/aarch64/a-b-kernel.S @@ -XXX,XX +XXX,XX @@ # pc-relative address. Also the branch instructions should use relative # addresses only. -#include "../migration-test.h" +#include "../bootfile.h" .section .text diff --git a/tests/qtest/migration/i386/Makefile b/tests/qtest/migration/i386/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/i386/Makefile +++ b/tests/qtest/migration/i386/Makefile @@ -XXX,XX +XXX,XX @@ all: a-b-bootblock.h a-b-bootblock.h: x86.bootsect x86.o echo "$$__note" > header.tmp - xxd -i $< | sed -e 's/.*int.*//' >> header.tmp + xxd -i $< | sed -e '/.*int.*/d' >> header.tmp nm x86.o | awk '{print "#define SYM_"$$3" 0x"$$1}' >> header.tmp mv header.tmp $@ @@ -XXX,XX +XXX,XX @@ x86.o: a-b-bootblock.S $(CROSS_PREFIX)gcc -I.. -m32 -march=i486 -c $< -o $@ clean: - @rm -rf *.boot *.o *.bootsect + @rm -rf *.boot *.o *.bootsect *.h diff --git a/tests/qtest/migration/i386/a-b-bootblock.S b/tests/qtest/migration/i386/a-b-bootblock.S index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/i386/a-b-bootblock.S +++ b/tests/qtest/migration/i386/a-b-bootblock.S @@ -XXX,XX +XXX,XX @@ # # Author: dgilbert@redhat.com -#include "migration-test.h" +#include "../bootfile.h" #define ACPI_ENABLE 0xf1 #define ACPI_PORT_SMI_CMD 0xb2 diff --git a/tests/qtest/migration/ppc64/Makefile b/tests/qtest/migration/ppc64/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/ppc64/Makefile +++ b/tests/qtest/migration/ppc64/Makefile @@ -XXX,XX +XXX,XX @@ all: a-b-kernel.h a-b-kernel.h: ppc64.kernel echo "$$__note" > $@ - xxd -i $< | sed -e 's/.*int.*//' >> $@ + xxd -i $< | sed -e '/.*int.*/d' >> $@ ppc64.kernel: ppc64.elf $(CROSS_PREFIX)objcopy -O binary -S $< $@ ppc64.elf: a-b-kernel.S - $(CROSS_PREFIX)gcc -static -o $@ -nostdlib -Wl,--build-id=none $< + $(CROSS_PREFIX)gcc -mbig-endian -static -o $@ -nostdlib -Wl,--build-id=none $< clean: - $(RM) *.kernel *.elf + $(RM) *.kernel *.elf *.h diff --git a/tests/qtest/migration/ppc64/a-b-kernel.S b/tests/qtest/migration/ppc64/a-b-kernel.S index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/ppc64/a-b-kernel.S +++ b/tests/qtest/migration/ppc64/a-b-kernel.S @@ -XXX,XX +XXX,XX @@ # This work is licensed under the terms of the GNU GPL, version 2 or later. # See the COPYING file in the top-level directory. -#include "../migration-test.h" +#include "../bootfile.h" .section .text diff --git a/tests/qtest/migration/s390x/Makefile b/tests/qtest/migration/s390x/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/s390x/Makefile +++ b/tests/qtest/migration/s390x/Makefile @@ -XXX,XX +XXX,XX @@ .PHONY: all clean all: a-b-bios.h -fwdir=../../../pc-bios/s390-ccw +fwdir=$(SRC_PATH)/pc-bios/s390-ccw CFLAGS+=-ffreestanding -fno-delete-null-pointer-checks -fPIE -Os \ -msoft-float -march=z900 -fno-asynchronous-unwind-tables \ @@ -XXX,XX +XXX,XX @@ CFLAGS+=-ffreestanding -fno-delete-null-pointer-checks -fPIE -Os \ a-b-bios.h: s390x.elf echo "$$__note" > header.tmp - xxd -i $< | sed -e 's/.*int.*//' >> header.tmp + xxd -i $< | sed -e '/.*int.*/d' >> header.tmp mv header.tmp $@ # We use common-page-size=16 to avoid big padding in the ELF file s390x.elf: a-b-bios.c $(CROSS_PREFIX)gcc $(CFLAGS) -I$(fwdir) $(fwdir)/start.S \ - $(fwdir)/sclp.c -Wl,-zcommon-page-size=16 -o $@ $< + -Wl,-zcommon-page-size=16 -o $@ $< $(CROSS_PREFIX)strip $@ clean: - @rm -rf *.elf *.o + @rm -rf *.elf *.o *.h -- 2.53.0
The following changes since commit b83371668192a705b878e909c5ae9c1233cbd5fb: Merge tag 'pbouvier/pr/plugins-20260618' of https://gitlab.com/p-b-o/qemu into staging (2026-06-19 15:00:01 -0400) are available in the Git repository at: https://gitlab.com/peterx/qemu.git tags/next-pull-request for you to fetch changes up to 1f3241bcaeee0df47f72edf6abad71fe36f3b0e7: system/physmem: make ram_block_discard_range() handle guest_memfd (2026-06-22 17:08:49 -0400) ---------------------------------------------------------------- Migration and mem pull request - Maciej's patch to fix rare crash in VFIO multifd thread pool mgmt - Peter's cleanup of @cpr-exec-command doc in migration.json - Akihiko's patch to fix a TSAN warning on ram_list operations - Bibo's migration-test coverage for loongarch - Peter's update on a-b-boot image - Marc-André's virtio-mem fix for CoCo ---------------------------------------------------------------- Akihiko Odaki (1): system/physmem: Synchronize ram_list accesses Bibo Mao (1): tests/qtest/migration: Add migration test on loongarch Gavin Shan (1): system/memory: Remove MAX_PHYS_ADDR Maciej S. Szmigiero (1): thread-pool: Allow at least 1 thread in thread_pool_adjust_max_threads_to_work() Marc-André Lureau (11): system/memory: split RamDiscardManager into source and manager system/memory: move RamDiscardManager to separate compilation unit system/memory: constify section arguments system/ram-discard-manager: implement replay via is_populated iteration virtio-mem: remove replay_populated/replay_discarded implementation system/ram-discard-manager: drop replay from source interface system/memory: implement RamDiscardManager multi-source aggregation system/physmem: destroy ram block attributes before RCU-deferred reclaim system/memory: add RamDiscardManager reference counting and cleanup tests: add unit tests for RamDiscardManager multi-source aggregation system/physmem: make ram_block_discard_range() handle guest_memfd Peter Xu (3): qapi/migration: Remove @cpr-exec-command doc in MigrationParameter migration: Use OBJECT_DECLARE_SIMPLE_TYPE migration/tests: Update a-b-boot images for all archs MAINTAINERS | 5 + qapi/migration.json | 4 - include/hw/vfio/vfio-container.h | 2 +- include/hw/vfio/vfio-cpr.h | 2 +- include/hw/virtio/virtio-mem.h | 3 - include/system/memory.h | 282 +--- include/system/ram-discard-manager.h | 358 +++++ include/system/ramblock.h | 5 +- migration/migration.h | 9 +- tests/qtest/migration/aarch64/a-b-kernel.h | 2 + tests/qtest/migration/bootfile.h | 4 + tests/qtest/migration/i386/a-b-bootblock.h | 2 + .../qtest/migration/loongarch64/a-b-kernel.h | 20 + tests/qtest/migration/ppc64/a-b-kernel.h | 2 + tests/qtest/migration/s390x/a-b-bios.h | 272 ++-- accel/kvm/kvm-all.c | 2 +- hw/vfio/cpr-legacy.c | 4 +- hw/vfio/listener.c | 10 +- hw/virtio/virtio-mem.c | 259 +--- migration/migration.c | 7 +- migration/ram.c | 16 +- system/memory.c | 88 +- system/memory_mapping.c | 4 +- system/physmem.c | 43 +- system/ram-block-attributes.c | 255 +--- system/ram-discard-manager.c | 612 ++++++++ target/i386/kvm/tdx.c | 2 +- tests/qtest/migration/bootfile.c | 4 + tests/qtest/migration/framework.c | 6 + tests/unit/test-ram-discard-manager-stubs.c | 48 + tests/unit/test-ram-discard-manager.c | 1235 +++++++++++++++++ util/thread-pool.c | 2 +- rust/bindings/system-sys/lib.rs | 2 +- system/meson.build | 1 + system/trace-events | 2 +- tests/qtest/meson.build | 3 +- tests/qtest/migration/Makefile | 4 +- tests/qtest/migration/loongarch64/Makefile | 20 + .../qtest/migration/loongarch64/a-b-kernel.S | 46 + tests/unit/meson.build | 8 +- 40 files changed, 2656 insertions(+), 999 deletions(-) create mode 100644 include/system/ram-discard-manager.h create mode 100644 tests/qtest/migration/loongarch64/a-b-kernel.h create mode 100644 system/ram-discard-manager.c create mode 100644 tests/unit/test-ram-discard-manager-stubs.c create mode 100644 tests/unit/test-ram-discard-manager.c create mode 100644 tests/qtest/migration/loongarch64/Makefile create mode 100644 tests/qtest/migration/loongarch64/a-b-kernel.S -- 2.54.0
From: "Maciej S. Szmigiero" <maciej.szmigiero@oracle.com> thread_pool_adjust_max_threads_to_work() is supposed to give each task its own thread by setting the pool max thread count limit accordingly. However, if there aren't any tasks currently in the pool the pool max thread count will be set to 0, which will trigger an assertion failure in thread_pool_set_max_threads() - because setting this value would completely block the pool by not allowing it to process any submitted tasks. This also can happen if a task is submitted via thread_pool_submit_immediate() to an empty pool but the task completes so quickly that by the time this function calls thread_pool_adjust_max_threads_to_work() the pool again has no unfinished tasks in it. Quoting from Maciej on reproducing this issue: It's difficult to reproduce in most setups. My main VFIO live migration setup never hit it for more than a year, other similar setup hit it recently 3 times. On the other hand, putting sleep(5) in the middle of thread_pool_submit_immediate() makes it reproduce nearly always for me. Fix this by making sure that the pool is allowed to create at least 1 thread. Fixes: b5aa74968b27 ("thread-pool: Implement generic (non-AIO) pool support") Signed-off-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/b76c763f576b0fb8a35960a8e3c3da59701d2a74.1779390317.git.maciej.szmigiero@oracle.com [peterx: added quote into commit message on reproduce details of the issue] Signed-off-by: Peter Xu <peterx@redhat.com> --- util/thread-pool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/thread-pool.c b/util/thread-pool.c index XXXXXXX..XXXXXXX 100644 --- a/util/thread-pool.c +++ b/util/thread-pool.c @@ -XXX,XX +XXX,XX @@ bool thread_pool_adjust_max_threads_to_work(ThreadPool *pool) { QEMU_LOCK_GUARD(&pool->cur_work_lock); - return thread_pool_set_max_threads(pool, pool->cur_work); + return thread_pool_set_max_threads(pool, MAX(pool->cur_work, 1)); } -- 2.54.0
This parameter was developed during similar window when the deduplication work was done in commit 2220c2b9ef ("qapi/migration: Don't document MigrationParameter"), hence it was overlooked. Remove the leftover parameter. After all, MigrationParameter is already in the exception list of the doc sanity checker. Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Markus Armbruster <armbru@redhat.com> Link: https://lore.kernel.org/r/20260528201236.359452-1-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- qapi/migration.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qapi/migration.json b/qapi/migration.json index XXXXXXX..XXXXXXX 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -XXX,XX +XXX,XX @@ # Migration parameters enumeration. The enumeration values mirror the # members of @MigrationParameters. # -# @cpr-exec-command: Command to start the new QEMU process when @mode -# is @cpr-exec. The first list element is the program's filename, -# the remainder its arguments. (Since 10.2) -# # Features: # # @unstable: Members @x-checkpoint-delay, @x-rdma-chunk-size, and -- 2.54.0
From: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Alex Bennée reported a ThreadSanitizer warning about a plain concurrent access to ram_list [1]. Ensure the concurrent accesses to ram_list are properly synchronized with atomic accesses, mutexes, or RCU. First, the plain assignments of ram_list.mru_block are replaced with qatomic_set(). A comment in qemu_get_ram_block() explains why the ordering requirement is relaxed, but it still needs to be atomically accessed. include/qemu/atomic.h says: > The C11 memory model says that variables that are accessed from > different threads should at least be done with __ATOMIC_RELAXED > primitives or the result is undefined. Generally this has little to > no effect on the generated code but not using the atomic primitives > will get flagged by sanitizers as a violation. Second, ram_list.version accesses are replaced with atomic operations or protected with a mutex. Unlike ram_list.mru_block, ram_list.version has tighter ordering requirements for one of its goals: ensuring that the reader-held rs->last_seen_block value is invalidated whenever a RAM block is reclaimed between two RCU reader critical sections. Below are steps a reader and an updater follow: Reader: R-1. Enter the first RCU read-side critical section: R-1-1. rs->last_version = qatomic_load_acquire(&ram_list.version) R-1-2. rs->last_seen_block = an element of ram_list.blocks R-2. Enter the second RCU read-side critical section: R-2-1. if (qatomic_read(&ram_list.version) != rs->last_version) R-2-2. rs->last_seen_block = NULL Updater: W-1. Enter a ram_list.mutex critical section W-1-1. Update ram_list.blocks W-1-2. qatomic_store_release(&ram_list.version, ram_list.version + 1) W-2. Enter another ram_list.mutex critical section W-2-1. QLIST_REMOVE_RCU(block, next) W-2-2. qatomic_store_release(&ram_list.version, ram_list.version + 1) W-2-3. call_rcu(block, reclaim_ramblock, rcu) W-1-2 represents the write observed by R-1-1. ram_list.version is read non-atomically on the update side because the update side is serialized with ram_list.mutex. The other ram_list accesses in these steps are reasoned about in two cases. When the grace period of W-2-3 contains R-2: qatomic_load_acquire() at R-1-1 and qatomic_store_release() at W-1-2 enforce the following ordering: W-1-1 -> W-1-2 -> R-1-1 -> R-1-2 The value of ram_list.blocks stored by W-1-1 or a newer value that was loaded by R-1-2 is still valid because of the grace period. When the grace period of W-2-3 ends before R-2: call_rcu() at W-2-3 and the read-side critical section at R-2 ensure the following ordering: W-2-2 -> W-2-3 -> the grace period -> R-2 -> R-2-1 The value of ram_list.version stored by W-2-2 or a newer value that was loaded by R-2-1 differs from rs->last_version and the reader invalidates rs->last_seen_block. Together, these steps ensure that rs->last_seen_block is invalidated whenever necessary. With added atomic operations, pre-existing memory barriers are no longer necessary and are removed. Any other ram_list accesses are already properly synchronized. [1] https://lore.kernel.org/qemu-devel/878q9fbmap.fsf@draig.linaro.org/ Signed-off-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Link: https://lore.kernel.org/r/20260523-tsan-v1-1-07d5eb9dcaa2@rsg.ci.i.u-tokyo.ac.jp Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/ram.c | 10 +++++----- system/physmem.c | 16 +++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static void ram_state_reset(RAMState *rs) rs->last_seen_block = NULL; rs->last_page = 0; - rs->last_version = ram_list.version; + + /* Read version before ram_list.blocks */ + rs->last_version = qatomic_load_acquire(&ram_list.version); + rs->xbzrle_started = false; ram_page_hint_reset(&rs->page_hint); @@ -XXX,XX +XXX,XX @@ static int ram_save_iterate(QEMUFile *f, void *opaque) */ WITH_QEMU_LOCK_GUARD(&rs->bitmap_mutex) { WITH_RCU_READ_LOCK_GUARD() { - if (ram_list.version != rs->last_version) { + if (qatomic_read(&ram_list.version) != rs->last_version) { ram_state_reset(rs); } - /* Read version before ram_list.blocks */ - smp_rmb(); - ret = rdma_registration_start(f, RAM_CONTROL_ROUND); if (ret < 0) { qemu_file_set_error(f, ret); diff --git a/system/physmem.c b/system/physmem.c index XXXXXXX..XXXXXXX 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -XXX,XX +XXX,XX @@ found: /* It is safe to write mru_block outside the BQL. This * is what happens: * - * mru_block = xxx + * qatomic_set(&mru_block, xxx) * rcu_read_unlock() * xxx removed from list * rcu_read_lock() * read mru_block - * mru_block = NULL; + * qatomic_set(&mru_block, NULL); * call_rcu(reclaim_ramblock, xxx); * rcu_read_unlock() * @@ -XXX,XX +XXX,XX @@ found: * when it was placed into the list. Here we're just making an extra * copy of the pointer. */ - ram_list.mru_block = block; + qatomic_set(&ram_list.mru_block, block); return block; } @@ -XXX,XX +XXX,XX @@ static void ram_block_add(RAMBlock *new_block, Error **errp) } else { /* list is empty */ QLIST_INSERT_HEAD_RCU(&ram_list.blocks, new_block, next); } - ram_list.mru_block = NULL; + qatomic_set(&ram_list.mru_block, NULL); /* Write list before version */ - smp_wmb(); - ram_list.version++; + qatomic_store_release(&ram_list.version, ram_list.version + 1); qemu_mutex_unlock_ramlist(); physical_memory_set_dirty_range(new_block->offset, @@ -XXX,XX +XXX,XX @@ void qemu_ram_free(RAMBlock *block) name = cpr_name(block->mr); cpr_delete_fd(name, 0); QLIST_REMOVE_RCU(block, next); - ram_list.mru_block = NULL; + qatomic_set(&ram_list.mru_block, NULL); /* Write list before version */ - smp_wmb(); - ram_list.version++; + qatomic_store_release(&ram_list.version, ram_list.version + 1); call_rcu(block, reclaim_ramblock, rcu); qemu_mutex_unlock_ramlist(); } -- 2.54.0
From: Gavin Shan <gshan@redhat.com> Remove MAX_PHYS_ADDR and MAX_PHYS_ADDR_SPACE_BITS as they're not used since the addition by commit 052e87b073cb ("memory: make section size a 128-bit integer"). Signed-off-by: Gavin Shan <gshan@redhat.com> Link: https://lore.kernel.org/r/20260608002303.851456-1-gshan@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/memory.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ enum device_endian { #define RAM_ADDR_INVALID (~(ram_addr_t)0) -#define MAX_PHYS_ADDR_SPACE_BITS 62 -#define MAX_PHYS_ADDR (((hwaddr)1 << MAX_PHYS_ADDR_SPACE_BITS) - 1) - #define TYPE_MEMORY_REGION "memory-region" DECLARE_INSTANCE_CHECKER(MemoryRegion, MEMORY_REGION, TYPE_MEMORY_REGION) -- 2.54.0
Migration object's class has nothing special, switch to the newly introduced macro. Suggested-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260609172514.2037645-2-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- migration/migration.h | 9 +-------- migration/migration.c | 7 +++---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/migration/migration.h b/migration/migration.h index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.h +++ b/migration/migration.h @@ -XXX,XX +XXX,XX @@ void fill_destination_postcopy_migration_info(MigrationInfo *info); #define TYPE_MIGRATION "migration" -typedef struct MigrationClass MigrationClass; -DECLARE_OBJ_CHECKERS(MigrationState, MigrationClass, - MIGRATION_OBJ, TYPE_MIGRATION) - -struct MigrationClass { - /*< private >*/ - DeviceClass parent_class; -}; +OBJECT_DECLARE_SIMPLE_TYPE(MigrationState, MIGRATION); struct MigrationState { /*< private >*/ diff --git a/migration/migration.c b/migration/migration.c index XXXXXXX..XXXXXXX 100644 --- a/migration/migration.c +++ b/migration/migration.c @@ -XXX,XX +XXX,XX @@ void migration_object_init(void) { /* This can only be called once. */ assert(!current_migration); - current_migration = MIGRATION_OBJ(object_new(TYPE_MIGRATION)); + current_migration = MIGRATION(object_new(TYPE_MIGRATION)); /* * Init the migrate incoming object as well no matter whether @@ -XXX,XX +XXX,XX @@ static void migration_class_init(ObjectClass *klass, const void *data) static void migration_instance_finalize(Object *obj) { - MigrationState *ms = MIGRATION_OBJ(obj); + MigrationState *ms = MIGRATION(obj); qapi_free_BitmapMigrationNodeAliasList(ms->parameters.block_bitmap_mapping); qapi_free_strList(ms->parameters.cpr_exec_command); @@ -XXX,XX +XXX,XX @@ static void migration_instance_finalize(Object *obj) static void migration_instance_init(Object *obj) { - MigrationState *ms = MIGRATION_OBJ(obj); + MigrationState *ms = MIGRATION(obj); ms->state = MIGRATION_STATUS_NONE; ms->mbps = -1; @@ -XXX,XX +XXX,XX @@ static const TypeInfo migration_type = { */ .parent = TYPE_DEVICE, .class_init = migration_class_init, - .class_size = sizeof(MigrationClass), .instance_size = sizeof(MigrationState), .instance_init = migration_instance_init, .instance_finalize = migration_instance_finalize, -- 2.54.0
From: Bibo Mao <maobibo@loongson.cn> Add migration qtest on loongarch64 system, the test passes to run with the following result: qemu:qtest+qtest-loongarch64/qtest-loongarch64/migration-test OK 15.94s 9 subtests passed Signed-off-by: Bibo Mao <maobibo@loongson.cn> Acked-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Link: https://lore.kernel.org/r/20260611084312.70042-1-maobibo@loongson.cn Signed-off-by: Peter Xu <peterx@redhat.com> --- MAINTAINERS | 1 + tests/qtest/migration/bootfile.h | 4 ++ .../qtest/migration/loongarch64/a-b-kernel.h | 20 ++++++++ tests/qtest/migration/bootfile.c | 4 ++ tests/qtest/migration/framework.c | 6 +++ tests/qtest/meson.build | 3 +- tests/qtest/migration/Makefile | 4 +- tests/qtest/migration/loongarch64/Makefile | 20 ++++++++ .../qtest/migration/loongarch64/a-b-kernel.S | 46 +++++++++++++++++++ 9 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 tests/qtest/migration/loongarch64/a-b-kernel.h create mode 100644 tests/qtest/migration/loongarch64/Makefile create mode 100644 tests/qtest/migration/loongarch64/a-b-kernel.S diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/intc/loongarch_*.c F: hw/intc/loongson_ipi_common.c F: hw/rtc/ls7a_rtc.c F: gdbstub/gdb-xml/loongarch*.xml +F: tests/qtest/migration/loongarch64/ M68K Machines ------------- diff --git a/tests/qtest/migration/bootfile.h b/tests/qtest/migration/bootfile.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/bootfile.h +++ b/tests/qtest/migration/bootfile.h @@ -XXX,XX +XXX,XX @@ */ #define ARM_TEST_MAX_KERNEL_SIZE (512 * 1024) +/* LoongArch64 */ +#define LOONGARCH_TEST_MEM_START (32 * 1024 * 1024) +#define LOONGARCH_TEST_MEM_END (100 * 1024 * 1024) + #ifndef MIGRATION_GUEST_CODE void bootfile_delete(void); char *bootfile_create(const char *arch, const char *dir, bool suspend_me); diff --git a/tests/qtest/migration/loongarch64/a-b-kernel.h b/tests/qtest/migration/loongarch64/a-b-kernel.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/migration/loongarch64/a-b-kernel.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/* + * This file is automatically generated from the assembly file in + * tests/qtest/migration/loongarch64. Edit that file and then run "make" + * inside tests/qtest/migration to update, and then remember to send both + * the header and the assembler differences in your patch submission. + */ +unsigned char loongarch64_kernel[] = { + 0x0c, 0xc0, 0x3f, 0x14, 0x8c, 0x81, 0x87, 0x03, 0x0d, 0x04, 0x81, 0x03, + 0x8d, 0x01, 0x00, 0x29, 0x0c, 0x00, 0x04, 0x14, 0x0d, 0x80, 0x0c, 0x14, + 0x2e, 0x00, 0x00, 0x14, 0x10, 0xc0, 0x3f, 0x14, 0x10, 0x82, 0x87, 0x03, + 0x11, 0x08, 0x81, 0x03, 0x80, 0x01, 0x00, 0x29, 0x8c, 0xb9, 0x10, 0x00, + 0x8d, 0xf9, 0xff, 0x5f, 0x12, 0x00, 0xc0, 0x02, 0x0c, 0x00, 0x04, 0x14, + 0x8f, 0x01, 0x00, 0x2a, 0xef, 0x05, 0x80, 0x02, 0xef, 0x5d, 0x00, 0x00, + 0x8f, 0x01, 0x00, 0x29, 0x8c, 0xb9, 0x10, 0x00, 0x8d, 0xed, 0xff, 0x5f, + 0x52, 0x06, 0xc0, 0x02, 0x52, 0x7e, 0x40, 0x03, 0x5f, 0xde, 0xff, 0x47, + 0x11, 0x02, 0x00, 0x29, 0xff, 0xd7, 0xff, 0x53 +}; + diff --git a/tests/qtest/migration/bootfile.c b/tests/qtest/migration/bootfile.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/bootfile.c +++ b/tests/qtest/migration/bootfile.c @@ -XXX,XX +XXX,XX @@ #include "bootfile.h" #include "i386/a-b-bootblock.h" #include "aarch64/a-b-kernel.h" +#include "loongarch64/a-b-kernel.h" #include "ppc64/a-b-kernel.h" #include "s390x/a-b-bios.h" @@ -XXX,XX +XXX,XX @@ char *bootfile_create(const char *arch, const char *dir, bool suspend_me) content = aarch64_kernel; len = sizeof(aarch64_kernel); g_assert(sizeof(aarch64_kernel) <= ARM_TEST_MAX_KERNEL_SIZE); + } else if (strcmp(arch, "loongarch64") == 0) { + content = loongarch64_kernel; + len = sizeof(loongarch64_kernel); } else { g_assert_not_reached(); } diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/framework.c +++ b/tests/qtest/migration/framework.c @@ -XXX,XX +XXX,XX @@ int migrate_args(char **from, char **to, MigrateStart *args) arch_opts = g_strdup_printf("-cpu max -kernel %s", bootpath); start_address = ARM_TEST_MEM_START; end_address = ARM_TEST_MEM_END; + } else if (strcmp(arch, "loongarch64") == 0) { + memory_size = "256M"; + machine_alias = "virt"; + arch_opts = g_strdup_printf("-bios %s", bootpath); + start_address = LOONGARCH_TEST_MEM_START; + end_address = LOONGARCH_TEST_MEM_END; } else { g_assert_not_reached(); } diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ 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'] + 'cpu-plug-test', + 'migration-test'] qtests_m68k = ['boot-serial-test'] + \ qtests_filter diff --git a/tests/qtest/migration/Makefile b/tests/qtest/migration/Makefile index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/Makefile +++ b/tests/qtest/migration/Makefile @@ -XXX,XX +XXX,XX @@ # See the COPYING file in the top-level directory. # -TARGET_LIST = i386 aarch64 s390x ppc64 +TARGET_LIST = i386 aarch64 s390x ppc64 loongarch64 SRC_PATH = ../../../.. @@ -XXX,XX +XXX,XX @@ help: @echo " Possible targets are: $(TARGET_LIST)" override define __note +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/$@. Edit that file and then run "make" diff --git a/tests/qtest/migration/loongarch64/Makefile b/tests/qtest/migration/loongarch64/Makefile new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/migration/loongarch64/Makefile @@ -XXX,XX +XXX,XX @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# To specify cross compiler prefix, use CROSS_PREFIX= +# $ make CROSS_PREFIX=loongarch64-linux-gnu- + +.PHONY: all clean +all: a-b-kernel.h + +a-b-kernel.h: loongarch64.kernel + echo "$$__note" > $@ + xxd -i $< | sed -e 's/.*int.*//' >> $@ + +loongarch64.kernel: loongarch64.elf + $(CROSS_PREFIX)objcopy -j .text -O binary $< $@ + +loongarch64.elf: a-b-kernel.S + $(CROSS_PREFIX)gcc -o $@ -nostdlib -Wl,--build-id=none $< + +clean: + $(RM) *.kernel *.elf diff --git a/tests/qtest/migration/loongarch64/a-b-kernel.S b/tests/qtest/migration/loongarch64/a-b-kernel.S new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/migration/loongarch64/a-b-kernel.S @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "../bootfile.h" + +#define LOONGARCH_CSR_CRMD 0 +#define LOONGARCH_VIRT_UART 0x1FE001E0 +.section .text + + .globl _start +_start: + /* output char 'A' to UART16550 */ + li.d $t0, LOONGARCH_VIRT_UART + li.w $t1, 'A' + st.b $t1, $t0, 0 + + /* traverse test memory region */ + li.d $t0, LOONGARCH_TEST_MEM_START + li.d $t1, LOONGARCH_TEST_MEM_END + li.d $t2, TEST_MEM_PAGE_SIZE + li.d $t4, LOONGARCH_VIRT_UART + li.w $t5, 'B' + +clean: + st.b $zero, $t0, 0 + add.d $t0, $t0, $t2 + bne $t0, $t1, clean + /* keeps a counter so we can limit the output speed */ + addi.d $t6, $zero, 0 + +mainloop: + li.d $t0, LOONGARCH_TEST_MEM_START + +innerloop: + ld.bu $t3, $t0, 0 + addi.w $t3, $t3, 1 + ext.w.b $t3, $t3 + st.b $t3, $t0, 0 + add.d $t0, $t0, $t2 + bne $t0, $t1, innerloop + + addi.d $t6, $t6, 1 + andi $t6, $t6, 31 + bnez $t6, mainloop + + st.b $t5, $t4, 0 + b mainloop -- 2.54.0
We just added a GPLv2 licence identifier to __note, update all headers with that. When at this, s390 seems to generate a new binary with the latest cross-compiler I have here: s390x-linux-gnu-gcc (GCC) 15.2.1 20250808 (Red Hat Cross 15.2.1-1) The hope is the new generated should normally be better. Update them. Cc: Bibo Mao <maobibo@loongson.cn> Cc: Cornelia Huck <cohuck@redhat.com> Cc: Eric Farman <farman@linux.ibm.com> Cc: Matthew Rosato <mjrosato@linux.ibm.com> Cc: Richard Henderson <richard.henderson@linaro.org> Cc: Ilya Leoshkevich <iii@linux.ibm.com> Cc: David Hildenbrand <david@kernel.org> Cc: qemu-s390x@nongnu.org Reviewed-by: Bibo Mao <maobibo@loongson.cn> Based-on: <20260611084312.70042-1-maobibo@loongson.cn> Acked-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260612155307.2172358-1-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- tests/qtest/migration/aarch64/a-b-kernel.h | 2 + tests/qtest/migration/i386/a-b-bootblock.h | 2 + tests/qtest/migration/ppc64/a-b-kernel.h | 2 + tests/qtest/migration/s390x/a-b-bios.h | 272 ++++++++++----------- 4 files changed, 138 insertions(+), 140 deletions(-) diff --git a/tests/qtest/migration/aarch64/a-b-kernel.h b/tests/qtest/migration/aarch64/a-b-kernel.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/aarch64/a-b-kernel.h +++ b/tests/qtest/migration/aarch64/a-b-kernel.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/aarch64. Edit that file and then run "make" diff --git a/tests/qtest/migration/i386/a-b-bootblock.h b/tests/qtest/migration/i386/a-b-bootblock.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/i386/a-b-bootblock.h +++ b/tests/qtest/migration/i386/a-b-bootblock.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/i386. Edit that file and then run "make" diff --git a/tests/qtest/migration/ppc64/a-b-kernel.h b/tests/qtest/migration/ppc64/a-b-kernel.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/ppc64/a-b-kernel.h +++ b/tests/qtest/migration/ppc64/a-b-kernel.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/ppc64. Edit that file and then run "make" diff --git a/tests/qtest/migration/s390x/a-b-bios.h b/tests/qtest/migration/s390x/a-b-bios.h index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/migration/s390x/a-b-bios.h +++ b/tests/qtest/migration/s390x/a-b-bios.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + /* * This file is automatically generated from the assembly file in * tests/qtest/migration/s390x. Edit that file and then run "make" @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x07, 0x00, 0x40, - 0x00, 0x10, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x0e, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x00, 0x00, @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x21, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x21, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, + 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x64, 0x74, 0xe5, 0x51, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x74, 0xe5, 0x52, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x6c, 0x64, 0x36, 0x34, 0x2e, 0x73, 0x6f, - 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xeb, 0xdf, 0xf0, 0x68, + 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x05, 0xa7, 0xfb, 0xff, 0x60, + 0xc0, 0x10, 0x00, 0x00, 0x96, 0xd4, 0xa7, 0x28, 0x00, 0x1c, 0x40, 0x20, + 0x10, 0x00, 0xa5, 0x2c, 0x00, 0x04, 0xe3, 0x20, 0x10, 0x0a, 0x00, 0x24, + 0xa7, 0x28, 0x00, 0x40, 0x40, 0x20, 0x10, 0x12, 0x58, 0x20, 0xd0, 0x00, + 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, 0x88, 0x30, 0x00, 0x1c, + 0xc0, 0xe5, 0x00, 0x00, 0x00, 0x63, 0xa7, 0x29, 0x00, 0x41, 0xc0, 0xe5, + 0x00, 0x00, 0x00, 0xbf, 0xa5, 0x2e, 0x00, 0x10, 0xa7, 0x19, 0x63, 0x00, + 0x92, 0x00, 0x20, 0x00, 0xa7, 0x2b, 0x10, 0x00, 0xa7, 0x17, 0xff, 0xfc, + 0xa5, 0x1e, 0x00, 0x10, 0xa7, 0x29, 0x63, 0x00, 0xe3, 0x30, 0x10, 0x00, + 0x00, 0x90, 0xa7, 0x3a, 0x00, 0x01, 0x42, 0x30, 0x10, 0x00, 0xa7, 0x1b, + 0x10, 0x00, 0xa7, 0x27, 0xff, 0xf7, 0xa7, 0x29, 0x00, 0x42, 0xc0, 0xe5, + 0x00, 0x00, 0x00, 0xa1, 0xa7, 0xf4, 0xff, 0xec, 0xc0, 0xf0, 0x00, 0x00, + 0x56, 0x44, 0xc0, 0x20, 0x00, 0x00, 0x0e, 0x91, 0xe3, 0x20, 0x20, 0x00, + 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x9e, 0x8b, 0xb9, 0x0b, 0x00, 0x32, + 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, 0xa7, 0x3b, 0xff, 0xff, + 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, 0x00, 0x44, 0xb9, 0x04, + 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, 0x10, 0x00, 0x10, 0x00, + 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, 0xc0, 0x20, 0x00, 0x00, + 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x57, + 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, 0xff, 0x89, 0xd7, 0x00, + 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x4c, 0xb2, 0xb2, + 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, + 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, 0x00, 0x24, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x47, 0xd2, 0x07, 0x01, 0xb0, 0x10, 0x00, 0xc0, 0x10, + 0x00, 0x00, 0x00, 0x39, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x66, 0xf0, 0x00, + 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, 0x01, 0xf8, 0x00, 0x24, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x32, 0xd2, 0x07, 0x01, 0xf0, 0x10, 0x00, + 0xc0, 0x10, 0x00, 0x00, 0x00, 0x20, 0xb2, 0xb2, 0x10, 0x00, 0xeb, 0x00, + 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, + 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x94, 0x00, + 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0x07, 0x07, + 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, + 0x07, 0xfe, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xc0, 0x50, 0x00, 0x00, + 0x00, 0x20, 0xc0, 0x10, 0x00, 0x00, 0x95, 0xf5, 0x42, 0x20, 0x10, 0x0e, + 0xa7, 0x28, 0x00, 0x0f, 0x40, 0x20, 0x10, 0x00, 0x58, 0x20, 0x50, 0x04, + 0x50, 0x20, 0x10, 0x08, 0x92, 0x00, 0x10, 0x02, 0x58, 0x20, 0x50, 0x00, + 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, 0x88, 0x30, 0x00, 0x1c, + 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x85, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x00, 0x76, 0x00, 0x05, 0x00, 0x07, 0x1a, 0x00, 0x00, 0x78, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x07, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, - 0xeb, 0xdf, 0xf0, 0x68, 0x00, 0x24, 0xc0, 0xd0, 0x00, 0x00, 0x01, 0x05, - 0xa7, 0xfb, 0xff, 0x60, 0xc0, 0x10, 0x00, 0x00, 0x96, 0xc0, 0xa7, 0x28, - 0x00, 0x1c, 0x40, 0x20, 0x10, 0x00, 0xa5, 0x2c, 0x00, 0x04, 0xe3, 0x20, - 0x10, 0x0a, 0x00, 0x24, 0xa7, 0x28, 0x00, 0x40, 0x40, 0x20, 0x10, 0x12, - 0x58, 0x20, 0xd0, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, - 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0x63, 0xa7, 0x29, - 0x00, 0x41, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xbf, 0xa5, 0x2e, 0x00, 0x10, - 0xa7, 0x19, 0x63, 0x00, 0x92, 0x00, 0x20, 0x00, 0xa7, 0x2b, 0x10, 0x00, - 0xa7, 0x17, 0xff, 0xfc, 0xa5, 0x1e, 0x00, 0x10, 0xa7, 0x29, 0x63, 0x00, - 0xe3, 0x30, 0x10, 0x00, 0x00, 0x90, 0xa7, 0x3a, 0x00, 0x01, 0x42, 0x30, - 0x10, 0x00, 0xa7, 0x1b, 0x10, 0x00, 0xa7, 0x27, 0xff, 0xf7, 0xa7, 0x29, - 0x00, 0x42, 0xc0, 0xe5, 0x00, 0x00, 0x00, 0xa1, 0xa7, 0xf4, 0xff, 0xec, - 0xc0, 0xf0, 0x00, 0x00, 0x56, 0x30, 0xc0, 0x20, 0x00, 0x00, 0x0e, 0x7d, - 0xe3, 0x20, 0x20, 0x00, 0x00, 0x04, 0xc0, 0x30, 0x00, 0x00, 0x9e, 0x77, - 0xb9, 0x0b, 0x00, 0x32, 0xb9, 0x02, 0x00, 0x33, 0xa7, 0x84, 0x00, 0x19, - 0xa7, 0x3b, 0xff, 0xff, 0xeb, 0x43, 0x00, 0x08, 0x00, 0x0c, 0xb9, 0x02, - 0x00, 0x44, 0xb9, 0x04, 0x00, 0x12, 0xa7, 0x84, 0x00, 0x09, 0xd7, 0xff, - 0x10, 0x00, 0x10, 0x00, 0x41, 0x10, 0x11, 0x00, 0xa7, 0x47, 0xff, 0xfb, - 0xc0, 0x20, 0x00, 0x00, 0x00, 0x0d, 0x44, 0x30, 0x20, 0x00, 0xc0, 0x20, - 0x00, 0x00, 0x00, 0x57, 0xd2, 0x0f, 0x01, 0xd0, 0x20, 0x00, 0xa7, 0xf4, - 0xff, 0x89, 0xd7, 0x00, 0x10, 0x00, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, - 0x00, 0x4c, 0xb2, 0xb2, 0x10, 0x00, 0xa7, 0xf4, 0x00, 0x00, 0xeb, 0x00, - 0xf0, 0x00, 0x00, 0x25, 0x96, 0x02, 0xf0, 0x06, 0xeb, 0x00, 0xf0, 0x00, - 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x2a, 0xe3, 0x10, 0x01, 0xb8, - 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x47, 0xd2, 0x07, 0x01, 0xb0, - 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x39, 0xb2, 0xb2, 0x10, 0x00, - 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x25, 0x96, 0xff, 0xf0, 0x04, 0xeb, 0x66, - 0xf0, 0x00, 0x00, 0x2f, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x1a, 0xe3, 0x10, - 0x01, 0xf8, 0x00, 0x24, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x32, 0xd2, 0x07, - 0x01, 0xf0, 0x10, 0x00, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x20, 0xb2, 0xb2, - 0x10, 0x00, 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x25, 0x94, 0xfd, 0xf0, 0x06, - 0xeb, 0x00, 0xf0, 0x00, 0x00, 0x2f, 0x07, 0xfe, 0xeb, 0x66, 0xf0, 0x00, - 0x00, 0x25, 0x94, 0x00, 0xf0, 0x04, 0xeb, 0x66, 0xf0, 0x00, 0x00, 0x2f, - 0x07, 0xfe, 0x07, 0x07, 0x00, 0x02, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01, - 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x80, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, - 0xc0, 0x50, 0x00, 0x00, 0x00, 0x20, 0xc0, 0x10, 0x00, 0x00, 0x95, 0xe1, - 0x42, 0x20, 0x10, 0x0e, 0xa7, 0x28, 0x00, 0x0f, 0x40, 0x20, 0x10, 0x00, - 0x58, 0x20, 0x50, 0x04, 0x50, 0x20, 0x10, 0x08, 0x92, 0x00, 0x10, 0x02, - 0x58, 0x20, 0x50, 0x00, 0xb2, 0x20, 0x00, 0x21, 0xb2, 0x22, 0x00, 0x30, - 0x88, 0x30, 0x00, 0x1c, 0xc0, 0xf4, 0xff, 0xff, 0xff, 0x85, 0x07, 0x07, - 0x07, 0x07, 0x07, 0x07, 0x00, 0x76, 0x00, 0x05, 0x00, 0x07, 0x1a, 0x00, - 0x00, 0x78, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xfe, 0xf5, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, + 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xfb, 0x00, 0x00, 0x00, 0x00, @@ -XXX,XX +XXX,XX @@ unsigned char s390x_elf[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, - 0x47, 0x43, 0x43, 0x3a, 0x20, 0x28, 0x53, 0x55, 0x53, 0x45, 0x20, 0x4c, - 0x69, 0x6e, 0x75, 0x78, 0x29, 0x20, 0x31, 0x32, 0x2e, 0x33, 0x2e, 0x30, - 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, 0x67, 0x6e, 0x75, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x79, - 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x2e, 0x72, - 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, 0x74, 0x65, 0x78, - 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, 0x00, 0x2e, 0x65, - 0x68, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x00, 0x2e, 0x64, 0x79, 0x6e, - 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, 0x6f, 0x74, 0x00, 0x2e, 0x64, - 0x61, 0x74, 0x61, 0x00, 0x2e, 0x62, 0x73, 0x73, 0x00, 0x2e, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x43, 0x43, 0x3a, 0x20, 0x28, 0x47, 0x4e, 0x55, 0x29, 0x20, 0x31, + 0x35, 0x2e, 0x32, 0x2e, 0x31, 0x20, 0x32, 0x30, 0x32, 0x35, 0x30, 0x38, + 0x30, 0x38, 0x20, 0x28, 0x52, 0x65, 0x64, 0x20, 0x48, 0x61, 0x74, 0x20, + 0x43, 0x72, 0x6f, 0x73, 0x73, 0x20, 0x31, 0x35, 0x2e, 0x32, 0x2e, 0x31, + 0x2d, 0x31, 0x29, 0x00, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, + 0x61, 0x62, 0x00, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x00, 0x2e, + 0x67, 0x6e, 0x75, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x00, 0x2e, 0x64, 0x79, + 0x6e, 0x73, 0x79, 0x6d, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, + 0x00, 0x2e, 0x72, 0x65, 0x6c, 0x61, 0x2e, 0x64, 0x79, 0x6e, 0x00, 0x2e, + 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x72, 0x6f, 0x64, 0x61, 0x74, 0x61, + 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x67, + 0x6f, 0x74, 0x00, 0x2e, 0x64, 0x61, 0x74, 0x61, 0x00, 0x2e, 0x62, 0x73, + 0x73, 0x00, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x6f, 0xff, 0xff, 0xf6, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x70, + 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x88, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1e, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd8, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x1f, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe8, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x5d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19, + 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x21, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Refactor the RamDiscardManager interface into two distinct components: - RamDiscardSource: An interface that state providers (virtio-mem, RamBlockAttributes) implement to provide discard state information (granularity, populated/discarded ranges, replay callbacks). - RamDiscardManager: A concrete QOM object that wraps a source, owns the listener list, and handles listener registration/unregistration and notifications. This separation moves the listener management logic from individual source implementations into the central RamDiscardManager, reducing code duplication between virtio-mem and RamBlockAttributes. The change prepares for future work where a RamDiscardManager could aggregate multiple sources. Note, the original virtio-mem code had conditions before discard: if (vmem->size) { rdl->notify_discard(rdl, rdl->section); } however, the new code calls discard unconditionally. This is considered safe, since the populate/discard of sections are already asymmetrical (unplug & unregister all listener section unconditionally). Reviewed-by: Peter Xu <peterx@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-1-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/hw/virtio/virtio-mem.h | 3 - include/system/memory.h | 197 ++++++++++++++++------------- include/system/ramblock.h | 2 - hw/virtio/virtio-mem.c | 163 +++++------------------- system/memory.c | 218 +++++++++++++++++++++++++++++---- system/ram-block-attributes.c | 171 ++++++++------------------ 6 files changed, 385 insertions(+), 369 deletions(-) diff --git a/include/hw/virtio/virtio-mem.h b/include/hw/virtio/virtio-mem.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/virtio-mem.h +++ b/include/hw/virtio/virtio-mem.h @@ -XXX,XX +XXX,XX @@ struct VirtIOMEM { /* notifiers to notify when "size" changes */ NotifierList size_change_notifiers; - /* listeners to notify on plug/unplug activity. */ - QLIST_HEAD(, RamDiscardListener) rdl_list; - /* Catch system resets -> qemu_devices_reset() only. */ VirtioMemSystemReset *system_reset; }; diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ typedef struct RamDiscardManager RamDiscardManager; DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); +#define TYPE_RAM_DISCARD_SOURCE "ram-discard-source" +typedef struct RamDiscardSourceClass RamDiscardSourceClass; +typedef struct RamDiscardSource RamDiscardSource; +DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass, + RAM_DISCARD_SOURCE, TYPE_RAM_DISCARD_SOURCE); + #ifdef CONFIG_FUZZ void fuzz_dma_read_cb(size_t addr, size_t len, @@ -XXX,XX +XXX,XX @@ static inline void ram_discard_listener_init(RamDiscardListener *rdl, /** * typedef ReplayRamDiscardState: * - * The callback handler for #RamDiscardManagerClass.replay_populated/ - * #RamDiscardManagerClass.replay_discarded to invoke on populated/discarded + * The callback handler for #RamDiscardSourceClass.replay_populated/ + * #RamDiscardSourceClass.replay_discarded to invoke on populated/discarded * parts. * * @section: the #MemoryRegionSection of populated/discarded part @@ -XXX,XX +XXX,XX @@ typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, void *opaque); /* - * RamDiscardManagerClass: - * - * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion - * regions are currently populated to be used/accessed by the VM, notifying - * after parts were discarded (freeing up memory) and before parts will be - * populated (consuming memory), to be used/accessed by the VM. + * RamDiscardSourceClass: * - * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the - * #MemoryRegion isn't mapped into an address space yet (either directly - * or via an alias); it cannot change while the #MemoryRegion is - * mapped into an address space. + * A #RamDiscardSource provides information about which parts of a specific + * RAM #MemoryRegion are currently populated (accessible) vs discarded. * - * The #RamDiscardManager is intended to be used by technologies that are - * incompatible with discarding of RAM (e.g., VFIO, which may pin all - * memory inside a #MemoryRegion), and require proper coordination to only - * map the currently populated parts, to hinder parts that are expected to - * remain discarded from silently getting populated and consuming memory. - * Technologies that support discarding of RAM don't have to bother and can - * simply map the whole #MemoryRegion. - * - * An example #RamDiscardManager is virtio-mem, which logically (un)plugs - * memory within an assigned RAM #MemoryRegion, coordinated with the VM. - * Logically unplugging memory consists of discarding RAM. The VM agreed to not - * access unplugged (discarded) memory - especially via DMA. virtio-mem will - * properly coordinate with listeners before memory is plugged (populated), - * and after memory is unplugged (discarded). - * - * Listeners are called in multiples of the minimum granularity (unless it - * would exceed the registered range) and changes are aligned to the minimum - * granularity within the #MemoryRegion. Listeners have to prepare for memory - * becoming discarded in a different granularity than it was populated and the - * other way around. + * This is an interface that state providers (like virtio-mem or + * RamBlockAttributes) implement to provide discard state information. A + * #RamDiscardManager wraps sources and manages listener registrations and + * notifications. */ -struct RamDiscardManagerClass { +struct RamDiscardSourceClass { /* private */ InterfaceClass parent_class; @@ -XXX,XX +XXX,XX @@ struct RamDiscardManagerClass { * @get_min_granularity: * * Get the minimum granularity in which listeners will get notified - * about changes within the #MemoryRegion via the #RamDiscardManager. + * about changes within the #MemoryRegion via the #RamDiscardSource. * - * @rdm: the #RamDiscardManager + * @rds: the #RamDiscardSource * @mr: the #MemoryRegion * * Returns the minimum granularity. */ - uint64_t (*get_min_granularity)(const RamDiscardManager *rdm, + uint64_t (*get_min_granularity)(const RamDiscardSource *rds, const MemoryRegion *mr); /** * @is_populated: * * Check whether the given #MemoryRegionSection is completely populated - * (i.e., no parts are currently discarded) via the #RamDiscardManager. + * (i.e., no parts are currently discarded) via the #RamDiscardSource. * There are no alignment requirements. * - * @rdm: the #RamDiscardManager + * @rds: the #RamDiscardSource * @section: the #MemoryRegionSection * * Returns whether the given range is completely populated. */ - bool (*is_populated)(const RamDiscardManager *rdm, + bool (*is_populated)(const RamDiscardSource *rds, const MemoryRegionSection *section); /** * @replay_populated: * * Call the #ReplayRamDiscardState callback for all populated parts within - * the #MemoryRegionSection via the #RamDiscardManager. + * the #MemoryRegionSection via the #RamDiscardSource. * * In case any call fails, no further calls are made. * - * @rdm: the #RamDiscardManager + * @rds: the #RamDiscardSource * @section: the #MemoryRegionSection * @replay_fn: the #ReplayRamDiscardState callback * @opaque: pointer to forward to the callback * * Returns 0 on success, or a negative error if any notification failed. */ - int (*replay_populated)(const RamDiscardManager *rdm, + int (*replay_populated)(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); @@ -XXX,XX +XXX,XX @@ struct RamDiscardManagerClass { * @replay_discarded: * * Call the #ReplayRamDiscardState callback for all discarded parts within - * the #MemoryRegionSection via the #RamDiscardManager. + * the #MemoryRegionSection via the #RamDiscardSource. * - * @rdm: the #RamDiscardManager + * @rds: the #RamDiscardSource * @section: the #MemoryRegionSection * @replay_fn: the #ReplayRamDiscardState callback * @opaque: pointer to forward to the callback * * Returns 0 on success, or a negative error if any notification failed. */ - int (*replay_discarded)(const RamDiscardManager *rdm, + int (*replay_discarded)(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); +}; - /** - * @register_listener: - * - * Register a #RamDiscardListener for the given #MemoryRegionSection and - * immediately notify the #RamDiscardListener about all populated parts - * within the #MemoryRegionSection via the #RamDiscardManager. - * - * In case any notification fails, no further notifications are triggered - * and an error is logged. - * - * @rdm: the #RamDiscardManager - * @rdl: the #RamDiscardListener - * @section: the #MemoryRegionSection - */ - void (*register_listener)(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section); +/** + * RamDiscardManager: + * + * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion + * regions are currently populated to be used/accessed by the VM, notifying + * after parts were discarded (freeing up memory) and before parts will be + * populated (consuming memory), to be used/accessed by the VM. + * + * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the + * #MemoryRegion isn't mapped into an address space yet (either directly + * or via an alias); it cannot change while the #MemoryRegion is + * mapped into an address space. + * + * The #RamDiscardManager is intended to be used by technologies that are + * incompatible with discarding of RAM (e.g., VFIO, which may pin all + * memory inside a #MemoryRegion), and require proper coordination to only + * map the currently populated parts, to hinder parts that are expected to + * remain discarded from silently getting populated and consuming memory. + * Technologies that support discarding of RAM don't have to bother and can + * simply map the whole #MemoryRegion. + * + * An example #RamDiscardSource is virtio-mem, which logically (un)plugs + * memory within an assigned RAM #MemoryRegion, coordinated with the VM. + * Logically unplugging memory consists of discarding RAM. The VM agreed to not + * access unplugged (discarded) memory - especially via DMA. virtio-mem will + * properly coordinate with listeners before memory is plugged (populated), + * and after memory is unplugged (discarded). + * + * Listeners are called in multiples of the minimum granularity (unless it + * would exceed the registered range) and changes are aligned to the minimum + * granularity within the #MemoryRegion. Listeners have to prepare for memory + * becoming discarded in a different granularity than it was populated and the + * other way around. + */ +struct RamDiscardManager { + Object parent; - /** - * @unregister_listener: - * - * Unregister a previously registered #RamDiscardListener via the - * #RamDiscardManager after notifying the #RamDiscardListener about all - * populated parts becoming unpopulated within the registered - * #MemoryRegionSection. - * - * @rdm: the #RamDiscardManager - * @rdl: the #RamDiscardListener - */ - void (*unregister_listener)(RamDiscardManager *rdm, - RamDiscardListener *rdl); + RamDiscardSource *rds; + MemoryRegion *mr; + QLIST_HEAD(, RamDiscardListener) rdl_list; }; uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, /** * ram_discard_manager_replay_populated: * - * A wrapper to call the #RamDiscardManagerClass.replay_populated callback - * of the #RamDiscardManager. + * A wrapper to call the #RamDiscardSourceClass.replay_populated callback + * of the #RamDiscardSource sources. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, /** * ram_discard_manager_replay_discarded: * - * A wrapper to call the #RamDiscardManagerClass.replay_discarded callback - * of the #RamDiscardManager. + * A wrapper to call the #RamDiscardSourceClass.replay_discarded callback + * of the #RamDiscardSource sources. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm, void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl); +/* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + uint64_t offset, uint64_t size); + + /* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + uint64_t offset, uint64_t size); + +/* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm); + +/* + * Replay populated sections to all registered listeners. + * + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm); + /** * memory_translate_iotlb: Extract addresses from a TLB entry. * Called with rcu_read_lock held. @@ -XXX,XX +XXX,XX @@ static inline bool memory_region_has_ram_discard_manager(MemoryRegion *mr) } /** - * memory_region_set_ram_discard_manager: set the #RamDiscardManager for a + * memory_region_add_ram_discard_source: add a #RamDiscardSource for a * #MemoryRegion * - * This function must not be called for a mapped #MemoryRegion, a #MemoryRegion - * that does not cover RAM, or a #MemoryRegion that already has a - * #RamDiscardManager assigned. Return 0 if the rdm is set successfully. + * @mr: the #MemoryRegion + * @source: #RamDiscardSource to add + */ +int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); + +/** + * memory_region_del_ram_discard_source: remove a #RamDiscardSource for a + * #MemoryRegion * * @mr: the #MemoryRegion - * @rdm: #RamDiscardManager to set + * @source: #RamDiscardSource to remove */ -int memory_region_set_ram_discard_manager(MemoryRegion *mr, - RamDiscardManager *rdm); +void memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); /** * memory_region_find: translate an address/size relative to a diff --git a/include/system/ramblock.h b/include/system/ramblock.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -XXX,XX +XXX,XX @@ struct RamBlockAttributes { /* 1-setting of the bitmap represents ram is populated (shared) */ unsigned bitmap_size; unsigned long *bitmap; - - QLIST_HEAD(, RamDiscardListener) rdl_list; }; /* @offset: the offset within the RAMBlock */ diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -XXX,XX +XXX,XX @@ #include "qemu/error-report.h" #include "qemu/units.h" #include "qemu/target-info-qapi.h" +#include "system/memory.h" #include "system/numa.h" #include "system/system.h" #include "system/ramblock.h" @@ -XXX,XX +XXX,XX @@ static int virtio_mem_for_each_unplugged_section(const VirtIOMEM *vmem, return ret; } -static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg) -{ - RamDiscardListener *rdl = arg; - - return rdl->notify_populate(rdl, s); -} - static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); - } + ram_discard_manager_notify_discard(rdm, offset, size); } static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl, *rdl2; - int ret = 0; - - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } - - if (ret) { - /* Notify all already-notified listeners. */ - QLIST_FOREACH(rdl2, &vmem->rdl_list, next) { - MemoryRegionSection tmp = *rdl2->section; - - if (rdl2 == rdl) { - break; - } - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl2->notify_discard(rdl2, &tmp); - } - } - return ret; + return ram_discard_manager_notify_populate(rdm, offset, size); } static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); if (!vmem->size) { return; } - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - rdl->notify_discard(rdl, rdl->section); - } + ram_discard_manager_notify_discard_all(rdm); } static bool virtio_mem_is_range_plugged(const VirtIOMEM *vmem, @@ -XXX,XX +XXX,XX @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) return; } - /* - * Set ourselves as RamDiscardManager before the plug handler maps the - * memory region and exposes it via an address space. - */ - if (memory_region_set_ram_discard_manager(&vmem->memdev->mr, - RAM_DISCARD_MANAGER(vmem))) { - error_setg(errp, "Failed to set RamDiscardManager"); + if (memory_region_add_ram_discard_source(&vmem->memdev->mr, + RAM_DISCARD_SOURCE(vmem))) { + error_setg(errp, "Failed to add RAM discard source"); ram_block_coordinated_discard_require(false); return; } @@ -XXX,XX +XXX,XX @@ static void virtio_mem_device_realize(DeviceState *dev, Error **errp) ret = ram_block_discard_range(rb, 0, qemu_ram_get_used_length(rb)); if (ret) { error_setg_errno(errp, -ret, "Unexpected error discarding RAM"); - memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL); + memory_region_del_ram_discard_source(&vmem->memdev->mr, + RAM_DISCARD_SOURCE(vmem)); ram_block_coordinated_discard_require(false); return; } @@ -XXX,XX +XXX,XX @@ static void virtio_mem_device_unrealize(DeviceState *dev) * The unplug handler unmapped the memory region, it cannot be * found via an address space anymore. Unset ourselves. */ - memory_region_set_ram_discard_manager(&vmem->memdev->mr, NULL); + memory_region_del_ram_discard_source(&vmem->memdev->mr, RAM_DISCARD_SOURCE(vmem)); ram_block_coordinated_discard_require(false); } @@ -XXX,XX +XXX,XX @@ static int virtio_mem_activate_memslot_range_cb(VirtIOMEM *vmem, void *arg, static int virtio_mem_post_load_bitmap(VirtIOMEM *vmem) { - RamDiscardListener *rdl; - int ret; - + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); /* * We restored the bitmap and updated the requested size; activate all * memslots (so listeners register) before notifying about plugged blocks. @@ -XXX,XX +XXX,XX @@ static int virtio_mem_post_load_bitmap(VirtIOMEM *vmem) * We started out with all memory discarded and our memory region is mapped * into an address space. Replay, now that we updated the bitmap. */ - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_populate_cb); - if (ret) { - return ret; - } - } - return 0; + return ram_discard_manager_replay_populated_to_listeners(rdm); } static int virtio_mem_post_load(void *opaque, int version_id) @@ -XXX,XX +XXX,XX @@ static void virtio_mem_instance_init(Object *obj) VirtIOMEM *vmem = VIRTIO_MEM(obj); notifier_list_init(&vmem->size_change_notifiers); - QLIST_INIT(&vmem->rdl_list); object_property_add(obj, VIRTIO_MEM_SIZE_PROP, "size", virtio_mem_get_size, NULL, NULL, NULL); @@ -XXX,XX +XXX,XX @@ static const Property virtio_mem_legacy_guests_properties[] = { unplugged_inaccessible, ON_OFF_AUTO_ON), }; -static uint64_t virtio_mem_rdm_get_min_granularity(const RamDiscardManager *rdm, +static uint64_t virtio_mem_rds_get_min_granularity(const RamDiscardSource *rds, const MemoryRegion *mr) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); g_assert(mr == &vmem->memdev->mr); return vmem->block_size; } -static bool virtio_mem_rdm_is_populated(const RamDiscardManager *rdm, +static bool virtio_mem_rds_is_populated(const RamDiscardSource *rds, const MemoryRegionSection *s) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); uint64_t start_gpa = vmem->addr + s->offset_within_region; uint64_t end_gpa = start_gpa + int128_get64(s->size); @@ -XXX,XX +XXX,XX @@ struct VirtIOMEMReplayData { void *opaque; }; -static int virtio_mem_rdm_replay_populated_cb(MemoryRegionSection *s, void *arg) +static int virtio_mem_rds_replay_cb(MemoryRegionSection *s, void *arg) { struct VirtIOMEMReplayData *data = arg; return data->fn(s, data->opaque); } -static int virtio_mem_rdm_replay_populated(const RamDiscardManager *rdm, +static int virtio_mem_rds_replay_populated(const RamDiscardSource *rds, MemoryRegionSection *s, ReplayRamDiscardState replay_fn, void *opaque) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); struct VirtIOMEMReplayData data = { .fn = replay_fn, .opaque = opaque, @@ -XXX,XX +XXX,XX @@ static int virtio_mem_rdm_replay_populated(const RamDiscardManager *rdm, g_assert(s->mr == &vmem->memdev->mr); return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rdm_replay_populated_cb); -} - -static int virtio_mem_rdm_replay_discarded_cb(MemoryRegionSection *s, - void *arg) -{ - struct VirtIOMEMReplayData *data = arg; - - return data->fn(s, data->opaque); + virtio_mem_rds_replay_cb); } -static int virtio_mem_rdm_replay_discarded(const RamDiscardManager *rdm, +static int virtio_mem_rds_replay_discarded(const RamDiscardSource *rds, MemoryRegionSection *s, ReplayRamDiscardState replay_fn, void *opaque) { - const VirtIOMEM *vmem = VIRTIO_MEM(rdm); + const VirtIOMEM *vmem = VIRTIO_MEM(rds); struct VirtIOMEMReplayData data = { .fn = replay_fn, .opaque = opaque, @@ -XXX,XX +XXX,XX @@ static int virtio_mem_rdm_replay_discarded(const RamDiscardManager *rdm, g_assert(s->mr == &vmem->memdev->mr); return virtio_mem_for_each_unplugged_section(vmem, s, &data, - virtio_mem_rdm_replay_discarded_cb); -} - -static void virtio_mem_rdm_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *s) -{ - VirtIOMEM *vmem = VIRTIO_MEM(rdm); - int ret; - - g_assert(s->mr == &vmem->memdev->mr); - rdl->section = memory_region_section_new_copy(s); - - QLIST_INSERT_HEAD(&vmem->rdl_list, rdl, next); - ret = virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_populate_cb); - if (ret) { - error_report("%s: Replaying plugged ranges failed: %s", __func__, - strerror(-ret)); - } -} - -static void virtio_mem_rdm_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) -{ - VirtIOMEM *vmem = VIRTIO_MEM(rdm); - - g_assert(rdl->section->mr == &vmem->memdev->mr); - if (vmem->size) { - rdl->notify_discard(rdl, rdl->section); - } - - memory_region_section_free_copy(rdl->section); - rdl->section = NULL; - QLIST_REMOVE(rdl, next); + virtio_mem_rds_replay_cb); } static void virtio_mem_unplug_request_check(VirtIOMEM *vmem, Error **errp) @@ -XXX,XX +XXX,XX @@ static void virtio_mem_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); VirtIOMEMClass *vmc = VIRTIO_MEM_CLASS(klass); - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); device_class_set_props(dc, virtio_mem_properties); if (virtio_mem_has_legacy_guests()) { @@ -XXX,XX +XXX,XX @@ static void virtio_mem_class_init(ObjectClass *klass, const void *data) vmc->remove_size_change_notifier = virtio_mem_remove_size_change_notifier; vmc->unplug_request_check = virtio_mem_unplug_request_check; - rdmc->get_min_granularity = virtio_mem_rdm_get_min_granularity; - rdmc->is_populated = virtio_mem_rdm_is_populated; - rdmc->replay_populated = virtio_mem_rdm_replay_populated; - rdmc->replay_discarded = virtio_mem_rdm_replay_discarded; - rdmc->register_listener = virtio_mem_rdm_register_listener; - rdmc->unregister_listener = virtio_mem_rdm_unregister_listener; + rdsc->get_min_granularity = virtio_mem_rds_get_min_granularity; + rdsc->is_populated = virtio_mem_rds_is_populated; + rdsc->replay_populated = virtio_mem_rds_replay_populated; + rdsc->replay_discarded = virtio_mem_rds_replay_discarded; } static const TypeInfo virtio_mem_info = { @@ -XXX,XX +XXX,XX @@ static const TypeInfo virtio_mem_info = { .class_init = virtio_mem_class_init, .class_size = sizeof(VirtIOMEMClass), .interfaces = (const InterfaceInfo[]) { - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { } }, }; diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) return mr->rdm; } -int memory_region_set_ram_discard_manager(MemoryRegion *mr, - RamDiscardManager *rdm) +static RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, + RamDiscardSource *rds) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); + + rdm->rds = rds; + rdm->mr = mr; + QLIST_INIT(&rdm->rdl_list); + return rdm; +} + +int memory_region_add_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) { g_assert(memory_region_is_ram(mr)); - if (mr->rdm && rdm) { + if (mr->rdm) { return -EBUSY; } - mr->rdm = rdm; + mr->rdm = ram_discard_manager_new(mr, RAM_DISCARD_SOURCE(source)); return 0; } +void memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + g_assert(mr->rdm->rds == source); + + object_unref(mr->rdm); + mr->rdm = NULL; +} + +static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->get_min_granularity); + return rdsc->get_min_granularity(rds, mr); +} + +static bool ram_discard_source_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->is_populated); + return rdsc->is_populated(rds, section); +} + +static int ram_discard_source_replay_populated(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->replay_populated); + return rdsc->replay_populated(rds, section, replay_fn, opaque); +} + +static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->replay_discarded); + return rdsc->replay_discarded(rds, section, replay_fn, opaque); +} + uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, const MemoryRegion *mr) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->get_min_granularity); - return rdmc->get_min_granularity(rdm, mr); + return ram_discard_source_get_min_granularity(rdm->rds, mr); } bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, const MemoryRegionSection *section) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->is_populated); - return rdmc->is_populated(rdm, section); + return ram_discard_source_is_populated(rdm->rds, section); } int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); - - g_assert(rdmc->replay_populated); - return rdmc->replay_populated(rdm, section, replay_fn, opaque); + return ram_discard_source_replay_populated(rdm->rds, section, replay_fn, opaque); } int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); + return ram_discard_source_replay_discarded(rdm->rds, section, replay_fn, opaque); +} + +static void ram_discard_manager_initfn(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + + QLIST_INIT(&rdm->rdl_list); +} + +static void ram_discard_manager_finalize(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); - g_assert(rdmc->replay_discarded); - return rdmc->replay_discarded(rdm, section, replay_fn, opaque); + g_assert(QLIST_EMPTY(&rdm->rdl_list)); +} + +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + ret = rdl->notify_populate(rdl, &tmp); + if (ret) { + break; + } + } + + if (ret) { + /* Notify all already-notified listeners about discard. */ + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl2->section; + + if (rdl2 == rdl) { + break; + } + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + rdl2->notify_discard(rdl2, &tmp); + } + } + return ret; +} + +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + rdl->notify_discard(rdl, &tmp); + } +} + +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) +{ + RamDiscardListener *rdl; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + rdl->notify_discard(rdl, rdl->section); + } +} + +static int rdm_populate_cb(MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + + return rdl->notify_populate(rdl, section); } void ram_discard_manager_register_listener(RamDiscardManager *rdm, RamDiscardListener *rdl, MemoryRegionSection *section) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); + int ret; + + g_assert(section->mr == rdm->mr); + + rdl->section = memory_region_section_new_copy(section); + QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); - g_assert(rdmc->register_listener); - rdmc->register_listener(rdm, rdl, section); + ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, + rdm_populate_cb, rdl); + if (ret) { + error_report("%s: Replaying populated ranges failed: %s", __func__, + strerror(-ret)); + } } void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_GET_CLASS(rdm); + g_assert(rdl->section); + g_assert(rdl->section->mr == rdm->mr); + + rdl->notify_discard(rdl, rdl->section); + memory_region_section_free_copy(rdl->section); + rdl->section = NULL; + QLIST_REMOVE(rdl, next); +} + +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) +{ + RamDiscardListener *rdl; + int ret = 0; - g_assert(rdmc->unregister_listener); - rdmc->unregister_listener(rdm, rdl); + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, + rdm_populate_cb, rdl); + if (ret) { + break; + } + } + return ret; } /* Called with rcu_read_lock held. */ @@ -XXX,XX +XXX,XX @@ static const TypeInfo iommu_memory_region_info = { }; static const TypeInfo ram_discard_manager_info = { - .parent = TYPE_INTERFACE, + .parent = TYPE_OBJECT, .name = TYPE_RAM_DISCARD_MANAGER, - .class_size = sizeof(RamDiscardManagerClass), + .instance_size = sizeof(RamDiscardManager), + .instance_init = ram_discard_manager_initfn, + .instance_finalize = ram_discard_manager_finalize, +}; + +static const TypeInfo ram_discard_source_info = { + .parent = TYPE_INTERFACE, + .name = TYPE_RAM_DISCARD_SOURCE, + .class_size = sizeof(RamDiscardSourceClass), }; static void memory_register_types(void) @@ -XXX,XX +XXX,XX @@ static void memory_register_types(void) type_register_static(&memory_region_info); type_register_static(&iommu_memory_region_info); type_register_static(&ram_discard_manager_info); + type_register_static(&ram_discard_source_info); } type_init(memory_register_types) diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -XXX,XX +XXX,XX @@ OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RamBlockAttributes, ram_block_attributes, RAM_BLOCK_ATTRIBUTES, OBJECT, - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { }) static size_t @@ -XXX,XX +XXX,XX @@ ram_block_attributes_get_block_size(void) return qemu_real_host_page_size(); } - -static bool -ram_block_attributes_rdm_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section) -{ - const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - const size_t block_size = ram_block_attributes_get_block_size(); - const uint64_t first_bit = section->offset_within_region / block_size; - const uint64_t last_bit = - first_bit + int128_get64(section->size) / block_size - 1; - unsigned long first_discarded_bit; - - first_discarded_bit = find_next_zero_bit(attr->bitmap, last_bit + 1, - first_bit); - return first_discarded_bit > last_bit; -} - typedef int (*ram_block_attributes_section_cb)(MemoryRegionSection *s, void *arg); -static int -ram_block_attributes_notify_populate_cb(MemoryRegionSection *section, - void *arg) -{ - RamDiscardListener *rdl = arg; - - return rdl->notify_populate(rdl, section); -} - static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, MemoryRegionSection *section, @@ -XXX,XX +XXX,XX @@ ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *attr, return ret; } -static uint64_t -ram_block_attributes_rdm_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr) -{ - const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - g_assert(mr == attr->ram_block->mr); - return ram_block_attributes_get_block_size(); -} +typedef struct RamBlockAttributesReplayData { + ReplayRamDiscardState fn; + void *opaque; +} RamBlockAttributesReplayData; -static void -ram_block_attributes_rdm_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section) +static int ram_block_attributes_rds_replay_cb(MemoryRegionSection *section, + void *arg) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); - int ret; - - g_assert(section->mr == attr->ram_block->mr); - rdl->section = memory_region_section_new_copy(section); - - QLIST_INSERT_HEAD(&attr->rdl_list, rdl, next); + RamBlockAttributesReplayData *data = arg; - ret = ram_block_attributes_for_each_populated_section(attr, section, rdl, - ram_block_attributes_notify_populate_cb); - if (ret) { - error_report("%s: Failed to register RAM discard listener: %s", - __func__, strerror(-ret)); - exit(1); - } + return data->fn(section, data->opaque); } -static void -ram_block_attributes_rdm_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) +/* RamDiscardSource interface implementation */ +static uint64_t +ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); + const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); - g_assert(rdl->section); - g_assert(rdl->section->mr == attr->ram_block->mr); - - rdl->notify_discard(rdl, rdl->section); - - memory_region_section_free_copy(rdl->section); - rdl->section = NULL; - QLIST_REMOVE(rdl, next); + g_assert(mr == attr->ram_block->mr); + return ram_block_attributes_get_block_size(); } -typedef struct RamBlockAttributesReplayData { - ReplayRamDiscardState fn; - void *opaque; -} RamBlockAttributesReplayData; - -static int ram_block_attributes_rdm_replay_cb(MemoryRegionSection *section, - void *arg) +static bool +ram_block_attributes_rds_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) { - RamBlockAttributesReplayData *data = arg; + const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); + const size_t block_size = ram_block_attributes_get_block_size(); + const uint64_t first_bit = section->offset_within_region / block_size; + const uint64_t last_bit = + first_bit + int128_get64(section->size) / block_size - 1; + unsigned long first_discarded_bit; - return data->fn(section, data->opaque); + first_discarded_bit = find_next_zero_bit(attr->bitmap, last_bit + 1, + first_bit); + return first_discarded_bit > last_bit; } static int -ram_block_attributes_rdm_replay_populated(const RamDiscardManager *rdm, +ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); + RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; g_assert(section->mr == attr->ram_block->mr); return ram_block_attributes_for_each_populated_section(attr, section, &data, - ram_block_attributes_rdm_replay_cb); + ram_block_attributes_rds_replay_cb); } static int -ram_block_attributes_rdm_replay_discarded(const RamDiscardManager *rdm, +ram_block_attributes_rds_replay_discarded(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); + RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; g_assert(section->mr == attr->ram_block->mr); return ram_block_attributes_for_each_discarded_section(attr, section, &data, - ram_block_attributes_rdm_replay_cb); + ram_block_attributes_rds_replay_cb); } static bool @@ -XXX,XX +XXX,XX @@ ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset, return true; } -static void ram_block_attributes_notify_discard(RamBlockAttributes *attr, - uint64_t offset, - uint64_t size) +static void +ram_block_attributes_notify_discard(RamBlockAttributes *attr, + uint64_t offset, + uint64_t size) { - RamDiscardListener *rdl; + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); - } + ram_discard_manager_notify_discard(rdm, offset, size); } static int ram_block_attributes_notify_populate(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl; - int ret = 0; - - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } + RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - return ret; + return ram_discard_manager_notify_populate(rdm, offset, size); } int ram_block_attributes_state_change(RamBlockAttributes *attr, @@ -XXX,XX +XXX,XX @@ RamBlockAttributes *ram_block_attributes_create(RAMBlock *ram_block) attr = RAM_BLOCK_ATTRIBUTES(object_new(TYPE_RAM_BLOCK_ATTRIBUTES)); attr->ram_block = ram_block; - if (memory_region_set_ram_discard_manager(mr, RAM_DISCARD_MANAGER(attr))) { + + if (memory_region_add_ram_discard_source(mr, RAM_DISCARD_SOURCE(attr))) { object_unref(OBJECT(attr)); return NULL; } @@ -XXX,XX +XXX,XX @@ void ram_block_attributes_destroy(RamBlockAttributes *attr) g_assert(attr); g_free(attr->bitmap); - memory_region_set_ram_discard_manager(attr->ram_block->mr, NULL); + memory_region_del_ram_discard_source(attr->ram_block->mr, RAM_DISCARD_SOURCE(attr)); object_unref(OBJECT(attr)); } static void ram_block_attributes_init(Object *obj) { - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(obj); - - QLIST_INIT(&attr->rdl_list); } static void ram_block_attributes_finalize(Object *obj) @@ -XXX,XX +XXX,XX @@ static void ram_block_attributes_finalize(Object *obj) static void ram_block_attributes_class_init(ObjectClass *klass, const void *data) { - RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); - - rdmc->get_min_granularity = ram_block_attributes_rdm_get_min_granularity; - rdmc->register_listener = ram_block_attributes_rdm_register_listener; - rdmc->unregister_listener = ram_block_attributes_rdm_unregister_listener; - rdmc->is_populated = ram_block_attributes_rdm_is_populated; - rdmc->replay_populated = ram_block_attributes_rdm_replay_populated; - rdmc->replay_discarded = ram_block_attributes_rdm_replay_discarded; + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity = ram_block_attributes_rds_get_min_granularity; + rdsc->is_populated = ram_block_attributes_rds_is_populated; + rdsc->replay_populated = ram_block_attributes_rds_replay_populated; + rdsc->replay_discarded = ram_block_attributes_rds_replay_discarded; } -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Extract RamDiscardManager and RamDiscardSource from system/memory.c into dedicated a unit. This reduces coupling and allows code that only needs the RamDiscardManager interface to avoid pulling in all of memory.h dependencies. rust-sys bindings are no longer generated for RamDiscardSourceClass at this point, thus we drop the unneeded InterfaceClass use. Reviewed-by: Peter Xu <peterx@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-2-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- MAINTAINERS | 2 + include/system/memory.h | 280 +------------------------ include/system/ram-discard-manager.h | 297 +++++++++++++++++++++++++++ system/memory.c | 221 -------------------- system/ram-discard-manager.c | 240 ++++++++++++++++++++++ rust/bindings/system-sys/lib.rs | 2 +- system/meson.build | 1 + 7 files changed, 542 insertions(+), 501 deletions(-) create mode 100644 include/system/ram-discard-manager.h create mode 100644 system/ram-discard-manager.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: include/system/memory.h F: include/system/memory_cached.h F: include/system/memory_ldst* F: include/system/physmem.h +F: include/system/ram-discard-manager.h F: include/system/ramblock.h F: include/system/memory_mapping.h F: system/dma-helpers.c @@ -XXX,XX +XXX,XX @@ F: system/physmem.c F: system/memory_ldst* F: system/memory-internal.h F: system/ram-block-attributes.c +F: system/ram-discard-manager.c F: scripts/coccinelle/memory-region-housekeeping.cocci Memory devices diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ #include "exec/hwaddr.h" #include "system/ram_addr.h" +#include "system/ram-discard-manager.h" #include "exec/memattrs.h" #include "exec/memop.h" #include "qemu/bswap.h" @@ -XXX,XX +XXX,XX @@ typedef struct IOMMUMemoryRegionClass IOMMUMemoryRegionClass; DECLARE_OBJ_CHECKERS(IOMMUMemoryRegion, IOMMUMemoryRegionClass, IOMMU_MEMORY_REGION, TYPE_IOMMU_MEMORY_REGION) -#define TYPE_RAM_DISCARD_MANAGER "ram-discard-manager" -typedef struct RamDiscardManagerClass RamDiscardManagerClass; -typedef struct RamDiscardManager RamDiscardManager; -DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, - RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); - -#define TYPE_RAM_DISCARD_SOURCE "ram-discard-source" -typedef struct RamDiscardSourceClass RamDiscardSourceClass; -typedef struct RamDiscardSource RamDiscardSource; -DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass, - RAM_DISCARD_SOURCE, TYPE_RAM_DISCARD_SOURCE); - #ifdef CONFIG_FUZZ void fuzz_dma_read_cb(size_t addr, size_t len, @@ -XXX,XX +XXX,XX @@ struct IOMMUMemoryRegionClass { int (*num_indexes)(IOMMUMemoryRegion *iommu); }; -typedef struct RamDiscardListener RamDiscardListener; -typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, - MemoryRegionSection *section); -typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, - MemoryRegionSection *section); - -struct RamDiscardListener { - /* - * @notify_populate: - * - * Notification that previously discarded memory is about to get populated. - * Listeners are able to object. If any listener objects, already - * successfully notified listeners are notified about a discard again. - * - * @rdl: the #RamDiscardListener getting notified - * @section: the #MemoryRegionSection to get populated. The section - * is aligned within the memory region to the minimum granularity - * unless it would exceed the registered section. - * - * Returns 0 on success. If the notification is rejected by the listener, - * an error is returned. - */ - NotifyRamPopulate notify_populate; - - /* - * @notify_discard: - * - * Notification that previously populated memory was discarded successfully - * and listeners should drop all references to such memory and prevent - * new population (e.g., unmap). - * - * @rdl: the #RamDiscardListener getting notified - * @section: the #MemoryRegionSection to get discarded. The section - * is aligned within the memory region to the minimum granularity - * unless it would exceed the registered section. - */ - NotifyRamDiscard notify_discard; - - MemoryRegionSection *section; - QLIST_ENTRY(RamDiscardListener) next; -}; - -static inline void ram_discard_listener_init(RamDiscardListener *rdl, - NotifyRamPopulate populate_fn, - NotifyRamDiscard discard_fn) -{ - rdl->notify_populate = populate_fn; - rdl->notify_discard = discard_fn; -} - -/** - * typedef ReplayRamDiscardState: - * - * The callback handler for #RamDiscardSourceClass.replay_populated/ - * #RamDiscardSourceClass.replay_discarded to invoke on populated/discarded - * parts. - * - * @section: the #MemoryRegionSection of populated/discarded part - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if failed. - */ -typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, - void *opaque); - -/* - * RamDiscardSourceClass: - * - * A #RamDiscardSource provides information about which parts of a specific - * RAM #MemoryRegion are currently populated (accessible) vs discarded. - * - * This is an interface that state providers (like virtio-mem or - * RamBlockAttributes) implement to provide discard state information. A - * #RamDiscardManager wraps sources and manages listener registrations and - * notifications. - */ -struct RamDiscardSourceClass { - /* private */ - InterfaceClass parent_class; - - /* public */ - - /** - * @get_min_granularity: - * - * Get the minimum granularity in which listeners will get notified - * about changes within the #MemoryRegion via the #RamDiscardSource. - * - * @rds: the #RamDiscardSource - * @mr: the #MemoryRegion - * - * Returns the minimum granularity. - */ - uint64_t (*get_min_granularity)(const RamDiscardSource *rds, - const MemoryRegion *mr); - - /** - * @is_populated: - * - * Check whether the given #MemoryRegionSection is completely populated - * (i.e., no parts are currently discarded) via the #RamDiscardSource. - * There are no alignment requirements. - * - * @rds: the #RamDiscardSource - * @section: the #MemoryRegionSection - * - * Returns whether the given range is completely populated. - */ - bool (*is_populated)(const RamDiscardSource *rds, - const MemoryRegionSection *section); - - /** - * @replay_populated: - * - * Call the #ReplayRamDiscardState callback for all populated parts within - * the #MemoryRegionSection via the #RamDiscardSource. - * - * In case any call fails, no further calls are made. - * - * @rds: the #RamDiscardSource - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_populated)(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @replay_discarded: - * - * Call the #ReplayRamDiscardState callback for all discarded parts within - * the #MemoryRegionSection via the #RamDiscardSource. - * - * @rds: the #RamDiscardSource - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_discarded)(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); -}; - -/** - * RamDiscardManager: - * - * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion - * regions are currently populated to be used/accessed by the VM, notifying - * after parts were discarded (freeing up memory) and before parts will be - * populated (consuming memory), to be used/accessed by the VM. - * - * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the - * #MemoryRegion isn't mapped into an address space yet (either directly - * or via an alias); it cannot change while the #MemoryRegion is - * mapped into an address space. - * - * The #RamDiscardManager is intended to be used by technologies that are - * incompatible with discarding of RAM (e.g., VFIO, which may pin all - * memory inside a #MemoryRegion), and require proper coordination to only - * map the currently populated parts, to hinder parts that are expected to - * remain discarded from silently getting populated and consuming memory. - * Technologies that support discarding of RAM don't have to bother and can - * simply map the whole #MemoryRegion. - * - * An example #RamDiscardSource is virtio-mem, which logically (un)plugs - * memory within an assigned RAM #MemoryRegion, coordinated with the VM. - * Logically unplugging memory consists of discarding RAM. The VM agreed to not - * access unplugged (discarded) memory - especially via DMA. virtio-mem will - * properly coordinate with listeners before memory is plugged (populated), - * and after memory is unplugged (discarded). - * - * Listeners are called in multiples of the minimum granularity (unless it - * would exceed the registered range) and changes are aligned to the minimum - * granularity within the #MemoryRegion. Listeners have to prepare for memory - * becoming discarded in a different granularity than it was populated and the - * other way around. - */ -struct RamDiscardManager { - Object parent; - - RamDiscardSource *rds; - MemoryRegion *mr; - QLIST_HEAD(, RamDiscardListener) rdl_list; -}; - -uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr); - -bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section); - -/** - * ram_discard_manager_replay_populated: - * - * A wrapper to call the #RamDiscardSourceClass.replay_populated callback - * of the #RamDiscardSource sources. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ -int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque); - -/** - * ram_discard_manager_replay_discarded: - * - * A wrapper to call the #RamDiscardSourceClass.replay_discarded callback - * of the #RamDiscardSource sources. - * - * @rdm: the #RamDiscardManager - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ -int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque); - -void ram_discard_manager_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section); - -void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl); - -/* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. - */ -int ram_discard_manager_notify_populate(RamDiscardManager *rdm, - uint64_t offset, uint64_t size); - - /* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. - */ -void ram_discard_manager_notify_discard(RamDiscardManager *rdm, - uint64_t offset, uint64_t size); - -/* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. - */ -void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm); - -/* - * Replay populated sections to all registered listeners. - * - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. - */ -int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm); - /** * memory_translate_iotlb: Extract addresses from a TLB entry. * Called with rcu_read_lock held. diff --git a/include/system/ram-discard-manager.h b/include/system/ram-discard-manager.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/system/ram-discard-manager.h @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RAM Discard Manager + * + * Copyright Red Hat, Inc. 2026 + */ + +#ifndef RAM_DISCARD_MANAGER_H +#define RAM_DISCARD_MANAGER_H + +#include "qemu/typedefs.h" +#include "qom/object.h" +#include "qemu/queue.h" + +#define TYPE_RAM_DISCARD_MANAGER "ram-discard-manager" +typedef struct RamDiscardManagerClass RamDiscardManagerClass; +typedef struct RamDiscardManager RamDiscardManager; +DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, + RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); + +#define TYPE_RAM_DISCARD_SOURCE "ram-discard-source" +typedef struct RamDiscardSourceClass RamDiscardSourceClass; +typedef struct RamDiscardSource RamDiscardSource; +DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass, + RAM_DISCARD_SOURCE, TYPE_RAM_DISCARD_SOURCE); + +typedef struct RamDiscardListener RamDiscardListener; +typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, + MemoryRegionSection *section); +typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, + MemoryRegionSection *section); + +struct RamDiscardListener { + /* + * @notify_populate: + * + * Notification that previously discarded memory is about to get populated. + * Listeners are able to object. If any listener objects, already + * successfully notified listeners are notified about a discard again. + * + * @rdl: the #RamDiscardListener getting notified + * @section: the #MemoryRegionSection to get populated. The section + * is aligned within the memory region to the minimum granularity + * unless it would exceed the registered section. + * + * Returns 0 on success. If the notification is rejected by the listener, + * an error is returned. + */ + NotifyRamPopulate notify_populate; + + /* + * @notify_discard: + * + * Notification that previously populated memory was discarded successfully + * and listeners should drop all references to such memory and prevent + * new population (e.g., unmap). + * + * @rdl: the #RamDiscardListener getting notified + * @section: the #MemoryRegionSection to get discarded. The section + * is aligned within the memory region to the minimum granularity + * unless it would exceed the registered section. + */ + NotifyRamDiscard notify_discard; + + MemoryRegionSection *section; + QLIST_ENTRY(RamDiscardListener) next; +}; + +static inline void ram_discard_listener_init(RamDiscardListener *rdl, + NotifyRamPopulate populate_fn, + NotifyRamDiscard discard_fn) +{ + rdl->notify_populate = populate_fn; + rdl->notify_discard = discard_fn; +} + +/** + * typedef ReplayRamDiscardState: + * + * The callback handler for #RamDiscardSourceClass.replay_populated/ + * #RamDiscardSourceClass.replay_discarded to invoke on populated/discarded + * parts. + * + * @section: the #MemoryRegionSection of populated/discarded part + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if failed. + */ +typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, + void *opaque); + +/* + * RamDiscardSourceClass: + * + * A #RamDiscardSource provides information about which parts of a specific + * RAM #MemoryRegion are currently populated (accessible) vs discarded. + * + * This is an interface that state providers (like virtio-mem or + * RamBlockAttributes) implement to provide discard state information. A + * #RamDiscardManager wraps sources and manages listener registrations and + * notifications. + */ +struct RamDiscardSourceClass { + /* private */ + InterfaceClass parent_class; + + /* public */ + + /** + * @get_min_granularity: + * + * Get the minimum granularity in which listeners will get notified + * about changes within the #MemoryRegion via the #RamDiscardSource. + * + * @rds: the #RamDiscardSource + * @mr: the #MemoryRegion + * + * Returns the minimum granularity. + */ + uint64_t (*get_min_granularity)(const RamDiscardSource *rds, + const MemoryRegion *mr); + + /** + * @is_populated: + * + * Check whether the given #MemoryRegionSection is completely populated + * (i.e., no parts are currently discarded) via the #RamDiscardSource. + * There are no alignment requirements. + * + * @rds: the #RamDiscardSource + * @section: the #MemoryRegionSection + * + * Returns whether the given range is completely populated. + */ + bool (*is_populated)(const RamDiscardSource *rds, + const MemoryRegionSection *section); + + /** + * @replay_populated: + * + * Call the #ReplayRamDiscardState callback for all populated parts within + * the #MemoryRegionSection via the #RamDiscardSource. + * + * In case any call fails, no further calls are made. + * + * @rds: the #RamDiscardSource + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ + int (*replay_populated)(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, void *opaque); + + /** + * @replay_discarded: + * + * Call the #ReplayRamDiscardState callback for all discarded parts within + * the #MemoryRegionSection via the #RamDiscardSource. + * + * @rds: the #RamDiscardSource + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ + int (*replay_discarded)(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, void *opaque); +}; + +/** + * RamDiscardManager: + * + * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion + * regions are currently populated to be used/accessed by the VM, notifying + * after parts were discarded (freeing up memory) and before parts will be + * populated (consuming memory), to be used/accessed by the VM. + * + * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the + * #MemoryRegion isn't mapped into an address space yet (either directly + * or via an alias); it cannot change while the #MemoryRegion is + * mapped into an address space. + * + * The #RamDiscardManager is intended to be used by technologies that are + * incompatible with discarding of RAM (e.g., VFIO, which may pin all + * memory inside a #MemoryRegion), and require proper coordination to only + * map the currently populated parts, to hinder parts that are expected to + * remain discarded from silently getting populated and consuming memory. + * Technologies that support discarding of RAM don't have to bother and can + * simply map the whole #MemoryRegion. + * + * An example #RamDiscardSource is virtio-mem, which logically (un)plugs + * memory within an assigned RAM #MemoryRegion, coordinated with the VM. + * Logically unplugging memory consists of discarding RAM. The VM agreed to not + * access unplugged (discarded) memory - especially via DMA. virtio-mem will + * properly coordinate with listeners before memory is plugged (populated), + * and after memory is unplugged (discarded). + * + * Listeners are called in multiples of the minimum granularity (unless it + * would exceed the registered range) and changes are aligned to the minimum + * granularity within the #MemoryRegion. Listeners have to prepare for memory + * becoming discarded in a different granularity than it was populated and the + * other way around. + */ +struct RamDiscardManager { + Object parent; + + RamDiscardSource *rds; + MemoryRegion *mr; + QLIST_HEAD(, RamDiscardListener) rdl_list; +}; + +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, + RamDiscardSource *rds); + +uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, + const MemoryRegion *mr); + +bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section); + +/** + * ram_discard_manager_replay_populated: + * + * A wrapper to call the #RamDiscardSourceClass.replay_populated callback + * of the #RamDiscardSource sources. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ +int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque); + +/** + * ram_discard_manager_replay_discarded: + * + * A wrapper to call the #RamDiscardSourceClass.replay_discarded callback + * of the #RamDiscardSource sources. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection + * @replay_fn: the #ReplayRamDiscardState callback + * @opaque: pointer to forward to the callback + * + * Returns 0 on success, or a negative error if any notification failed. + */ +int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque); + +void ram_discard_manager_register_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl, + MemoryRegionSection *section); + +void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl); + +/* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + uint64_t offset, uint64_t size); + +/* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + uint64_t offset, uint64_t size); + +/* + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm); + +/* + * Replay populated sections to all registered listeners. + * + * Note: later refactoring should take the source into account and the manager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm); + +#endif /* RAM_DISCARD_MANAGER_H */ diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) return mr->rdm; } -static RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, - RamDiscardSource *rds) -{ - RamDiscardManager *rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); - - rdm->rds = rds; - rdm->mr = mr; - QLIST_INIT(&rdm->rdl_list); - return rdm; -} - int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { @@ -XXX,XX +XXX,XX @@ void memory_region_del_ram_discard_source(MemoryRegion *mr, mr->rdm = NULL; } -static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds, - const MemoryRegion *mr) -{ - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); - - g_assert(rdsc->get_min_granularity); - return rdsc->get_min_granularity(rds, mr); -} - -static bool ram_discard_source_is_populated(const RamDiscardSource *rds, - const MemoryRegionSection *section) -{ - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); - - g_assert(rdsc->is_populated); - return rdsc->is_populated(rds, section); -} - -static int ram_discard_source_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); - - g_assert(rdsc->replay_populated); - return rdsc->replay_populated(rds, section, replay_fn, opaque); -} - -static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); - - g_assert(rdsc->replay_discarded); - return rdsc->replay_discarded(rds, section, replay_fn, opaque); -} - -uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr) -{ - return ram_discard_source_get_min_granularity(rdm->rds, mr); -} - -bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section) -{ - return ram_discard_source_is_populated(rdm->rds, section); -} - -int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - return ram_discard_source_replay_populated(rdm->rds, section, replay_fn, opaque); -} - -int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - return ram_discard_source_replay_discarded(rdm->rds, section, replay_fn, opaque); -} - -static void ram_discard_manager_initfn(Object *obj) -{ - RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); - - QLIST_INIT(&rdm->rdl_list); -} - -static void ram_discard_manager_finalize(Object *obj) -{ - RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); - - g_assert(QLIST_EMPTY(&rdm->rdl_list)); -} - -int ram_discard_manager_notify_populate(RamDiscardManager *rdm, - uint64_t offset, uint64_t size) -{ - RamDiscardListener *rdl, *rdl2; - int ret = 0; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } - - if (ret) { - /* Notify all already-notified listeners about discard. */ - QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl2->section; - - if (rdl2 == rdl) { - break; - } - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl2->notify_discard(rdl2, &tmp); - } - } - return ret; -} - -void ram_discard_manager_notify_discard(RamDiscardManager *rdm, - uint64_t offset, uint64_t size) -{ - RamDiscardListener *rdl; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); - } -} - -void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) -{ - RamDiscardListener *rdl; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - rdl->notify_discard(rdl, rdl->section); - } -} - -static int rdm_populate_cb(MemoryRegionSection *section, void *opaque) -{ - RamDiscardListener *rdl = opaque; - - return rdl->notify_populate(rdl, section); -} - -void ram_discard_manager_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *section) -{ - int ret; - - g_assert(section->mr == rdm->mr); - - rdl->section = memory_region_section_new_copy(section); - QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); - - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); - if (ret) { - error_report("%s: Replaying populated ranges failed: %s", __func__, - strerror(-ret)); - } -} - -void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl) -{ - g_assert(rdl->section); - g_assert(rdl->section->mr == rdm->mr); - - rdl->notify_discard(rdl, rdl->section); - memory_region_section_free_copy(rdl->section); - rdl->section = NULL; - QLIST_REMOVE(rdl, next); -} - -int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) -{ - RamDiscardListener *rdl; - int ret = 0; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); - if (ret) { - break; - } - } - return ret; -} - /* Called with rcu_read_lock held. */ MemoryRegion *memory_translate_iotlb(IOMMUTLBEntry *iotlb, hwaddr *xlat_p, Error **errp) @@ -XXX,XX +XXX,XX @@ static const TypeInfo iommu_memory_region_info = { .abstract = true, }; -static const TypeInfo ram_discard_manager_info = { - .parent = TYPE_OBJECT, - .name = TYPE_RAM_DISCARD_MANAGER, - .instance_size = sizeof(RamDiscardManager), - .instance_init = ram_discard_manager_initfn, - .instance_finalize = ram_discard_manager_finalize, -}; - -static const TypeInfo ram_discard_source_info = { - .parent = TYPE_INTERFACE, - .name = TYPE_RAM_DISCARD_SOURCE, - .class_size = sizeof(RamDiscardSourceClass), -}; - static void memory_register_types(void) { type_register_static(&memory_region_info); type_register_static(&iommu_memory_region_info); - type_register_static(&ram_discard_manager_info); - type_register_static(&ram_discard_source_info); } type_init(memory_register_types) diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/system/ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RAM Discard Manager + * + * Copyright Red Hat, Inc. 2026 + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "system/memory.h" + +static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->get_min_granularity); + return rdsc->get_min_granularity(rds, mr); +} + +static bool ram_discard_source_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->is_populated); + return rdsc->is_populated(rds, section); +} + +static int ram_discard_source_replay_populated(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->replay_populated); + return rdsc->replay_populated(rds, section, replay_fn, opaque); +} + +static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + + g_assert(rdsc->replay_discarded); + return rdsc->replay_discarded(rds, section, replay_fn, opaque); +} + +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, + RamDiscardSource *rds) +{ + RamDiscardManager *rdm; + + rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); + rdm->rds = rds; + rdm->mr = mr; + QLIST_INIT(&rdm->rdl_list); + return rdm; +} + +uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, + const MemoryRegion *mr) +{ + return ram_discard_source_get_min_granularity(rdm->rds, mr); +} + +bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, + const MemoryRegionSection *section) +{ + return ram_discard_source_is_populated(rdm->rds, section); +} + +int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + return ram_discard_source_replay_populated(rdm->rds, section, + replay_fn, opaque); +} + +int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, + void *opaque) +{ + return ram_discard_source_replay_discarded(rdm->rds, section, + replay_fn, opaque); +} + +static void ram_discard_manager_initfn(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + + QLIST_INIT(&rdm->rdl_list); +} + +static void ram_discard_manager_finalize(Object *obj) +{ + RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + + g_assert(QLIST_EMPTY(&rdm->rdl_list)); +} + +int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + ret = rdl->notify_populate(rdl, &tmp); + if (ret) { + break; + } + } + + if (ret) { + /* Notify all already-notified listeners about discard. */ + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl2->section; + + if (rdl2 == rdl) { + break; + } + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + rdl2->notify_discard(rdl2, &tmp); + } + } + return ret; +} + +void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + uint64_t offset, uint64_t size) +{ + RamDiscardListener *rdl; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp = *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + rdl->notify_discard(rdl, &tmp); + } +} + +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) +{ + RamDiscardListener *rdl; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + rdl->notify_discard(rdl, rdl->section); + } +} + +static int rdm_populate_cb(MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + + return rdl->notify_populate(rdl, section); +} + +void ram_discard_manager_register_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl, + MemoryRegionSection *section) +{ + int ret; + + g_assert(section->mr == rdm->mr); + + rdl->section = memory_region_section_new_copy(section); + QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); + + ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, + rdm_populate_cb, rdl); + if (ret) { + error_report("%s: Replaying populated ranges failed: %s", __func__, + strerror(-ret)); + } +} + +void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, + RamDiscardListener *rdl) +{ + g_assert(rdl->section); + g_assert(rdl->section->mr == rdm->mr); + + rdl->notify_discard(rdl, rdl->section); + memory_region_section_free_copy(rdl->section); + rdl->section = NULL; + QLIST_REMOVE(rdl, next); +} + +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) +{ + RamDiscardListener *rdl; + int ret = 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, + rdm_populate_cb, rdl); + if (ret) { + break; + } + } + return ret; +} + +static const TypeInfo ram_discard_manager_info = { + .parent = TYPE_OBJECT, + .name = TYPE_RAM_DISCARD_MANAGER, + .instance_size = sizeof(RamDiscardManager), + .instance_init = ram_discard_manager_initfn, + .instance_finalize = ram_discard_manager_finalize, +}; + +static const TypeInfo ram_discard_source_info = { + .parent = TYPE_INTERFACE, + .name = TYPE_RAM_DISCARD_SOURCE, + .class_size = sizeof(RamDiscardSourceClass), +}; + +static void ram_discard_manager_register_types(void) +{ + type_register_static(&ram_discard_manager_info); + type_register_static(&ram_discard_source_info); +} + +type_init(ram_discard_manager_register_types) diff --git a/rust/bindings/system-sys/lib.rs b/rust/bindings/system-sys/lib.rs index XXXXXXX..XXXXXXX 100644 --- a/rust/bindings/system-sys/lib.rs +++ b/rust/bindings/system-sys/lib.rs @@ -XXX,XX +XXX,XX @@ use common::Zeroable; use hwcore_sys::{qemu_irq, DeviceClass, DeviceState}; -use qom_sys::{InterfaceClass, Object, ObjectClass}; +use qom_sys::{Object, ObjectClass}; use util_sys::{Error, EventNotifier, QEMUBH}; #[cfg(MESON)] diff --git a/system/meson.build b/system/meson.build index XXXXXXX..XXXXXXX 100644 --- a/system/meson.build +++ b/system/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(files( 'globals.c', 'ioport.c', 'ram-block-attributes.c', + 'ram-discard-manager.c', 'memory_mapping.c', 'memory.c', 'physmem.c', -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> The sections shouldn't be modified. Reviewed-by: Peter Xu <peterx@redhat.com> Reviewed-by: Cédric Le Goater <clg@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Acked-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com> # for CPR Acked-by: David Hildenbrand (Arm) <david@kernel.org> Link: https://lore.kernel.org/r/20260604-rdm5-v5-3-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/hw/vfio/vfio-container.h | 2 +- include/hw/vfio/vfio-cpr.h | 2 +- include/system/ram-discard-manager.h | 14 +++++++------- hw/vfio/cpr-legacy.c | 4 ++-- hw/vfio/listener.c | 10 +++++----- hw/virtio/virtio-mem.c | 10 +++++----- migration/ram.c | 6 +++--- system/memory_mapping.c | 4 ++-- system/ram-block-attributes.c | 8 ++++---- system/ram-discard-manager.c | 10 +++++----- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/hw/vfio/vfio-container.h b/include/hw/vfio/vfio-container.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/vfio/vfio-container.h +++ b/include/hw/vfio/vfio-container.h @@ -XXX,XX +XXX,XX @@ struct VFIOIOMMUClass { }; VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section); + VFIOContainer *bcontainer, const MemoryRegionSection *section); void vfio_container_region_add(VFIOContainer *bcontainer, MemoryRegionSection *section, bool cpr_remap); diff --git a/include/hw/vfio/vfio-cpr.h b/include/hw/vfio/vfio-cpr.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/vfio/vfio-cpr.h +++ b/include/hw/vfio/vfio-cpr.h @@ -XXX,XX +XXX,XX @@ void vfio_cpr_giommu_remap(struct VFIOContainer *bcontainer, MemoryRegionSection *section); bool vfio_cpr_ram_discard_replay_populated( - struct VFIOContainer *bcontainer, MemoryRegionSection *section); + struct VFIOContainer *bcontainer, const MemoryRegionSection *section); void vfio_cpr_save_vector_fd(struct VFIOPCIDevice *vdev, const char *name, int nr, int fd); diff --git a/include/system/ram-discard-manager.h b/include/system/ram-discard-manager.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -XXX,XX +XXX,XX @@ DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass, typedef struct RamDiscardListener RamDiscardListener; typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, - MemoryRegionSection *section); + const MemoryRegionSection *section); typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, - MemoryRegionSection *section); + const MemoryRegionSection *section); struct RamDiscardListener { /* @@ -XXX,XX +XXX,XX @@ static inline void ram_discard_listener_init(RamDiscardListener *rdl, * * Returns 0 on success, or a negative error if failed. */ -typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, +typedef int (*ReplayRamDiscardState)(const MemoryRegionSection *section, void *opaque); /* @@ -XXX,XX +XXX,XX @@ struct RamDiscardSourceClass { * Returns 0 on success, or a negative error if any notification failed. */ int (*replay_populated)(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); /** @@ -XXX,XX +XXX,XX @@ struct RamDiscardSourceClass { * Returns 0 on success, or a negative error if any notification failed. */ int (*replay_discarded)(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); }; @@ -XXX,XX +XXX,XX @@ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, * Returns 0 on success, or a negative error if any notification failed. */ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, * Returns 0 on success, or a negative error if any notification failed. */ int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); diff --git a/hw/vfio/cpr-legacy.c b/hw/vfio/cpr-legacy.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/cpr-legacy.c +++ b/hw/vfio/cpr-legacy.c @@ -XXX,XX +XXX,XX @@ void vfio_cpr_giommu_remap(VFIOContainer *bcontainer, memory_region_iommu_replay(giommu->iommu_mr, &giommu->n); } -static int vfio_cpr_rdm_remap(MemoryRegionSection *section, void *opaque) +static int vfio_cpr_rdm_remap(const MemoryRegionSection *section, void *opaque) { RamDiscardListener *rdl = opaque; @@ -XXX,XX +XXX,XX @@ static int vfio_cpr_rdm_remap(MemoryRegionSection *section, void *opaque) * directly, which calls vfio_legacy_cpr_dma_map. */ bool vfio_cpr_ram_discard_replay_populated(VFIOContainer *bcontainer, - MemoryRegionSection *section) + const MemoryRegionSection *section) { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr); VFIORamDiscardListener *vrdl = diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vfio/listener.c +++ b/hw/vfio/listener.c @@ -XXX,XX +XXX,XX @@ out: } static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener, listener); @@ -XXX,XX +XXX,XX @@ static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl, } static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl = container_of(rdl, VFIORamDiscardListener, listener); @@ -XXX,XX +XXX,XX @@ static void vfio_device_error_append(VFIODevice *vbasedev, Error **errp) } VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section) + VFIOContainer *bcontainer, const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl; @@ -XXX,XX +XXX,XX @@ out: } } -static int vfio_ram_discard_query_dirty_bitmap(MemoryRegionSection *section, - void *opaque) +static int vfio_ram_discard_query_dirty_bitmap(const MemoryRegionSection *section, + void *opaque) { const hwaddr size = int128_get64(section->size); const hwaddr iova = section->offset_within_address_space; diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -XXX,XX +XXX,XX @@ static int virtio_mem_for_each_plugged_range(VirtIOMEM *vmem, void *arg, typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg); static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, + const MemoryRegionSection *s, void *arg, virtio_mem_section_cb cb) { @@ -XXX,XX +XXX,XX @@ static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem, } static int virtio_mem_for_each_unplugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, + const MemoryRegionSection *s, void *arg, virtio_mem_section_cb cb) { @@ -XXX,XX +XXX,XX @@ static int virtio_mem_rds_replay_cb(MemoryRegionSection *s, void *arg) } static int virtio_mem_rds_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *s, + const MemoryRegionSection *s, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ static int virtio_mem_rds_replay_populated(const RamDiscardSource *rds, g_assert(s->mr == &vmem->memdev->mr); return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rds_replay_cb); + virtio_mem_rds_replay_cb); } static int virtio_mem_rds_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *s, + const MemoryRegionSection *s, ReplayRamDiscardState replay_fn, void *opaque) { diff --git a/migration/ram.c b/migration/ram.c index XXXXXXX..XXXXXXX 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -XXX,XX +XXX,XX @@ static inline bool migration_bitmap_clear_dirty(RAMState *rs, return ret; } -static int dirty_bitmap_clear_section(MemoryRegionSection *section, +static int dirty_bitmap_clear_section(const MemoryRegionSection *section, void *opaque) { const hwaddr offset = section->offset_within_region; @@ -XXX,XX +XXX,XX @@ static inline void populate_read_range(RAMBlock *block, ram_addr_t offset, } } -static inline int populate_read_section(MemoryRegionSection *section, +static inline int populate_read_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size = int128_get64(section->size); @@ -XXX,XX +XXX,XX @@ void ram_write_tracking_prepare(void) } } -static inline int uffd_protect_section(MemoryRegionSection *section, +static inline int uffd_protect_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size = int128_get64(section->size); diff --git a/system/memory_mapping.c b/system/memory_mapping.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory_mapping.c +++ b/system/memory_mapping.c @@ -XXX,XX +XXX,XX @@ typedef struct GuestPhysListener { } GuestPhysListener; static void guest_phys_block_add_section(GuestPhysListener *g, - MemoryRegionSection *section) + const MemoryRegionSection *section) { const hwaddr target_start = section->offset_within_address_space; const hwaddr target_end = target_start + int128_get64(section->size); @@ -XXX,XX +XXX,XX @@ static void guest_phys_block_add_section(GuestPhysListener *g, #endif } -static int guest_phys_ram_populate_cb(MemoryRegionSection *section, +static int guest_phys_ram_populate_cb(const MemoryRegionSection *section, void *opaque) { GuestPhysListener *g = opaque; diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -XXX,XX +XXX,XX @@ typedef int (*ram_block_attributes_section_cb)(MemoryRegionSection *s, static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, - MemoryRegionSection *section, + const MemoryRegionSection *section, void *arg, ram_block_attributes_section_cb cb) { @@ -XXX,XX +XXX,XX @@ ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, static int ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *attr, - MemoryRegionSection *section, + const MemoryRegionSection *section, void *arg, ram_block_attributes_section_cb cb) { @@ -XXX,XX +XXX,XX @@ ram_block_attributes_rds_is_populated(const RamDiscardSource *rds, static int ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, static int ram_block_attributes_rds_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ static bool ram_discard_source_is_populated(const RamDiscardSource *rds, } static int ram_discard_source_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ static int ram_discard_source_replay_populated(const RamDiscardSource *rds, } static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, } int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, } int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) } } -static int rdm_populate_cb(MemoryRegionSection *section, void *opaque) +static int rdm_populate_cb(const MemoryRegionSection *section, void *opaque) { RamDiscardListener *rdl = opaque; -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Replace the source-level replay wrappers with a new replay_by_populated_state() helper that iterates the section at min-granularity, calls is_populated() for each chunk, and aggregates consecutive chunks of the same state before invoking the callback. This moves the iteration logic from individual sources into the manager, preparing for multi-source aggregation where the manager must combine state from multiple sources anyway. The replay_populated/replay_discarded vtable entries in RamDiscardSourceClass are no longer called but remain in the interface for now; they will be removed in follow-up commits along with the now-dead source implementations. Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Link: https://lore.kernel.org/r/20260604-rdm5-v5-4-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- system/ram-discard-manager.c | 85 ++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ static bool ram_discard_source_is_populated(const RamDiscardSource *rds, return rdsc->is_populated(rds, section); } -static int ram_discard_source_replay_populated(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) +/* + * Iterate the section at source granularity, aggregating consecutive chunks + * with matching populated state, and call replay_fn for each run. + */ +static int replay_by_populated_state(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + bool replay_populated, + ReplayRamDiscardState replay_fn, + void *opaque) { - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + uint64_t granularity, offset, size, end, pos, run_start = 0; + bool in_run = false; + int ret = 0; - g_assert(rdsc->replay_populated); - return rdsc->replay_populated(rds, section, replay_fn, opaque); -} + granularity = ram_discard_source_get_min_granularity(rdm->rds, rdm->mr); + offset = section->offset_within_region; + size = int128_get64(section->size); + end = offset + size; + + /* Align iteration to granularity boundaries */ + pos = QEMU_ALIGN_DOWN(offset, granularity); + + for (; pos < end; pos += granularity) { + MemoryRegionSection chunk = { + .mr = section->mr, + .offset_within_region = pos, + .size = int128_make64(granularity), + }; + bool populated = ram_discard_source_is_populated(rdm->rds, &chunk); + + if (populated == replay_populated) { + if (!in_run) { + run_start = pos; + in_run = true; + } + } else if (in_run) { + MemoryRegionSection tmp = *section; + + if (memory_region_section_intersect_range(&tmp, run_start, + pos - run_start)) { + ret = replay_fn(&tmp, opaque); + if (ret) { + return ret; + } + } + in_run = false; + } + } -static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds); + if (in_run) { + MemoryRegionSection tmp = *section; - g_assert(rdsc->replay_discarded); - return rdsc->replay_discarded(rds, section, replay_fn, opaque); + if (memory_region_section_intersect_range(&tmp, run_start, + pos - run_start)) { + ret = replay_fn(&tmp, opaque); + } + } + + return ret; } RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - return ram_discard_source_replay_populated(rdm->rds, section, - replay_fn, opaque); + return replay_by_populated_state(rdm, section, true, replay_fn, opaque); } int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - return ram_discard_source_replay_discarded(rdm->rds, section, - replay_fn, opaque); + return replay_by_populated_state(rdm, section, false, replay_fn, opaque); } static void ram_discard_manager_initfn(Object *obj) @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm, rdl->section = memory_region_section_new_copy(section); QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); + ret = ram_discard_manager_replay_populated(rdm, rdl->section, + rdm_populate_cb, rdl); if (ret) { error_report("%s: Replaying populated ranges failed: %s", __func__, strerror(-ret)); @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) int ret = 0; QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); + ret = ram_discard_manager_replay_populated(rdm, rdl->section, + rdm_populate_cb, rdl); if (ret) { break; } -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> The replay iteration logic has been moved into the RamDiscardManager, which now iterates at source granularity using is_populated(). The source-level replay_populated/replay_discarded methods and their helpers are no longer called. Remove the now-dead replay methods, the VirtIOMEMReplayData struct, the virtio_mem_for_each_plugged/unplugged_section() helpers (only used by the replay methods), and the virtio_mem_section_cb typedef. Reviewed-by: Peter Xu <peterx@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-5-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- hw/virtio/virtio-mem.c | 112 ----------------------------------------- 1 file changed, 112 deletions(-) diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -XXX,XX +XXX,XX @@ static int virtio_mem_for_each_plugged_range(VirtIOMEM *vmem, void *arg, return ret; } -typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg); - -static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem, - const MemoryRegionSection *s, - void *arg, - virtio_mem_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - int ret = 0; - - first_bit = s->offset_within_region / vmem->block_size; - first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, first_bit); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp = *s; - - offset = first_bit * vmem->block_size; - last_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret = cb(&tmp, arg); - if (ret) { - break; - } - first_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, - last_bit + 2); - } - return ret; -} - -static int virtio_mem_for_each_unplugged_section(const VirtIOMEM *vmem, - const MemoryRegionSection *s, - void *arg, - virtio_mem_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - int ret = 0; - - first_bit = s->offset_within_region / vmem->block_size; - first_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, first_bit); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp = *s; - - offset = first_bit * vmem->block_size; - last_bit = find_next_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret = cb(&tmp, arg); - if (ret) { - break; - } - first_bit = find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, - last_bit + 2); - } - return ret; -} - static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { @@ -XXX,XX +XXX,XX @@ static bool virtio_mem_rds_is_populated(const RamDiscardSource *rds, return virtio_mem_is_range_plugged(vmem, start_gpa, end_gpa - start_gpa); } -struct VirtIOMEMReplayData { - ReplayRamDiscardState fn; - void *opaque; -}; - -static int virtio_mem_rds_replay_cb(MemoryRegionSection *s, void *arg) -{ - struct VirtIOMEMReplayData *data = arg; - - return data->fn(s, data->opaque); -} - -static int virtio_mem_rds_replay_populated(const RamDiscardSource *rds, - const MemoryRegionSection *s, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - const VirtIOMEM *vmem = VIRTIO_MEM(rds); - struct VirtIOMEMReplayData data = { - .fn = replay_fn, - .opaque = opaque, - }; - - g_assert(s->mr == &vmem->memdev->mr); - return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rds_replay_cb); -} - -static int virtio_mem_rds_replay_discarded(const RamDiscardSource *rds, - const MemoryRegionSection *s, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - const VirtIOMEM *vmem = VIRTIO_MEM(rds); - struct VirtIOMEMReplayData data = { - .fn = replay_fn, - .opaque = opaque, - }; - - g_assert(s->mr == &vmem->memdev->mr); - return virtio_mem_for_each_unplugged_section(vmem, s, &data, - virtio_mem_rds_replay_cb); -} - static void virtio_mem_unplug_request_check(VirtIOMEM *vmem, Error **errp) { if (vmem->unplugged_inaccessible == ON_OFF_AUTO_OFF) { @@ -XXX,XX +XXX,XX @@ static void virtio_mem_class_init(ObjectClass *klass, const void *data) rdsc->get_min_granularity = virtio_mem_rds_get_min_granularity; rdsc->is_populated = virtio_mem_rds_is_populated; - rdsc->replay_populated = virtio_mem_rds_replay_populated; - rdsc->replay_discarded = virtio_mem_rds_replay_discarded; } static const TypeInfo virtio_mem_info = { -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Remove replay_populated and replay_discarded from RamDiscardSourceClass now that the RamDiscardManager handles replay iteration internally via is_populated. Remove the now-dead replay methods, helpers, and for_each_populated/discarded_section() from ram-block-attributes, which was the last source still carrying this code. Reviewed-by: Peter Xu <peterx@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-6-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/ram-discard-manager.h | 52 +++-------- system/ram-block-attributes.c | 130 --------------------------- 2 files changed, 10 insertions(+), 172 deletions(-) diff --git a/include/system/ram-discard-manager.h b/include/system/ram-discard-manager.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -XXX,XX +XXX,XX @@ static inline void ram_discard_listener_init(RamDiscardListener *rdl, /** * typedef ReplayRamDiscardState: * - * The callback handler for #RamDiscardSourceClass.replay_populated/ - * #RamDiscardSourceClass.replay_discarded to invoke on populated/discarded + * The callback handler used by ram_discard_manager_replay_populated() and + * ram_discard_manager_replay_discarded() to invoke on populated/discarded * parts. * * @section: the #MemoryRegionSection of populated/discarded part @@ -XXX,XX +XXX,XX @@ struct RamDiscardSourceClass { */ bool (*is_populated)(const RamDiscardSource *rds, const MemoryRegionSection *section); - - /** - * @replay_populated: - * - * Call the #ReplayRamDiscardState callback for all populated parts within - * the #MemoryRegionSection via the #RamDiscardSource. - * - * In case any call fails, no further calls are made. - * - * @rds: the #RamDiscardSource - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_populated)(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @replay_discarded: - * - * Call the #ReplayRamDiscardState callback for all discarded parts within - * the #MemoryRegionSection via the #RamDiscardSource. - * - * @rds: the #RamDiscardSource - * @section: the #MemoryRegionSection - * @replay_fn: the #ReplayRamDiscardState callback - * @opaque: pointer to forward to the callback - * - * Returns 0 on success, or a negative error if any notification failed. - */ - int (*replay_discarded)(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); }; /** @@ -XXX,XX +XXX,XX @@ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, /** * ram_discard_manager_replay_populated: * - * A wrapper to call the #RamDiscardSourceClass.replay_populated callback - * of the #RamDiscardSource sources. + * Iterate the given #MemoryRegionSection at minimum granularity, calling + * #RamDiscardSourceClass.is_populated for each chunk, and invoke @replay_fn + * for each contiguous populated range. In case any call fails, no further + * calls are made. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, /** * ram_discard_manager_replay_discarded: * - * A wrapper to call the #RamDiscardSourceClass.replay_discarded callback - * of the #RamDiscardSource sources. + * Iterate the given #MemoryRegionSection at minimum granularity, calling + * #RamDiscardSourceClass.is_populated for each chunk, and invoke @replay_fn + * for each contiguous discarded range. In case any call fails, no further + * calls are made. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -XXX,XX +XXX,XX @@ ram_block_attributes_get_block_size(void) return qemu_real_host_page_size(); } -typedef int (*ram_block_attributes_section_cb)(MemoryRegionSection *s, - void *arg); - -static int -ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, - const MemoryRegionSection *section, - void *arg, - ram_block_attributes_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size = ram_block_attributes_get_block_size(); - int ret = 0; - - first_bit = section->offset_within_region / block_size; - first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp = *section; - - offset = first_bit * block_size; - last_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret = cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - last_bit + 2); - } - - return ret; -} - -static int -ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *attr, - const MemoryRegionSection *section, - void *arg, - ram_block_attributes_section_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size = ram_block_attributes_get_block_size(); - int ret = 0; - - first_bit = section->offset_within_region / block_size; - first_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp = *section; - - offset = first_bit * block_size; - last_bit = find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size = (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret = cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit = find_next_zero_bit(attr->bitmap, - attr->bitmap_size, - last_bit + 2); - } - - return ret; -} - - -typedef struct RamBlockAttributesReplayData { - ReplayRamDiscardState fn; - void *opaque; -} RamBlockAttributesReplayData; - -static int ram_block_attributes_rds_replay_cb(MemoryRegionSection *section, - void *arg) -{ - RamBlockAttributesReplayData *data = arg; - - return data->fn(section, data->opaque); -} - /* RamDiscardSource interface implementation */ static uint64_t ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds, @@ -XXX,XX +XXX,XX @@ ram_block_attributes_rds_is_populated(const RamDiscardSource *rds, return first_discarded_bit > last_bit; } -static int -ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); - RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; - - g_assert(section->mr == attr->ram_block->mr); - return ram_block_attributes_for_each_populated_section(attr, section, &data, - ram_block_attributes_rds_replay_cb); -} - -static int -ram_block_attributes_rds_replay_discarded(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); - RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; - - g_assert(section->mr == attr->ram_block->mr); - return ram_block_attributes_for_each_discarded_section(attr, section, &data, - ram_block_attributes_rds_replay_cb); -} - static bool ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset, uint64_t size) @@ -XXX,XX +XXX,XX @@ static void ram_block_attributes_class_init(ObjectClass *klass, rdsc->get_min_granularity = ram_block_attributes_rds_get_min_granularity; rdsc->is_populated = ram_block_attributes_rds_is_populated; - rdsc->replay_populated = ram_block_attributes_rds_replay_populated; - rdsc->replay_discarded = ram_block_attributes_rds_replay_discarded; } -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Refactor RamDiscardManager to aggregate multiple RamDiscardSource instances. This enables scenarios where multiple components (e.g., virtio-mem and RamBlockAttributes) can coordinate memory discard state for the same memory region. The aggregation uses: - Populated: ALL sources populated - Discarded: ANY source discarded When a source is added with existing listeners, they are notified about regions that become discarded. When a source is removed, listeners are notified about regions that become populated. Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-7-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/memory.h | 4 +- include/system/ram-discard-manager.h | 141 +++++++-- hw/virtio/virtio-mem.c | 8 +- system/memory.c | 17 +- system/ram-block-attributes.c | 6 +- system/ram-discard-manager.c | 427 ++++++++++++++++++++++++--- 6 files changed, 518 insertions(+), 85 deletions(-) diff --git a/include/system/memory.h b/include/system/memory.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -XXX,XX +XXX,XX @@ int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *sou * * @mr: the #MemoryRegion * @source: #RamDiscardSource to remove + * + * Returns: 0 on success, or a negative error code on failure. */ -void memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); +int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source); /** * memory_region_find: translate an address/size relative to a diff --git a/include/system/ram-discard-manager.h b/include/system/ram-discard-manager.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -XXX,XX +XXX,XX @@ struct RamDiscardSourceClass { * becoming discarded in a different granularity than it was populated and the * other way around. */ + +typedef struct RamDiscardSourceEntry RamDiscardSourceEntry; + +struct RamDiscardSourceEntry { + RamDiscardSource *rds; + QLIST_ENTRY(RamDiscardSourceEntry) next; +}; + struct RamDiscardManager { Object parent; - RamDiscardSource *rds; MemoryRegion *mr; + QLIST_HEAD(, RamDiscardSourceEntry) source_list; + uint64_t min_granularity; QLIST_HEAD(, RamDiscardListener) rdl_list; }; -RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, - RamDiscardSource *rds); +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr); + +/** + * ram_discard_manager_add_source: + * + * Register a #RamDiscardSource with the #RamDiscardManager. The manager + * aggregates state from all registered sources using AND semantics: a region + * is considered populated only if ALL sources report it as populated. + * + * If listeners are already registered, they will be notified about any + * regions that become discarded due to adding this source. Specifically, + * for each region that the new source reports as discarded, if all other + * sources reported it as populated, listeners receive a discard notification. + * + * If any listener rejects the notification (returns an error), previously + * notified listeners are rolled back with populate notifications and the + * source is not added. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource to add + * + * Returns: 0 on success, -EBUSY if @source is already registered, or a + * negative error code if a listener rejected the state change. + */ +int ram_discard_manager_add_source(RamDiscardManager *rdm, + RamDiscardSource *source); + +/** + * ram_discard_manager_del_source: + * + * Unregister a #RamDiscardSource from the #RamDiscardManager. + * + * If listeners are already registered, they will be notified about any + * regions that become populated due to removing this source. Specifically, + * for each region that the removed source reported as discarded, if all + * remaining sources report it as populated, listeners receive a populate + * notification. + * + * If any listener rejects the notification (returns an error), previously + * notified listeners are rolled back with discard notifications and the + * source is not removed. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource to remove + * + * Returns: 0 on success, -ENOENT if @source is not registered, or a + * negative error code if a listener rejected the state change. + */ +int ram_discard_manager_del_source(RamDiscardManager *rdm, + RamDiscardSource *source); + uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, const MemoryRegion *mr); +/** + * ram_discard_manager_is_populated: + * + * Check if the given memory region section is populated. + * If the manager has no sources, it is considered populated. + * + * @rdm: the #RamDiscardManager + * @section: the #MemoryRegionSection to check + * + * Returns: true if the section is populated, false otherwise. + */ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, const MemoryRegionSection *section); /** * ram_discard_manager_replay_populated: * - * Iterate the given #MemoryRegionSection at minimum granularity, calling - * #RamDiscardSourceClass.is_populated for each chunk, and invoke @replay_fn - * for each contiguous populated range. In case any call fails, no further - * calls are made. + * Call @replay_fn on regions that are populated in all sources. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, /** * ram_discard_manager_replay_discarded: * - * Iterate the given #MemoryRegionSection at minimum granularity, calling - * #RamDiscardSourceClass.is_populated for each chunk, and invoke @replay_fn - * for each contiguous discarded range. In case any call fails, no further - * calls are made. + * Call @replay_fn on regions that are discarded in any sources. * * @rdm: the #RamDiscardManager * @section: the #MemoryRegionSection @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm, void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl); -/* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. +/** + * ram_discard_manager_notify_populate: + * + * Notify listeners that a region is about to be populated by a source. + * For multi-source aggregation, only notifies when all sources agree + * the region is populated (intersection). + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is populating + * @offset: offset within the memory region + * @size: size of the region being populated + * + * Returns 0 on success, or a negative error if any listener rejects. */ int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size); -/* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. +/** + * ram_discard_manager_notify_discard: + * + * Notify listeners that a region has been discarded by a source. + * For multi-source aggregation, always notifies immediately + * (union semantics - any source discarding makes region discarded). + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is discarding + * @offset: offset within the memory region + * @size: size of the region being discarded */ void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size); -/* - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. +/** + * ram_discard_manager_notify_discard_all: + * + * Notify listeners that all regions have been discarded by a source. + * + * @rdm: the #RamDiscardManager + * @source: the #RamDiscardSource that is discarding */ -void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm); +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm, + RamDiscardSource *source); -/* +/** + * ram_discard_manager_replay_populated_to_listeners: + * * Replay populated sections to all registered listeners. + * For multi-source aggregation, only replays regions where all sources + * are populated (intersection). * - * Note: later refactoring should take the source into account and the manager - * should be able to aggregate multiple sources. + * @rdm: the #RamDiscardManager + * + * Returns 0 on success, or a negative error if any notification failed. */ int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm); diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -XXX,XX +XXX,XX @@ static void virtio_mem_notify_unplug(VirtIOMEM *vmem, uint64_t offset, { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - ram_discard_manager_notify_discard(rdm, offset, size); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(vmem), + offset, size); } static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, @@ -XXX,XX +XXX,XX @@ static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(&vmem->memdev->mr); - return ram_discard_manager_notify_populate(rdm, offset, size); + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(vmem), + offset, size); } static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) @@ -XXX,XX +XXX,XX @@ static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) return; } - ram_discard_manager_notify_discard_all(rdm); + ram_discard_manager_notify_discard_all(rdm, RAM_DISCARD_SOURCE(vmem)); } static bool virtio_mem_is_range_plugged(const VirtIOMEM *vmem, diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { g_assert(memory_region_is_ram(mr)); - if (mr->rdm) { - return -EBUSY; + + if (!mr->rdm) { + mr->rdm = ram_discard_manager_new(mr); } - mr->rdm = ram_discard_manager_new(mr, RAM_DISCARD_SOURCE(source)); - return 0; + return ram_discard_manager_add_source(mr->rdm, source); } -void memory_region_del_ram_discard_source(MemoryRegion *mr, +int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { - g_assert(mr->rdm->rds == source); + g_assert(mr->rdm); + + return ram_discard_manager_del_source(mr->rdm, source); - object_unref(mr->rdm); - mr->rdm = NULL; + /* if there is no source and no listener left, we could free rdm */ } /* Called with rcu_read_lock held. */ diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -XXX,XX +XXX,XX @@ ram_block_attributes_notify_discard(RamBlockAttributes *attr, { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - ram_discard_manager_notify_discard(rdm, offset, size); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(attr), + offset, size); } static int @@ -XXX,XX +XXX,XX @@ ram_block_attributes_notify_populate(RamBlockAttributes *attr, { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); - return ram_discard_manager_notify_populate(rdm, offset, size); + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(attr), + offset, size); } int ram_block_attributes_state_change(RamBlockAttributes *attr, diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qemu/queue.h" #include "system/memory.h" static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSource *rds, @@ -XXX,XX +XXX,XX @@ static bool ram_discard_source_is_populated(const RamDiscardSource *rds, } /* - * Iterate the section at source granularity, aggregating consecutive chunks - * with matching populated state, and call replay_fn for each run. + * Iterate a single source's populated or discarded regions and call + * replay_fn for each contiguous run. */ -static int replay_by_populated_state(const RamDiscardManager *rdm, - const MemoryRegionSection *section, - bool replay_populated, - ReplayRamDiscardState replay_fn, - void *opaque) +static int replay_source_by_state(const RamDiscardSource *source, + const MemoryRegion *mr, + const MemoryRegionSection *section, + bool replay_populated, + ReplayRamDiscardState replay_fn, + void *opaque) { uint64_t granularity, offset, size, end, pos, run_start = 0; bool in_run = false; int ret = 0; - granularity = ram_discard_source_get_min_granularity(rdm->rds, rdm->mr); + granularity = ram_discard_source_get_min_granularity(source, mr); offset = section->offset_within_region; size = int128_get64(section->size); end = offset + size; @@ -XXX,XX +XXX,XX @@ static int replay_by_populated_state(const RamDiscardManager *rdm, .offset_within_region = pos, .size = int128_make64(granularity), }; - bool populated = ram_discard_source_is_populated(rdm->rds, &chunk); + bool populated = ram_discard_source_is_populated(source, &chunk); if (populated == replay_populated) { if (!in_run) { @@ -XXX,XX +XXX,XX @@ static int replay_by_populated_state(const RamDiscardManager *rdm, return ret; } -RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, - RamDiscardSource *rds) +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr) { RamDiscardManager *rdm; rdm = RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); - rdm->rds = rds; rdm->mr = mr; - QLIST_INIT(&rdm->rdl_list); return rdm; } +static void ram_discard_manager_update_granularity(RamDiscardManager *rdm) +{ + RamDiscardSourceEntry *entry; + uint64_t granularity = 0; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + uint64_t src_granularity; + + src_granularity = + ram_discard_source_get_min_granularity(entry->rds, rdm->mr); + g_assert(src_granularity != 0); + if (granularity == 0) { + granularity = src_granularity; + } else { + granularity = MIN(granularity, src_granularity); + } + } + rdm->min_granularity = granularity; +} + +static RamDiscardSourceEntry * +ram_discard_manager_find_source(RamDiscardManager *rdm, RamDiscardSource *rds) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (entry->rds == rds) { + return entry; + } + } + return NULL; +} + +static int rdl_populate_cb(const MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + MemoryRegionSection tmp = *rdl->section; + + g_assert(section->mr == rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_region, + int128_get64(section->size))) { + return 0; + } + + return rdl->notify_populate(rdl, &tmp); +} + +static int rdl_discard_cb(const MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl = opaque; + MemoryRegionSection tmp = *rdl->section; + + g_assert(section->mr == rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_region, + int128_get64(section->size))) { + return 0; + } + + rdl->notify_discard(rdl, &tmp); + return 0; +} + +static bool rdm_is_all_populated_skip(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + const RamDiscardSource *skip_source) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (skip_source && entry->rds == skip_source) { + continue; + } + if (!ram_discard_source_is_populated(entry->rds, section)) { + return false; + } + } + return true; +} + +typedef struct SourceNotifyCtx { + RamDiscardManager *rdm; + RamDiscardListener *rdl; + RamDiscardSource *source; /* added or removed */ +} SourceNotifyCtx; + +/* + * Unified helper to replay regions based on populated state. + * If replay_populated is true: replay regions where ALL sources are populated. + * If replay_populated is false: replay regions where ANY source is discarded. + */ +static int replay_by_populated_state(const RamDiscardManager *rdm, + const MemoryRegionSection *section, + const RamDiscardSource *skip_source, + bool replay_populated, + ReplayRamDiscardState replay_fn, + void *user_opaque) +{ + uint64_t granularity = rdm->min_granularity; + uint64_t offset, end_offset; + uint64_t run_start = 0; + bool in_run = false; + int ret = 0; + + if (QLIST_EMPTY(&rdm->source_list)) { + if (replay_populated) { + return replay_fn(section, user_opaque); + } + return 0; + } + + g_assert(granularity != 0); + + offset = section->offset_within_region; + end_offset = offset + int128_get64(section->size); + + while (offset < end_offset) { + MemoryRegionSection subsection = { + .mr = section->mr, + .offset_within_region = offset, + .size = int128_make64(MIN(granularity, end_offset - offset)), + }; + bool all_populated; + bool included; + + all_populated = rdm_is_all_populated_skip(rdm, &subsection, + skip_source); + included = replay_populated ? all_populated : !all_populated; + + if (included) { + if (!in_run) { + run_start = offset; + in_run = true; + } + } else { + if (in_run) { + MemoryRegionSection run_section = { + .mr = section->mr, + .offset_within_region = run_start, + .size = int128_make64(offset - run_start), + }; + ret = replay_fn(&run_section, user_opaque); + if (ret) { + return ret; + } + in_run = false; + } + } + if (granularity > end_offset - offset) { + break; + } + offset += granularity; + } + + if (in_run) { + MemoryRegionSection run_section = { + .mr = section->mr, + .offset_within_region = run_start, + .size = int128_make64(end_offset - run_start), + }; + ret = replay_fn(&run_section, user_opaque); + } + + return ret; +} + +static int add_source_check_discard_cb(const MemoryRegionSection *section, + void *opaque) +{ + SourceNotifyCtx *ctx = opaque; + + return replay_by_populated_state(ctx->rdm, section, ctx->source, true, + rdl_discard_cb, ctx->rdl); +} + +static int del_source_check_populate_cb(const MemoryRegionSection *section, + void *opaque) +{ + SourceNotifyCtx *ctx = opaque; + + return replay_by_populated_state(ctx->rdm, section, ctx->source, true, + rdl_populate_cb, ctx->rdl); +} + +int ram_discard_manager_add_source(RamDiscardManager *rdm, + RamDiscardSource *source) +{ + RamDiscardSourceEntry *entry; + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + if (ram_discard_manager_find_source(rdm, source)) { + return -EBUSY; + } + + /* + * If there are existing listeners, notify them about regions that + * become discarded due to adding this source. Only notify for regions + * that were previously populated (all other sources agreed). + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl, + /* no need to set source */ + }; + ret = replay_source_by_state(source, rdm->mr, rdl->section, + false, + add_source_check_discard_cb, &ctx); + if (ret) { + break; + } + } + if (ret) { + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl2, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + del_source_check_populate_cb, + &ctx); + if (rdl == rdl2) { + break; + } + } + + return ret; + } + + entry = g_new0(RamDiscardSourceEntry, 1); + entry->rds = source; + QLIST_INSERT_HEAD(&rdm->source_list, entry, next); + + ram_discard_manager_update_granularity(rdm); + + return ret; +} + +int ram_discard_manager_del_source(RamDiscardManager *rdm, + RamDiscardSource *source) +{ + RamDiscardSourceEntry *entry; + RamDiscardListener *rdl, *rdl2; + int ret = 0; + + entry = ram_discard_manager_find_source(rdm, source); + if (!entry) { + return -ENOENT; + } + + /* + * If there are existing listeners, check if any regions become + * populated due to removing this source. + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl, + .source = source, + }; + /* + * From the previously discarded regions, check if any + * regions become populated. + */ + ret = replay_source_by_state(source, rdm->mr, rdl->section, + false, + del_source_check_populate_cb, + &ctx); + if (ret) { + break; + } + } + if (ret) { + QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { + SourceNotifyCtx ctx = { + .rdm = rdm, + .rdl = rdl2, + .source = source, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + add_source_check_discard_cb, + &ctx); + if (rdl == rdl2) { + break; + } + } + + return ret; + } + + QLIST_REMOVE(entry, next); + g_free(entry); + ram_discard_manager_update_granularity(rdm); + return ret; +} + uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm, const MemoryRegion *mr) { - return ram_discard_source_get_min_granularity(rdm->rds, mr); + g_assert(mr == rdm->mr); + return rdm->min_granularity; } +/* + * Aggregated query: returns true only if ALL sources report populated (AND). + */ bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, const MemoryRegionSection *section) { - return ram_discard_source_is_populated(rdm->rds, section); + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (!ram_discard_source_is_populated(entry->rds, section)) { + return false; + } + } + return true; } int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - return replay_by_populated_state(rdm, section, true, replay_fn, opaque); + return replay_by_populated_state(rdm, section, NULL, true, + replay_fn, opaque); } int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - return replay_by_populated_state(rdm, section, false, replay_fn, opaque); + return replay_by_populated_state(rdm, section, NULL, false, + replay_fn, opaque); } static void ram_discard_manager_initfn(Object *obj) { RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); + QLIST_INIT(&rdm->source_list); QLIST_INIT(&rdm->rdl_list); + rdm->min_granularity = 0; } static void ram_discard_manager_finalize(Object *obj) @@ -XXX,XX +XXX,XX @@ static void ram_discard_manager_finalize(Object *obj) RamDiscardManager *rdm = RAM_DISCARD_MANAGER(obj); g_assert(QLIST_EMPTY(&rdm->rdl_list)); + g_assert(QLIST_EMPTY(&rdm->source_list)); } int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size) { RamDiscardListener *rdl, *rdl2; + MemoryRegionSection section = { + .mr = rdm->mr, + .offset_within_region = offset, + .size = int128_make64(size), + }; int ret = 0; - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; + g_assert(ram_discard_manager_find_source(rdm, source)); - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret = rdl->notify_populate(rdl, &tmp); + /* + * Only notify about regions that are populated in ALL sources. + * Skip the calling source: it has implicitly declared itself populated + * for this range but may not have updated its bitmap yet. + */ + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret = replay_by_populated_state(rdm, §ion, source, true, + rdl_populate_cb, rdl); if (ret) { break; } } if (ret) { - /* Notify all already-notified listeners about discard. */ + /* + * Rollback: notify discard for listeners we already notified, + * including the failing listener which may have been partially + * notified. Listeners must handle discard notifications for + * regions they didn't receive populate notifications for. + */ QLIST_FOREACH(rdl2, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl2->section; - + replay_by_populated_state(rdm, §ion, source, true, + rdl_discard_cb, rdl2); if (rdl2 == rdl) { break; } - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl2->notify_discard(rdl2, &tmp); } } return ret; } void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size) { RamDiscardListener *rdl; - + MemoryRegionSection section = { + .mr = rdm->mr, + .offset_within_region = offset, + .size = int128_make64(size), + }; + + g_assert(ram_discard_manager_find_source(rdm, source)); + + /* + * Only notify about ranges that were aggregately populated before this + * source's discard. Since the source has already updated its state, + * we use replay_by_populated_state with this source skipped - it will + * replay only the ranges where all OTHER sources are populated. + */ QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp = *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - rdl->notify_discard(rdl, &tmp); + replay_by_populated_state(rdm, §ion, source, true, + rdl_discard_cb, rdl); } } -void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm, + RamDiscardSource *source) { RamDiscardListener *rdl; + g_assert(ram_discard_manager_find_source(rdm, source)); + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { rdl->notify_discard(rdl, rdl->section); } } -static int rdm_populate_cb(const MemoryRegionSection *section, void *opaque) -{ - RamDiscardListener *rdl = opaque; - - return rdl->notify_populate(rdl, section); -} - void ram_discard_manager_register_listener(RamDiscardManager *rdm, RamDiscardListener *rdl, MemoryRegionSection *section) @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm, QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); ret = ram_discard_manager_replay_populated(rdm, rdl->section, - rdm_populate_cb, rdl); + rdl_populate_cb, rdl); if (ret) { error_report("%s: Replaying populated ranges failed: %s", __func__, strerror(-ret)); @@ -XXX,XX +XXX,XX @@ int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) QLIST_FOREACH(rdl, &rdm->rdl_list, next) { ret = ram_discard_manager_replay_populated(rdm, rdl->section, - rdm_populate_cb, rdl); + rdl_populate_cb, rdl); if (ret) { break; } -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> ram_block_attributes_destroy() was called from reclaim_ramblock(), which runs as an RCU callback deferred by call_rcu(). However,when the RamDiscardManager is finalized, it will assert that its source_list is empty in the next commit. Since the RCU callback hasn't run yet, the source added by ram_block_attributes_create() is still attached. Move ram_block_attributes_destroy() into qemu_ram_free() so the source is removed synchronously. This is safe because qemu_ram_free() during shutdown runs after pause_all_vcpus(), so no vCPU thread can concurrently access the attributes via kvm_convert_memory(). Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-8-5768e6a0943d@redhat.com [peterx: rebase on top of qemu_ram_free() change] Signed-off-by: Peter Xu <peterx@redhat.com> --- system/physmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/physmem.c b/system/physmem.c index XXXXXXX..XXXXXXX 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -XXX,XX +XXX,XX @@ static void reclaim_ramblock(RAMBlock *block) } if (block->guest_memfd >= 0) { - ram_block_attributes_destroy(block->attributes); close(block->guest_memfd); ram_block_coordinated_discard_require(false); } @@ -XXX,XX +XXX,XX @@ void qemu_ram_free(RAMBlock *block) qatomic_set(&ram_list.mru_block, NULL); /* Write list before version */ qatomic_store_release(&ram_list.version, ram_list.version + 1); + g_clear_pointer(&block->attributes, ram_block_attributes_destroy); call_rcu(block, reclaim_ramblock, rcu); qemu_mutex_unlock_ramlist(); } -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Listeners now hold a reference to the RamDiscardManager, ensuring it stays alive while listeners are registered. The RDM is eagerly freed when the last source and listener are removed, and also unreffed during MemoryRegion finalization as a safety net. This completes the TODO left in the previous commit and prevents both use-after-free and memory leaks of the RamDiscardManager. Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-9-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- system/memory.c | 14 +++++++++++--- system/ram-discard-manager.c | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/system/memory.c b/system/memory.c index XXXXXXX..XXXXXXX 100644 --- a/system/memory.c +++ b/system/memory.c @@ -XXX,XX +XXX,XX @@ static void memory_region_finalize(Object *obj) memory_region_clear_coalescing(mr); g_free((char *)mr->name); g_free(mr->ioeventfds); + object_unref(mr->rdm); } Object *memory_region_owner(const MemoryRegion *mr) @@ -XXX,XX +XXX,XX @@ int memory_region_add_ram_discard_source(MemoryRegion *mr, int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { + int ret; g_assert(mr->rdm); - return ram_discard_manager_del_source(mr->rdm, source); - - /* if there is no source and no listener left, we could free rdm */ + ret = ram_discard_manager_del_source(mr->rdm, source); + if (ret != 0) { + return ret; + } + if (QLIST_EMPTY(&mr->rdm->source_list) && QLIST_EMPTY(&mr->rdm->rdl_list)) { + object_unref(mr->rdm); + mr->rdm = NULL; + } + return 0; } /* Called with rcu_read_lock held. */ diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index XXXXXXX..XXXXXXX 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm, g_assert(section->mr == rdm->mr); + object_ref(rdm); rdl->section = memory_region_section_new_copy(section); QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); @@ -XXX,XX +XXX,XX @@ void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, memory_region_section_free_copy(rdl->section); rdl->section = NULL; QLIST_REMOVE(rdl, next); + object_unref(rdm); } int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm) -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Add various unit tests for the RamDiscardManager multi-source aggregation functionality. The test uses a TestRamDiscardSource QOM object that tracks populated state via a bitmap, similar to RamBlockAttributes implementation. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-10-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- MAINTAINERS | 2 + tests/unit/test-ram-discard-manager-stubs.c | 48 + tests/unit/test-ram-discard-manager.c | 1235 +++++++++++++++++++ tests/unit/meson.build | 8 +- 4 files changed, 1292 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test-ram-discard-manager-stubs.c create mode 100644 tests/unit/test-ram-discard-manager.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: system/memory-internal.h F: system/ram-block-attributes.c F: system/ram-discard-manager.c F: scripts/coccinelle/memory-region-housekeeping.cocci +F: tests/unit/test-ram-discard-manager.c +F: tests/unit/test-ram-discard-manager-stubs.c Memory devices M: David Hildenbrand <david@kernel.org> diff --git a/tests/unit/test-ram-discard-manager-stubs.c b/tests/unit/test-ram-discard-manager-stubs.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/unit/test-ram-discard-manager-stubs.c @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qom/object.h" +#include "glib.h" +#include "system/memory.h" + +RamDiscardManager *memory_region_get_ram_discard_manager(MemoryRegion *mr) +{ + return mr->rdm; +} + +int memory_region_add_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + if (!mr->rdm) { + mr->rdm = ram_discard_manager_new(mr); + } + return ram_discard_manager_add_source(mr->rdm, source); +} + +int memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + RamDiscardManager *rdm = mr->rdm; + + if (!rdm) { + return 0; + } + + return ram_discard_manager_del_source(rdm, source); +} + +uint64_t memory_region_size(const MemoryRegion *mr) +{ + return int128_get64(mr->size); +} + +MemoryRegionSection *memory_region_section_new_copy(MemoryRegionSection *s) +{ + MemoryRegionSection *copy = g_new(MemoryRegionSection, 1); + *copy = *s; + return copy; +} + +void memory_region_section_free_copy(MemoryRegionSection *s) +{ + g_free(s); +} diff --git a/tests/unit/test-ram-discard-manager.c b/tests/unit/test-ram-discard-manager.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/unit/test-ram-discard-manager.c @@ -XXX,XX +XXX,XX @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "qemu/osdep.h" +#include "qemu/bitmap.h" +#include "qemu/module.h" +#include "qemu/main-loop.h" +#include "qapi/error.h" +#include "qom/object.h" +#include "qom/qom-qobject.h" +#include "glib.h" +#include "system/memory.h" + +#define TYPE_TEST_RAM_DISCARD_SOURCE "test-ram-discard-source" + +OBJECT_DECLARE_SIMPLE_TYPE(TestRamDiscardSource, TEST_RAM_DISCARD_SOURCE) + +struct TestRamDiscardSource { + Object parent; + + MemoryRegion *mr; + uint64_t granularity; + unsigned long *bitmap; + uint64_t bitmap_size; +}; + +static uint64_t test_rds_get_min_granularity(const RamDiscardSource *rds, + const MemoryRegion *mr) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + + g_assert(mr == src->mr); + return src->granularity; +} + +static bool test_rds_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds); + uint64_t offset = section->offset_within_region; + uint64_t size = int128_get64(section->size); + uint64_t first_bit = offset / src->granularity; + uint64_t last_bit = (offset + size - 1) / src->granularity; + unsigned long found; + + g_assert(section->mr == src->mr); + + /* Check if any bit in range is zero (discarded) */ + found = find_next_zero_bit(src->bitmap, last_bit + 1, first_bit); + return found > last_bit; +} + +static void test_rds_class_init(ObjectClass *klass, const void *data) +{ + RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity = test_rds_get_min_granularity; + rdsc->is_populated = test_rds_is_populated; +} + +static const TypeInfo test_rds_info = { + .name = TYPE_TEST_RAM_DISCARD_SOURCE, + .parent = TYPE_OBJECT, + .instance_size = sizeof(TestRamDiscardSource), + .class_init = test_rds_class_init, + .interfaces = (const InterfaceInfo[]) { + { TYPE_RAM_DISCARD_SOURCE }, + { } + }, +}; + +static TestRamDiscardSource *test_source_new(MemoryRegion *mr, + uint64_t granularity) +{ + TestRamDiscardSource *src; + uint64_t region_size = memory_region_size(mr); + + src = TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURCE)); + src->mr = mr; + src->granularity = granularity; + src->bitmap_size = DIV_ROUND_UP(region_size, granularity); + src->bitmap = bitmap_new(src->bitmap_size); + + return src; +} + +static void test_source_free(TestRamDiscardSource *src) +{ + g_free(src->bitmap); + object_unref(OBJECT(src)); +} + +static void test_source_populate(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_set(src->bitmap, first_bit, nbits); +} + +static void test_source_discard(TestRamDiscardSource *src, + uint64_t offset, uint64_t size) +{ + uint64_t first_bit = offset / src->granularity; + uint64_t nbits = size / src->granularity; + + bitmap_clear(src->bitmap, first_bit, nbits); +} + +typedef struct TestListener { + RamDiscardListener rdl; + int populate_count; + int discard_count; + uint64_t last_populate_offset; + uint64_t last_populate_size; + uint64_t last_discard_offset; + uint64_t last_discard_size; + int fail_on_populate; /* Return error on Nth populate */ + int populate_call_num; +} TestListener; + +static int test_listener_populate(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->populate_call_num++; + if (tl->fail_on_populate > 0 && + tl->populate_call_num >= tl->fail_on_populate) { + return -ENOMEM; + } + + tl->populate_count++; + tl->last_populate_offset = section->offset_within_region; + tl->last_populate_size = int128_get64(section->size); + return 0; +} + +static void test_listener_discard(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl = container_of(rdl, TestListener, rdl); + + tl->discard_count++; + tl->last_discard_offset = section->offset_within_region; + tl->last_discard_size = int128_get64(section->size); +} + +static void test_listener_init(TestListener *tl) +{ + ram_discard_listener_init(&tl->rdl, + test_listener_populate, + test_listener_discard); +} + +#define TEST_REGION_SIZE (16 * 1024 * 1024) /* 16 MB */ +#define GRANULARITY_4K (4 * 1024) +#define GRANULARITY_2M (2 * 1024 * 1024) + +static MemoryRegion *test_mr; + +static void test_setup(void) +{ + test_mr = g_new0(MemoryRegion, 1); + test_mr->size = int128_make64(TEST_REGION_SIZE); + test_mr->ram = true; +} + +static void test_teardown(void) +{ + g_clear_pointer(&test_mr->rdm, object_unref); + object_unparent(OBJECT(test_mr)); + g_free(test_mr); + test_mr = NULL; +} + +static void test_single_source_basic(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_null(rdm); + + /* Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + /* Initially all discarded */ + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate a range in source */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + + /* Now should be populated */ + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check larger section */ + section.size = int128_make64(GRANULARITY_4K * 4); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check section that spans populated and discarded */ + section.size = int128_make64(GRANULARITY_4K * 8); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + test_source_free(src); + test_teardown(); +} + +static void test_single_source_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some ranges before adding listener */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated regions */ + g_assert_cmpint(tl.populate_count, ==, 2); + + /* Notify populate for new range */ + tl.populate_count = 0; + test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 16, + GRANULARITY_4K * 2); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 16); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 2); + + /* Notify discard */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, 0); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + /* Unregister listener */ + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static void test_two_sources_same_granularity(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add first source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + + /* Add second source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Check granularity */ + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded -> aggregated discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src1 only */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in src2 only */ + test_source_discard(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate in both -> aggregated populated */ + test_source_populate(src1, 0, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Remove sources */ + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Two sources with different granularities (4K and 2M). + * The aggregated granularity should be GCD(4K, 2M) = 4K. + */ +static void test_two_sources_different_granularity(void) +{ + TestRamDiscardSource *src_4k, *src_2m; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src_4k = test_source_new(test_mr, GRANULARITY_4K); + src_2m = test_source_new(test_mr, GRANULARITY_2M); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_4k)); + g_assert_cmpint(ret, ==, 0); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_2m)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + ==, GRANULARITY_4K); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K); + + /* Both discarded */ + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 4K in src_4k, but src_2m still discarded the whole 2M block */ + test_source_populate(src_4k, 0, GRANULARITY_4K); + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate 2M in src_2m (which includes the 4K block) */ + test_source_populate(src_2m, 0, GRANULARITY_2M); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check a 4K block at offset 4K (populated in src_2m but not in src_4k) */ + section.offset_within_region = GRANULARITY_4K; + g_assert_false(ram_discard_manager_is_populated(rdm, §ion)); + + /* Populate it in src_4k */ + test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_2m)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4k)); + + test_source_free(src_2m); + test_source_free(src_4k); + test_teardown(); +} + +/* + * Test: Notification with two sources. + * Populate notification should only fire when all sources are populated. + */ +static void test_two_sources_notification(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* No populate notifications yet (all discarded) */ + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate in src1 only - no notification (src2 still discarded) */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 0); + + /* Populate same range in src2 - now should notify */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src2), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Discard from src1 - should notify discard immediately */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Adding source with existing listener. + * When a new source is added, listeners should be notified about + * regions that become discarded. + */ +static void test_add_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src1 */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about populated region */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpint(tl.last_populate_offset, ==, 0); + g_assert_cmpint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* src2 has part of the region populated, part discarded */ + /* src2 has 0-4 populated, 4-8 discarded */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + /* Add src2 - listener should be notified about newly discarded regions */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + /* + * The range 4K*4 to 4K*8 was populated in src1 but discarded in src2, + * so it becomes aggregated-discarded. Listener should be notified. + * Only this range should trigger a discard notification - regions beyond + * 4K*8 were already discarded in src1, so adding src2 doesn't change them. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpint(tl.last_discard_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpint(tl.last_discard_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Removing source with existing listener. + * When a source is removed, listeners should be notified about + * regions that become populated. + */ +static void test_remove_source_with_listener(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* src1: all of first 8 blocks populated */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + /* src2: only first 4 blocks populated */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Only first 4 blocks are aggregated-populated */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Remove src2 - blocks 4-8 should become populated */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + + /* Listener should be notified about newly populated region (4K*4 to 4K*8) */ + g_assert_cmpint(tl.populate_count, >=, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Add a source, register a listener, remove the source, then add it back. + * This checks the transition from 0 sources (all populated) to 1 source + * (partially discarded) with an active listener. + */ +static void test_readd_source_with_listener(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + /* 1. Add source */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* 2. Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Listener notified about populated region (0 - 32K) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 8); + + /* 3. Remove source */ + tl.populate_count = 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + + /* + * With 0 sources, everything is populated. + * The range that was discarded in src (from 32K to end) becomes populated. + */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_populate_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + /* 4. Add source back */ + tl.discard_count = 0; + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* + * Now we have 1 source again. The range (32K to end) is discarded again. + * Listener should be notified about this discard. + */ + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_discard_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Duplicate source registration should fail. + */ +static void test_duplicate_source(void) +{ + TestRamDiscardSource *src; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + + /* Adding same source again should fail */ + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, -EBUSY); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Populate notification rollback on listener error. + */ +static void test_populate_rollback(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register two listeners */ + test_listener_init(&tl1); + test_listener_init(&tl2); + tl2.fail_on_populate = 1; /* Second listener fails on first populate */ + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + + /* + * Register tl2 first so it's visited second (QLIST_INSERT_HEAD reverses + * registration order). This ensures tl1 receives populate before tl2 + * fails. + */ + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion); + + /* Try to populate - should fail and roll back */ + test_source_populate(src, 0, GRANULARITY_4K); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, -ENOMEM); + + /* First listener should have received populate then discard (rollback) */ + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl1.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Replay populated with two sources (intersection). + */ +static void test_replay_populated_intersection(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-7 populated + * src2: blocks 4-11 populated + * Intersection: blocks 4-7 + */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + test_source_populate(src2, GRANULARITY_4K * 4, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener - should only get notified about intersection */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should have been notified about blocks 4-7 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 4); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Empty region (no sources). + */ +static void test_no_sources(void) +{ + test_setup(); + + /* No sources - should have no manager */ + g_assert_null(memory_region_get_ram_discard_manager(test_mr)); + g_assert_false(memory_region_has_ram_discard_manager(test_mr)); + + test_teardown(); +} + +static void test_redundant_discard(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* Add sources */ + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(TEST_REGION_SIZE); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Populate intersection (0-4K) in both sources */ + test_source_populate(src1, 0, GRANULARITY_4K); + test_source_populate(src2, 0, GRANULARITY_4K); + + /* Notify populate src1 - should trigger listener populate */ + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl.populate_count, ==, 1); + + /* Now Discard src1 -> Aggregate Discarded */ + tl.discard_count = 0; + test_source_discard(src1, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + + /* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */ + /* Listener should NOT receive another discard notification for the same range. */ + test_source_discard(src2, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, GRANULARITY_4K); + + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +/* + * Test: Listener with partial section coverage. + * Listener should only receive notifications for its registered range. + */ +static void test_partial_listener_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate blocks 0-7 */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for only blocks 2-5 (not the full region) */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = GRANULARITY_4K * 2; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should be notified only about blocks 2-5 (intersection) */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 2); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard block 0 - outside listener's section, no notification */ + tl.discard_count = 0; + test_source_discard(src, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 0); + + /* Discard block 3 - inside listener's section */ + test_source_discard(src, GRANULARITY_4K * 3, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 3, GRANULARITY_4K); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 3); + + /* Discard spanning boundary (blocks 5-6) - only block 5 in section */ + tl.discard_count = 0; + test_source_discard(src, GRANULARITY_4K * 5, GRANULARITY_4K * 2); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 5, GRANULARITY_4K * 2); + g_assert_cmpint(tl.discard_count, ==, 1); + g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 5); + g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with different (non-overlapping) sections. + */ +static void test_multiple_listeners_different_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-3 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 8-11 */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 8; + section2.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Initially all discarded - no populate notifications */ + g_assert_cmpint(tl1.populate_count, ==, 0); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 0-3 - only tl1 should be notified */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 0); + + /* Populate blocks 8-11 - only tl2 should be notified */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 4-7 (gap) - neither listener should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Multiple listeners with overlapping sections. + */ +static void test_overlapping_listener_sections(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section1, section2; + TestListener tl1 = { 0, }, tl2 = { 0, }; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-7 */ + test_listener_init(&tl1); + section1.mr = test_mr; + section1.offset_within_region = 0; + section1.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1); + + /* Listener 2: blocks 4-11 (overlaps with tl1 on blocks 4-7) */ + test_listener_init(&tl2); + section2.mr = test_mr; + section2.offset_within_region = GRANULARITY_4K * 4; + section2.size = int128_make64(GRANULARITY_4K * 8); + ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2); + + /* Populate blocks 4-7 (overlap region) - both should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 1); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 0-3 - only tl1 */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 1); + + /* Populate blocks 8-11 - only tl2 */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpint(tl1.populate_count, ==, 2); + g_assert_cmpint(tl2.populate_count, ==, 2); + + ram_discard_manager_unregister_listener(rdm, &tl2.rdl); + ram_discard_manager_unregister_listener(rdm, &tl1.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +/* + * Test: Listener at exact memory region boundaries. + */ +static void test_boundary_section(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + TestListener tl = { 0, }; + uint64_t last_offset; + int ret; + + test_setup(); + + src = test_source_new(test_mr, GRANULARITY_4K); + + /* Populate last 4 blocks of the region */ + last_offset = TEST_REGION_SIZE - GRANULARITY_4K * 4; + test_source_populate(src, last_offset, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, ==, 0); + rdm = memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for exactly the last 4 blocks */ + test_listener_init(&tl); + section.mr = test_mr; + section.offset_within_region = last_offset; + section.size = int128_make64(GRANULARITY_4K * 4); + ram_discard_manager_register_listener(rdm, &tl.rdl, §ion); + + /* Should receive notification for the populated range */ + g_assert_cmpint(tl.populate_count, ==, 1); + g_assert_cmpuint(tl.last_populate_offset, ==, last_offset); + g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4); + + /* Discard exactly at boundary */ + tl.discard_count = 0; + test_source_discard(src, last_offset, GRANULARITY_4K * 4); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src), + last_offset, GRANULARITY_4K * 4); + g_assert_cmpint(tl.discard_count, ==, 1); + + ram_discard_manager_unregister_listener(rdm, &tl.rdl); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src)); + test_source_free(src); + test_teardown(); +} + +static int count_discarded_blocks(const MemoryRegionSection *section, + void *opaque) +{ + int *count = opaque; + *count += int128_get64(section->size) / GRANULARITY_4K; + return 0; +} + +/* + * Test: replay_discarded with two sources (union semantics). + */ +static void test_replay_discarded(void) +{ + TestRamDiscardSource *src1, *src2; + RamDiscardManager *rdm; + MemoryRegionSection section; + int count = 0; + int ret; + + test_setup(); + + src1 = test_source_new(test_mr, GRANULARITY_4K); + src2 = test_source_new(test_mr, GRANULARITY_4K); + + /* + * src1: blocks 0-3 populated, rest discarded + * src2: blocks 2-5 populated, rest discarded + * Aggregated populated: blocks 2-3 (intersection) + * Aggregated discarded: blocks 0-1, 4-5, 6+ (union of discarded) + */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + test_source_populate(src2, GRANULARITY_4K * 2, GRANULARITY_4K * 4); + + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, ==, 0); + ret = memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, ==, 0); + + rdm = memory_region_get_ram_discard_manager(test_mr); + + section.mr = test_mr; + section.offset_within_region = 0; + section.size = int128_make64(GRANULARITY_4K * 8); + + /* Count discarded blocks */ + ret = ram_discard_manager_replay_discarded(rdm, §ion, + count_discarded_blocks, &count); + + g_assert_cmpint(ret, ==, 0); + /* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) = 6 blocks */ + g_assert_cmpint(count, ==, 6); + + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1)); + + test_source_free(src2); + test_source_free(src1); + test_teardown(); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + module_call_init(MODULE_INIT_QOM); + type_register_static(&test_rds_info); + + g_test_add_func("/ram-discard-manager/single-source/basic", + test_single_source_basic); + g_test_add_func("/ram-discard-manager/single-source/listener", + test_single_source_listener); + g_test_add_func("/ram-discard-manager/two-sources/same-granularity", + test_two_sources_same_granularity); + g_test_add_func("/ram-discard-manager/two-sources/different-granularity", + test_two_sources_different_granularity); + g_test_add_func("/ram-discard-manager/two-sources/notification", + test_two_sources_notification); + g_test_add_func("/ram-discard-manager/dynamic/add-source-with-listener", + test_add_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/remove-source-with-listener", + test_remove_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listener", + test_readd_source_with_listener); + g_test_add_func("/ram-discard-manager/edge/duplicate-source", + test_duplicate_source); + g_test_add_func("/ram-discard-manager/edge/populate-rollback", + test_populate_rollback); + g_test_add_func("/ram-discard-manager/edge/replay-intersection", + test_replay_populated_intersection); + g_test_add_func("/ram-discard-manager/edge/no-sources", + test_no_sources); + g_test_add_func("/ram-discard-manager/multi-source/redundant-discard", + test_redundant_discard); + g_test_add_func("/ram-discard-manager/listener/partial-section", + test_partial_listener_section); + g_test_add_func("/ram-discard-manager/listener/multiple-different", + test_multiple_listeners_different_sections); + g_test_add_func("/ram-discard-manager/listener/overlapping", + test_overlapping_listener_sections); + g_test_add_func("/ram-discard-manager/edge/boundary-section", + test_boundary_section); + g_test_add_func("/ram-discard-manager/multi-source/replay-discarded", + test_replay_discarded); + + return g_test_run(); +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -XXX,XX +XXX,XX @@ if have_system 'test-bufferiszero': [], 'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'], 'test-vmstate': [migration, io], - 'test-yank': ['socket-helpers.c', qom, io, chardev] + 'test-yank': ['socket-helpers.c', qom, io, chardev], + 'test-ram-discard-manager': [ + 'test-ram-discard-manager.c', + 'test-ram-discard-manager-stubs.c', + meson.project_source_root() / 'system/ram-discard-manager.c', + genh, qemuutil, qom + ], } if config_host_data.get('CONFIG_INOTIFY1') tests += {'test-util-filemonitor': []} -- 2.54.0
From: Marc-André Lureau <marcandre.lureau@redhat.com> Most callers of ram_block_discard_range() want to discard both the shared and guest_memfd backing. Only kvm_convert_memory() intentionally discards a single plane during private/shared conversions. Rename the current implementation to ram_block_discard_shared_range() and make ram_block_discard_range() a composite that also discards guest_memfd when present (rb->guest_memfd >= 0). This ensures callers like virtio-mem, virtio-balloon, hv-balloon, migration.. reclaim private pages on discard. Update kvm_convert_memory() to use the plane-specific ram_block_discard_shared_range() since it only needs to discard the shared backing when converting to private. Likewise, after TDVF image copy, use ram_block_discard_shared_range(). Reviewed-by: Peter Xu <peterx@redhat.com> Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/r/20260604-rdm5-v5-11-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com> --- include/system/ramblock.h | 3 ++- accel/kvm/kvm-all.c | 2 +- system/physmem.c | 25 +++++++++++++++++++++---- target/i386/kvm/tdx.c | 2 +- system/trace-events | 2 +- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/include/system/ramblock.h b/include/system/ramblock.h index XXXXXXX..XXXXXXX 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -XXX,XX +XXX,XX @@ struct RamBlockAttributes { /* @offset: the offset within the RAMBlock */ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length); -/* @offset: the offset within the RAMBlock */ +int ram_block_discard_shared_range(RAMBlock *rb, uint64_t offset, + size_t length); int ram_block_discard_guest_memfd_range(RAMBlock *rb, uint64_t offset, size_t length); diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index XXXXXXX..XXXXXXX 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -XXX,XX +XXX,XX @@ int kvm_convert_memory(hwaddr start, hwaddr size, bool to_private) */ goto out_unref; } - ret = ram_block_discard_range(rb, offset, size); + ret = ram_block_discard_shared_range(rb, offset, size); } else { ret = ram_block_discard_guest_memfd_range(rb, offset, size); } diff --git a/system/physmem.c b/system/physmem.c index XXXXXXX..XXXXXXX 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -XXX,XX +XXX,XX @@ int qemu_ram_foreach_block(RAMBlockIterFunc func, void *opaque) * Returns: 0 on success, none-0 on failure * */ -int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) +int ram_block_discard_shared_range(RAMBlock *rb, uint64_t offset, size_t length) { int ret = -1; @@ -XXX,XX +XXX,XX @@ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) * have a MAP_PRIVATE mapping, possibly messing with other * MAP_PRIVATE/MAP_SHARED mappings. There is no easy way to * change that behavior whithout violating the promised - * semantics of ram_block_discard_range(). + * semantics of ram_block_discard_shared_range(). * * Only warn, because it works as long as nobody else uses that * file. @@ -XXX,XX +XXX,XX @@ int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) goto err; #endif } - trace_ram_block_discard_range(rb->idstr, host_startaddr, length, - need_madvise, need_fallocate, ret); + trace_ram_block_discard_shared_range(rb->idstr, host_startaddr, length, + need_madvise, need_fallocate, + ret); } else { error_report("%s: Overrun block '%s' (%" PRIu64 "/%zx/" RAM_ADDR_FMT")", __func__, rb->idstr, offset, length, rb->max_length); @@ -XXX,XX +XXX,XX @@ err: return ret; } +int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) +{ + int ret; + + ret = ram_block_discard_shared_range(rb, offset, length); + if (ret) { + return ret; + } + + if (rb->guest_memfd >= 0) { + ret = ram_block_discard_guest_memfd_range(rb, offset, length); + } + + return ret; +} + int ram_block_discard_guest_memfd_range(RAMBlock *rb, uint64_t offset, size_t length) { diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c index XXXXXXX..XXXXXXX 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -XXX,XX +XXX,XX @@ static void tdx_finalize_vm(Notifier *notifier, void *unused) * KVM_MEMORY_MAPPING. It becomes useless. */ ram_block = tdx_guest->tdvf_mr->ram_block; - ram_block_discard_range(ram_block, 0, ram_block->max_length); + ram_block_discard_shared_range(ram_block, 0, ram_block->max_length); tdx_vm_ioctl(KVM_TDX_FINALIZE_VM, 0, NULL, &error_fatal); CONFIDENTIAL_GUEST_SUPPORT(tdx_guest)->ready = true; diff --git a/system/trace-events b/system/trace-events index XXXXXXX..XXXXXXX 100644 --- a/system/trace-events +++ b/system/trace-events @@ -XXX,XX +XXX,XX @@ global_dirty_changed(unsigned int bitmask) "bitmask 0x%"PRIx32 address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, uint32_t attrs) "as:%p addr 0x%"PRIx64":%"PRIx64" write:%d attrs:0x%x" find_ram_offset(uint64_t size, uint64_t offset) "size: 0x%" PRIx64 " @ 0x%" PRIx64 find_ram_offset_loop(uint64_t size, uint64_t candidate, uint64_t offset, uint64_t next, uint64_t mingap) "trying size: 0x%" PRIx64 " @ 0x%" PRIx64 ", offset: 0x%" PRIx64" next: 0x%" PRIx64 " mingap: 0x%" PRIx64 -ram_block_discard_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d" +ram_block_discard_shared_range(const char *rbname, void *hva, size_t length, bool need_madvise, bool need_fallocate, int ret) "%s@%p + 0x%zx: madvise: %d fallocate: %d ret: %d" qemu_ram_alloc_shared(const char *name, size_t size, size_t max_size, int fd, void *host) "%s size %zu max_size %zu fd %d host %p" subpage_register(void *subpage, uint32_t start, uint32_t end, int idx, int eidx, uint16_t section) "subpage %p start 0x%08x end 0x%08x idx 0x%08x eidx 0x%08x section %u" -- 2.54.0