From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580713; cv=none; d=zohomail.com; s=zohoarc; b=Wiy7pe/EYKWZYyGIzTYOBkTXOp9Q1dbCdGWIaMrbjH9MyNkzQp5EAuG3y1iWRYgE5XTtP2OJEe2Hu+ZzrOdts4+Jw7cpObR/NRJhYJrQ2K4eQ2pH/FyM60adczWFTWCnRGeXHCB/oZ+K/Y61bOgSzVXexgilsQFqQu5YDL89v5o= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580713; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=JUiEUEYWyUvnWmxgveaSVKDWnBICHhQkDOOp/6LsnZM=; b=AY/RTB6avt5I2a7PfEPMaAX4ROdZ1HtIi3oAwKhGDN71chDlqNGJtfsWZDX0iHaRsq02WWjyNbeCCJ6Y2dXij/vPJX/jb/DAuK4u0LQgru7SDPxToV1fW3ApOjS3LwMk6UxCbAR8OdBt3pK8LoRHdC9VE1ph8rZcVqVSXx5HjFY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580713122445.80905830324457; Thu, 4 Jun 2026 06:45:13 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8NQ-0001Ln-1g; Thu, 04 Jun 2026 09:44:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NL-0001L9-Uf for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:47 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NF-0008QB-SN for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:47 -0400 Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-679-4n7d9OUgN-ieTCaNT-ytQg-1; Thu, 04 Jun 2026 09:44:35 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3AFEA180059A; Thu, 4 Jun 2026 13:44:33 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3C1B3195608E; Thu, 4 Jun 2026 13:44:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580680; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JUiEUEYWyUvnWmxgveaSVKDWnBICHhQkDOOp/6LsnZM=; b=fKioEXHQnieaB1eb72fWmXUNQTClvAywzOSAfbVvxfb0b4iQsPkxdY3zHEIroM+Ck1k3CC nJgT2szTmbPa6XE6bUicd30YfUY19PjdZXNaMdyMc0fd3k1bFfY45Gf6yh+mUItta5Vru8 5wkUvGLX7pPEoupv4nsKNA7m2Y6cJys= X-MC-Unique: 4n7d9OUgN-ieTCaNT-ytQg-1 X-Mimecast-MFC-AGG-ID: 4n7d9OUgN-ieTCaNT-ytQg_1780580673 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:47 +0400 Subject: [PATCH v5 01/12] system/memory: split RamDiscardManager into source and manager MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-1-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=50715; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=s7oQSEGMGUp2czr38OoZ6tjePFQFykrWz+XQOFDjQ1o=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEw4IMVV4VYiPTkofbJ/+VeCAY66i2j4nJrB vAa/pcSYNWJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMAAKCRDa6OEJdZac 5czSD/9MYXts9y61cpUIz63FHHjv9WAze0Hoa+Us+494JE9/KiP3GRlS7CoswGNt1FvfVQ5jB2k erMzR4DckYDWccr5Xnwi90tmuP/QLW4YoHFzEdq2Vr6X47g9iM5XcbzwaKCtWusoNe21NUZAL6o Jg/610EnsuVKmv029oEq5nRjlerDp9qLLxpWLy2BGMwT99OAb4ljtjUtgEDQC/nA0lzJ1XTwlHn me4saI0ykrVzt4WLLC3K+PSJernuHZ67RXDCnGkKG0r2IdjMqBzzqklENPa7gAJbsi6rSQJHeIQ pIFrJ12JaWjM7lTnEoChjtT9jbaSP8SKf0EIt7bxobYn1LMM3bywbphMw3Y1Zh+pq3jvE3CbF0n LFgxgn+ubXjDk20wZE2lYIj4Vap0tYNkKkcbm4oU3oIoqu+1XYEmH2dVED4JfiTteZbLOjQIZex +a1PKNzx0QSD+eECYp8cq5jBwdt2TK6yn4Jk+H6CKQvMMfleA/g62Kn8Q85yNZPfdL1NbKhyhsc cI2Us/pIzhD8Kqyg+VXg/xew/Azz9LXPbd0GYJjdjYEBe61PTU3+rEFhRT6cs4AdKusIPszV0ah M1cMvSKctQY9rRz/h/Oh8/iXzF3hhe2t7mKScst9cJIqoH4oKwVVo6H1mP1aDMQd2YGa6UpRo50 EC2ZrJGg/QWrPqg== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580714839158500 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 Acked-by: David Hildenbrand Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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 221cfd76bf9..5d1d19c6bec 100644 --- a/include/hw/virtio/virtio-mem.h +++ b/include/hw/virtio/virtio-mem.h @@ -118,9 +118,6 @@ struct VirtIOMEM { /* notifiers to notify when "size" changes */ NotifierList size_change_notifiers; =20 - /* 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 1417132f6d9..a37d320293a 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -54,6 +54,12 @@ typedef struct RamDiscardManager RamDiscardManager; DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass, RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER); =20 +#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, @@ -595,8 +601,8 @@ static inline void ram_discard_listener_init(RamDiscard= Listener *rdl, /** * typedef ReplayRamDiscardState: * - * The callback handler for #RamDiscardManagerClass.replay_populated/ - * #RamDiscardManagerClass.replay_discarded to invoke on populated/discard= ed + * The callback handler for #RamDiscardSourceClass.replay_populated/ + * #RamDiscardSourceClass.replay_discarded to invoke on populated/discarded * parts. * * @section: the #MemoryRegionSection of populated/discarded part @@ -608,40 +614,17 @@ typedef int (*ReplayRamDiscardState)(MemoryRegionSect= ion *section, void *opaque); =20 /* - * RamDiscardManagerClass: - * - * A #RamDiscardManager coordinates which parts of specific RAM #MemoryReg= ion - * 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 t= o not - * access unplugged (discarded) memory - especially via DMA. virtio-mem wi= ll - * 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 minim= um - * granularity within the #MemoryRegion. Listeners have to prepare for mem= ory - * 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; =20 @@ -651,47 +634,47 @@ 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); =20 /** * @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); =20 /** * @replay_populated: * * Call the #ReplayRamDiscardState callback for all populated parts wi= thin - * 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 faile= d. */ - int (*replay_populated)(const RamDiscardManager *rdm, + int (*replay_populated)(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); =20 @@ -699,50 +682,60 @@ struct RamDiscardManagerClass { * @replay_discarded: * * Call the #ReplayRamDiscardState callback for all discarded parts wi= thin - * 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 faile= d. */ - int (*replay_discarded)(const RamDiscardManager *rdm, + int (*replay_discarded)(const RamDiscardSource *rds, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); +}; =20 - /** - * @register_listener: - * - * Register a #RamDiscardListener for the given #MemoryRegionSection a= nd - * immediately notify the #RamDiscardListener about all populated parts - * within the #MemoryRegionSection via the #RamDiscardManager. - * - * In case any notification fails, no further notifications are trigge= red - * 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 #MemoryReg= ion + * 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 t= o not + * access unplugged (discarded) memory - especially via DMA. virtio-mem wi= ll + * 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 minim= um + * granularity within the #MemoryRegion. Listeners have to prepare for mem= ory + * becoming discarded in a different granularity than it was populated and= the + * other way around. + */ +struct RamDiscardManager { + Object parent; =20 - /** - * @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; }; =20 uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *= rdm, @@ -754,8 +747,8 @@ bool ram_discard_manager_is_populated(const RamDiscardM= anager *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 @@ -772,8 +765,8 @@ int ram_discard_manager_replay_populated(const RamDisca= rdManager *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 @@ -794,6 +787,34 @@ void ram_discard_manager_register_listener(RamDiscardM= anager *rdm, void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl); =20 +/* + * Note: later refactoring should take the source into account and the man= ager + * 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 ma= nager + * 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 man= ager + * 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 man= ager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm); + /** * memory_translate_iotlb: Extract addresses from a TLB entry. * Called with rcu_read_lock held. @@ -2486,18 +2507,22 @@ static inline bool memory_region_has_ram_discard_ma= nager(MemoryRegion *mr) } =20 /** - * 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 #MemoryR= egion - * 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, RamDiscardSourc= e *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, RamDiscardSour= ce *source); =20 /** * memory_region_find: translate an address/size relative to a diff --git a/include/system/ramblock.h b/include/system/ramblock.h index 4435f8d55fe..2b38718fe5f 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -99,8 +99,6 @@ struct RamBlockAttributes { /* 1-setting of the bitmap represents ram is populated (shared) */ unsigned bitmap_size; unsigned long *bitmap; - - QLIST_HEAD(, RamDiscardListener) rdl_list; }; =20 /* @offset: the offset within the RAMBlock */ diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index a4b71974a1c..be149ee9441 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -16,6 +16,7 @@ #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" @@ -324,74 +325,31 @@ static int virtio_mem_for_each_unplugged_section(cons= t VirtIOMEM *vmem, return ret; } =20 -static int virtio_mem_notify_populate_cb(MemoryRegionSection *s, void *arg) -{ - RamDiscardListener *rdl =3D 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 =3D memory_region_get_ram_discard_manager(&vmem= ->memdev->mr); =20 - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp =3D *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); } =20 static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl, *rdl2; - int ret =3D 0; - - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - MemoryRegionSection tmp =3D *rdl->section; + RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(&vmem= ->memdev->mr); =20 - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret =3D rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } - - if (ret) { - /* Notify all already-notified listeners. */ - QLIST_FOREACH(rdl2, &vmem->rdl_list, next) { - MemoryRegionSection tmp =3D *rdl2->section; - - if (rdl2 =3D=3D 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); } =20 static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) { - RamDiscardListener *rdl; + RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(&vmem= ->memdev->mr); =20 if (!vmem->size) { return; } =20 - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - rdl->notify_discard(rdl, rdl->section); - } + ram_discard_manager_notify_discard_all(rdm); } =20 static bool virtio_mem_is_range_plugged(const VirtIOMEM *vmem, @@ -1037,13 +995,9 @@ static void virtio_mem_device_realize(DeviceState *de= v, Error **errp) return; } =20 - /* - * 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; } @@ -1062,7 +1016,8 @@ static void virtio_mem_device_realize(DeviceState *de= v, Error **errp) ret =3D 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; } @@ -1147,7 +1102,7 @@ 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_SO= URCE(vmem)); ram_block_coordinated_discard_require(false); } =20 @@ -1175,9 +1130,7 @@ static int virtio_mem_activate_memslot_range_cb(VirtI= OMEM *vmem, void *arg, =20 static int virtio_mem_post_load_bitmap(VirtIOMEM *vmem) { - RamDiscardListener *rdl; - int ret; - + RamDiscardManager *rdm =3D 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 blo= cks. @@ -1195,14 +1148,7 @@ static int virtio_mem_post_load_bitmap(VirtIOMEM *vm= em) * We started out with all memory discarded and our memory region is m= apped * into an address space. Replay, now that we updated the bitmap. */ - QLIST_FOREACH(rdl, &vmem->rdl_list, next) { - ret =3D virtio_mem_for_each_plugged_section(vmem, rdl->section, rd= l, - virtio_mem_notify_populat= e_cb); - if (ret) { - return ret; - } - } - return 0; + return ram_discard_manager_replay_populated_to_listeners(rdm); } =20 static int virtio_mem_post_load(void *opaque, int version_id) @@ -1650,7 +1596,6 @@ static void virtio_mem_instance_init(Object *obj) VirtIOMEM *vmem =3D VIRTIO_MEM(obj); =20 notifier_list_init(&vmem->size_change_notifiers); - QLIST_INIT(&vmem->rdl_list); =20 object_property_add(obj, VIRTIO_MEM_SIZE_PROP, "size", virtio_mem_get_= size, NULL, NULL, NULL); @@ -1694,19 +1639,19 @@ static const Property virtio_mem_legacy_guests_prop= erties[] =3D { unplugged_inaccessible, ON_OFF_AUTO_ON), }; =20 -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 =3D VIRTIO_MEM(rdm); + const VirtIOMEM *vmem =3D VIRTIO_MEM(rds); =20 g_assert(mr =3D=3D &vmem->memdev->mr); return vmem->block_size; } =20 -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 =3D VIRTIO_MEM(rdm); + const VirtIOMEM *vmem =3D VIRTIO_MEM(rds); uint64_t start_gpa =3D vmem->addr + s->offset_within_region; uint64_t end_gpa =3D start_gpa + int128_get64(s->size); =20 @@ -1727,19 +1672,19 @@ struct VirtIOMEMReplayData { void *opaque; }; =20 -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 =3D arg; =20 return data->fn(s, data->opaque); } =20 -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 =3D VIRTIO_MEM(rdm); + const VirtIOMEM *vmem =3D VIRTIO_MEM(rds); struct VirtIOMEMReplayData data =3D { .fn =3D replay_fn, .opaque =3D opaque, @@ -1747,23 +1692,15 @@ static int virtio_mem_rdm_replay_populated(const Ra= mDiscardManager *rdm, =20 g_assert(s->mr =3D=3D &vmem->memdev->mr); return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rdm_replay_populate= d_cb); -} - -static int virtio_mem_rdm_replay_discarded_cb(MemoryRegionSection *s, - void *arg) -{ - struct VirtIOMEMReplayData *data =3D arg; - - return data->fn(s, data->opaque); + virtio_mem_rds_replay_cb); } =20 -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 =3D VIRTIO_MEM(rdm); + const VirtIOMEM *vmem =3D VIRTIO_MEM(rds); struct VirtIOMEMReplayData data =3D { .fn =3D replay_fn, .opaque =3D opaque, @@ -1771,41 +1708,7 @@ static int virtio_mem_rdm_replay_discarded(const Ram= DiscardManager *rdm, =20 g_assert(s->mr =3D=3D &vmem->memdev->mr); return virtio_mem_for_each_unplugged_section(vmem, s, &data, - virtio_mem_rdm_replay_dis= carded_cb); -} - -static void virtio_mem_rdm_register_listener(RamDiscardManager *rdm, - RamDiscardListener *rdl, - MemoryRegionSection *s) -{ - VirtIOMEM *vmem =3D VIRTIO_MEM(rdm); - int ret; - - g_assert(s->mr =3D=3D &vmem->memdev->mr); - rdl->section =3D memory_region_section_new_copy(s); - - QLIST_INSERT_HEAD(&vmem->rdl_list, rdl, next); - ret =3D virtio_mem_for_each_plugged_section(vmem, rdl->section, rdl, - virtio_mem_notify_populate_c= b); - 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 =3D VIRTIO_MEM(rdm); - - g_assert(rdl->section->mr =3D=3D &vmem->memdev->mr); - if (vmem->size) { - rdl->notify_discard(rdl, rdl->section); - } - - memory_region_section_free_copy(rdl->section); - rdl->section =3D NULL; - QLIST_REMOVE(rdl, next); + virtio_mem_rds_replay_cb); } =20 static void virtio_mem_unplug_request_check(VirtIOMEM *vmem, Error **errp) @@ -1837,7 +1740,7 @@ static void virtio_mem_class_init(ObjectClass *klass,= const void *data) DeviceClass *dc =3D DEVICE_CLASS(klass); VirtioDeviceClass *vdc =3D VIRTIO_DEVICE_CLASS(klass); VirtIOMEMClass *vmc =3D VIRTIO_MEM_CLASS(klass); - RamDiscardManagerClass *rdmc =3D RAM_DISCARD_MANAGER_CLASS(klass); + RamDiscardSourceClass *rdsc =3D RAM_DISCARD_SOURCE_CLASS(klass); =20 device_class_set_props(dc, virtio_mem_properties); if (virtio_mem_has_legacy_guests()) { @@ -1861,12 +1764,10 @@ static void virtio_mem_class_init(ObjectClass *klas= s, const void *data) vmc->remove_size_change_notifier =3D virtio_mem_remove_size_change_not= ifier; vmc->unplug_request_check =3D virtio_mem_unplug_request_check; =20 - rdmc->get_min_granularity =3D virtio_mem_rdm_get_min_granularity; - rdmc->is_populated =3D virtio_mem_rdm_is_populated; - rdmc->replay_populated =3D virtio_mem_rdm_replay_populated; - rdmc->replay_discarded =3D virtio_mem_rdm_replay_discarded; - rdmc->register_listener =3D virtio_mem_rdm_register_listener; - rdmc->unregister_listener =3D virtio_mem_rdm_unregister_listener; + rdsc->get_min_granularity =3D virtio_mem_rds_get_min_granularity; + rdsc->is_populated =3D virtio_mem_rds_is_populated; + rdsc->replay_populated =3D virtio_mem_rds_replay_populated; + rdsc->replay_discarded =3D virtio_mem_rds_replay_discarded; } =20 static const TypeInfo virtio_mem_info =3D { @@ -1878,7 +1779,7 @@ static const TypeInfo virtio_mem_info =3D { .class_init =3D virtio_mem_class_init, .class_size =3D sizeof(VirtIOMEMClass), .interfaces =3D (const InterfaceInfo[]) { - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { } }, }; diff --git a/system/memory.c b/system/memory.c index 739ba11da6f..0ef40912991 100644 --- a/system/memory.c +++ b/system/memory.c @@ -2043,34 +2043,88 @@ RamDiscardManager *memory_region_get_ram_discard_ma= nager(MemoryRegion *mr) return mr->rdm; } =20 -int memory_region_set_ram_discard_manager(MemoryRegion *mr, - RamDiscardManager *rdm) +static RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, + RamDiscardSource *rds) +{ + RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DIS= CARD_MANAGER)); + + rdm->rds =3D rds; + rdm->mr =3D 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; } =20 - mr->rdm =3D rdm; + mr->rdm =3D ram_discard_manager_new(mr, RAM_DISCARD_SOURCE(source)); return 0; } =20 +void memory_region_del_ram_discard_source(MemoryRegion *mr, + RamDiscardSource *source) +{ + g_assert(mr->rdm->rds =3D=3D source); + + object_unref(mr->rdm); + mr->rdm =3D NULL; +} + +static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSou= rce *rds, + const MemoryRegion = *mr) +{ + RamDiscardSourceClass *rdsc =3D 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 *sec= tion) +{ + RamDiscardSourceClass *rdsc =3D 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 *sectio= n, + ReplayRamDiscardState repla= y_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc =3D 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 *sectio= n, + ReplayRamDiscardState repla= y_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc =3D 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 =3D 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); } =20 bool ram_discard_manager_is_populated(const RamDiscardManager *rdm, const MemoryRegionSection *section) { - RamDiscardManagerClass *rdmc =3D 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); } =20 int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, @@ -2078,10 +2132,7 @@ int ram_discard_manager_replay_populated(const RamDi= scardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - RamDiscardManagerClass *rdmc =3D 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_f= n, opaque); } =20 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -2089,29 +2140,133 @@ int ram_discard_manager_replay_discarded(const Ram= DiscardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - RamDiscardManagerClass *rdmc =3D RAM_DISCARD_MANAGER_GET_CLASS(rdm); + return ram_discard_source_replay_discarded(rdm->rds, section, replay_f= n, opaque); +} + +static void ram_discard_manager_initfn(Object *obj) +{ + RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(obj); + + QLIST_INIT(&rdm->rdl_list); +} + +static void ram_discard_manager_finalize(Object *obj) +{ + RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(obj); =20 - 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 =3D 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp =3D *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + ret =3D 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 =3D *rdl2->section; + + if (rdl2 =3D=3D 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 =3D *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 =3D opaque; + + return rdl->notify_populate(rdl, section); } =20 void ram_discard_manager_register_listener(RamDiscardManager *rdm, RamDiscardListener *rdl, MemoryRegionSection *section) { - RamDiscardManagerClass *rdmc =3D RAM_DISCARD_MANAGER_GET_CLASS(rdm); + int ret; + + g_assert(section->mr =3D=3D rdm->mr); + + rdl->section =3D memory_region_section_new_copy(section); + QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); =20 - g_assert(rdmc->register_listener); - rdmc->register_listener(rdm, rdl, section); + ret =3D 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)); + } } =20 void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl) { - RamDiscardManagerClass *rdmc =3D RAM_DISCARD_MANAGER_GET_CLASS(rdm); + g_assert(rdl->section); + g_assert(rdl->section->mr =3D=3D rdm->mr); + + rdl->notify_discard(rdl, rdl->section); + memory_region_section_free_copy(rdl->section); + rdl->section =3D NULL; + QLIST_REMOVE(rdl, next); +} + +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm) +{ + RamDiscardListener *rdl; + int ret =3D 0; =20 - g_assert(rdmc->unregister_listener); - rdmc->unregister_listener(rdm, rdl); + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret =3D ram_discard_source_replay_populated(rdm->rds, rdl->section, + rdm_populate_cb, rdl); + if (ret) { + break; + } + } + return ret; } =20 /* Called with rcu_read_lock held. */ @@ -3744,9 +3899,17 @@ static const TypeInfo iommu_memory_region_info =3D { }; =20 static const TypeInfo ram_discard_manager_info =3D { - .parent =3D TYPE_INTERFACE, + .parent =3D TYPE_OBJECT, .name =3D TYPE_RAM_DISCARD_MANAGER, - .class_size =3D sizeof(RamDiscardManagerClass), + .instance_size =3D sizeof(RamDiscardManager), + .instance_init =3D ram_discard_manager_initfn, + .instance_finalize =3D ram_discard_manager_finalize, +}; + +static const TypeInfo ram_discard_source_info =3D { + .parent =3D TYPE_INTERFACE, + .name =3D TYPE_RAM_DISCARD_SOURCE, + .class_size =3D sizeof(RamDiscardSourceClass), }; =20 static void memory_register_types(void) @@ -3754,6 +3917,7 @@ 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); } =20 type_init(memory_register_types) diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index 630b0fda126..a72924eea7d 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -18,7 +18,7 @@ OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RamBlockAttribu= tes, ram_block_attributes, RAM_BLOCK_ATTRIBUTES, OBJECT, - { TYPE_RAM_DISCARD_MANAGER }, + { TYPE_RAM_DISCARD_SOURCE }, { }) =20 static size_t @@ -32,35 +32,9 @@ ram_block_attributes_get_block_size(void) return qemu_real_host_page_size(); } =20 - -static bool -ram_block_attributes_rdm_is_populated(const RamDiscardManager *rdm, - const MemoryRegionSection *section) -{ - const RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rdm); - const size_t block_size =3D ram_block_attributes_get_block_size(); - const uint64_t first_bit =3D section->offset_within_region / block_siz= e; - const uint64_t last_bit =3D - first_bit + int128_get64(section->size) / block_size - 1; - unsigned long first_discarded_bit; - - first_discarded_bit =3D 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); =20 -static int -ram_block_attributes_notify_populate_cb(MemoryRegionSection *section, - void *arg) -{ - RamDiscardListener *rdl =3D arg; - - return rdl->notify_populate(rdl, section); -} - static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *= attr, MemoryRegionSection *secti= on, @@ -144,93 +118,73 @@ ram_block_attributes_for_each_discarded_section(const= RamBlockAttributes *attr, return ret; } =20 -static uint64_t -ram_block_attributes_rdm_get_min_granularity(const RamDiscardManager *rdm, - const MemoryRegion *mr) -{ - const RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rdm); =20 - g_assert(mr =3D=3D attr->ram_block->mr); - return ram_block_attributes_get_block_size(); -} +typedef struct RamBlockAttributesReplayData { + ReplayRamDiscardState fn; + void *opaque; +} RamBlockAttributesReplayData; =20 -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 =3D RAM_BLOCK_ATTRIBUTES(rdm); - int ret; - - g_assert(section->mr =3D=3D attr->ram_block->mr); - rdl->section =3D memory_region_section_new_copy(section); - - QLIST_INSERT_HEAD(&attr->rdl_list, rdl, next); + RamBlockAttributesReplayData *data =3D arg; =20 - ret =3D ram_block_attributes_for_each_populated_section(attr, section,= rdl, - ram_block_attributes_notify_populate_c= b); - if (ret) { - error_report("%s: Failed to register RAM discard listener: %s", - __func__, strerror(-ret)); - exit(1); - } + return data->fn(section, data->opaque); } =20 -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 =3D RAM_BLOCK_ATTRIBUTES(rdm); + const RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); =20 - g_assert(rdl->section); - g_assert(rdl->section->mr =3D=3D attr->ram_block->mr); - - rdl->notify_discard(rdl, rdl->section); - - memory_region_section_free_copy(rdl->section); - rdl->section =3D NULL; - QLIST_REMOVE(rdl, next); + g_assert(mr =3D=3D attr->ram_block->mr); + return ram_block_attributes_get_block_size(); } =20 -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 =3D arg; + const RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); + const size_t block_size =3D ram_block_attributes_get_block_size(); + const uint64_t first_bit =3D section->offset_within_region / block_siz= e; + const uint64_t last_bit =3D + first_bit + int128_get64(section->size) / block_size - 1; + unsigned long first_discarded_bit; =20 - return data->fn(section, data->opaque); + first_discarded_bit =3D find_next_zero_bit(attr->bitmap, last_bit + 1, + first_bit); + return first_discarded_bit > last_bit; } =20 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 =3D RAM_BLOCK_ATTRIBUTES(rdm); + RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); RamBlockAttributesReplayData data =3D { .fn =3D replay_fn, .opaque =3D= opaque }; =20 g_assert(section->mr =3D=3D attr->ram_block->mr); return ram_block_attributes_for_each_populated_section(attr, section, = &data, - ram_block_attributes_rdm_repla= y_cb); + ram_block_attributes_rds_replay_cb); } =20 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 =3D RAM_BLOCK_ATTRIBUTES(rdm); + RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); RamBlockAttributesReplayData data =3D { .fn =3D replay_fn, .opaque =3D= opaque }; =20 g_assert(section->mr =3D=3D attr->ram_block->mr); return ram_block_attributes_for_each_discarded_section(attr, section, = &data, - ram_block_attributes_rdm_repla= y_cb); + ram_block_attributes_rds_replay_cb); } =20 static bool @@ -257,42 +211,23 @@ ram_block_attributes_is_valid_range(RamBlockAttribute= s *attr, uint64_t offset, return true; } =20 -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 =3D memory_region_get_ram_discard_manager(attr-= >ram_block->mr); =20 - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp =3D *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); } =20 static int ram_block_attributes_notify_populate(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { - RamDiscardListener *rdl; - int ret =3D 0; - - QLIST_FOREACH(rdl, &attr->rdl_list, next) { - MemoryRegionSection tmp =3D *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret =3D rdl->notify_populate(rdl, &tmp); - if (ret) { - break; - } - } + RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(attr-= >ram_block->mr); =20 - return ret; + return ram_discard_manager_notify_populate(rdm, offset, size); } =20 int ram_block_attributes_state_change(RamBlockAttributes *attr, @@ -376,7 +311,8 @@ RamBlockAttributes *ram_block_attributes_create(RAMBloc= k *ram_block) attr =3D RAM_BLOCK_ATTRIBUTES(object_new(TYPE_RAM_BLOCK_ATTRIBUTES)); =20 attr->ram_block =3D 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; } @@ -391,15 +327,12 @@ void ram_block_attributes_destroy(RamBlockAttributes = *attr) g_assert(attr); =20 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)); } =20 static void ram_block_attributes_init(Object *obj) { - RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(obj); - - QLIST_INIT(&attr->rdl_list); } =20 static void ram_block_attributes_finalize(Object *obj) @@ -409,12 +342,10 @@ static void ram_block_attributes_finalize(Object *obj) static void ram_block_attributes_class_init(ObjectClass *klass, const void *data) { - RamDiscardManagerClass *rdmc =3D RAM_DISCARD_MANAGER_CLASS(klass); - - rdmc->get_min_granularity =3D ram_block_attributes_rdm_get_min_granula= rity; - rdmc->register_listener =3D ram_block_attributes_rdm_register_listener; - rdmc->unregister_listener =3D ram_block_attributes_rdm_unregister_list= ener; - rdmc->is_populated =3D ram_block_attributes_rdm_is_populated; - rdmc->replay_populated =3D ram_block_attributes_rdm_replay_populated; - rdmc->replay_discarded =3D ram_block_attributes_rdm_replay_discarded; + RamDiscardSourceClass *rdsc =3D RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity =3D ram_block_attributes_rds_get_min_granula= rity; + rdsc->is_populated =3D ram_block_attributes_rds_is_populated; + rdsc->replay_populated =3D ram_block_attributes_rds_replay_populated; + rdsc->replay_discarded =3D ram_block_attributes_rds_replay_discarded; } --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580765; cv=none; d=zohomail.com; s=zohoarc; b=BNvFr0c28FqBWrl5izU30Op0p3RKtLdlEosoglt36/F1b8LGgSNHcfe0sNDV6FtvrwQqDVSO6ILS86hPgm7CnIdeBsfuynPiDbEViC/cgQnSVm6oZjkMtFgH1t5tPThNSKhnkbWHhlgVB7CHweaEKiql/GPJyOABkRTYAYtaTsY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580765; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=xFd4jROmIbcszxTicjd4VphQ6CR2Ez/I+Q+uIQbi2DA=; b=B7KrzkTTgoFYcbCHjfpC66IcrS3gqnF5rPyP1BT6HIq7hrA9DnGORabQNLlnFqFnYdblrBbZrccjaIsFY/Q9k+wuYJHGtjqO5fwfhBGU8RJL87xpwsM5ntHQVYL22Q+V5UsLRyPiYptOcpdfTn14wDxoYlLe1PpN4FuzVvNIToI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580765894907.6279823715752; Thu, 4 Jun 2026 06:46:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Np-0001Ss-8z; Thu, 04 Jun 2026 09:45:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NQ-0001Lv-0O for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:54 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NK-0008R7-OZ for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:51 -0400 Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-231-YDgrq206MU2Nie6F1R-cIw-1; Thu, 04 Jun 2026 09:44:42 -0400 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 12047180058A; Thu, 4 Jun 2026 13:44:40 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 21E3B1955BC0; Thu, 4 Jun 2026 13:44:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580686; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=xFd4jROmIbcszxTicjd4VphQ6CR2Ez/I+Q+uIQbi2DA=; b=WkY2E+IRb2BSbUzZXw3eISykfLQ7XJ6hOgic9XJRch+vK8hOWmD0x4VFncZ0kw3yZeKKK1 BZPnBXtcfVo9vF+pW8TFandps3y6jyKxbOzwajwzx0ZOutU5Mm8+F0aAc8UKobjufIhbAn n3tGwL7Y/pzu52/cYsh8w+W7K9AfA0s= X-MC-Unique: YDgrq206MU2Nie6F1R-cIw-1 X-Mimecast-MFC-AGG-ID: YDgrq206MU2Nie6F1R-cIw_1780580680 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:48 +0400 Subject: [PATCH v5 02/12] system/memory: move RamDiscardManager to separate compilation unit MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-2-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=43849; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=b9d5YGfdXx8FwSiGWFW5ICNXMqFKxEthKrYcXItyGHs=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEw3qvrXftEn0YCGDQ3V8y1SAYW3e+pXXEz5 fGSOBcPMBCJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMAAKCRDa6OEJdZac 5ZpvEACIvBICkZ7wwoQi499/yRlA7F4lDaZMmpp2Y8pr1lmfTenduL+7VbqmsdcrVRpf/eMgLmT XCP+c12Wjslve/5UQP+49bA+6fvDIt1NfbTZIiiefayyGmV+/oMgQx8P5RxanB3nE01bEJRG0f2 iGdHvv6LTnbbTmkuVMVeHip7+I2TJ+XIkPs3HQwKntobVHpPjYGdS1A9zUAVWT8BHYUz74tEpAa yMLQCBwOLU0DiYdEz54akk5TrfO+qX/ppT52+F86beab+xGjDsrtAEPWHZNlKJYYzOl+dJorV6C SWVZwU7b7X66xOFKtm4r6dNGEfQl6zeBmaJ9+hTkiMHOPvef2JNMuw/S5GBOznCbMsqHydPyV6z +YPtfII0Q2PlivOKY/9QuGt40Y7kYsPebsKQo3BWjC1W9+ardwMnXaawL7mF2DiSa09HFPiwBI7 rxdjOKXDXmly6R3dPAvcdz4SC9Oj94WRLDmB6jDkhYwjkiO2epYCd5RDM/zhgB9LGr7nAjD1aLx kgi40TrYznZDYD33SBFbJ/OPEJwweTskvLpJ4giEIJglDChrZZT+l9d43bx4sr1x6T9qgB6XfwO Cp8GL485vPRwJdT1km18qzOpvNuaSKwGW+Bq8gcmDHxje0ObLWhPmtMZ1MIyw5Z26qGZ6+Agnqq XT5xZkMBXN26bhw== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580767061158500 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 Acked-by: David Hildenbrand Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7752917d8cf..25dbb9f1a77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3346,6 +3346,7 @@ 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 @@ -3356,6 +3357,7 @@ 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 =20 Memory devices diff --git a/include/system/memory.h b/include/system/memory.h index a37d320293a..28a75dac4ae 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -16,6 +16,7 @@ =20 #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" @@ -48,18 +49,6 @@ typedef struct IOMMUMemoryRegionClass IOMMUMemoryRegionC= lass; DECLARE_OBJ_CHECKERS(IOMMUMemoryRegion, IOMMUMemoryRegionClass, IOMMU_MEMORY_REGION, TYPE_IOMMU_MEMORY_REGION) =20 -#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, @@ -548,273 +537,6 @@ struct IOMMUMemoryRegionClass { int (*num_indexes)(IOMMUMemoryRegion *iommu); }; =20 -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 popul= ated. - * 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 granul= arity - * unless it would exceed the registered section. - * - * Returns 0 on success. If the notification is rejected by the listen= er, - * an error is returned. - */ - NotifyRamPopulate notify_populate; - - /* - * @notify_discard: - * - * Notification that previously populated memory was discarded success= fully - * 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 granul= arity - * 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 =3D populate_fn; - rdl->notify_discard =3D 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 wi= thin - * 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 faile= d. - */ - int (*replay_populated)(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @replay_discarded: - * - * Call the #ReplayRamDiscardState callback for all discarded parts wi= thin - * 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 faile= d. - */ - int (*replay_discarded)(const RamDiscardSource *rds, - MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); -}; - -/** - * RamDiscardManager: - * - * A #RamDiscardManager coordinates which parts of specific RAM #MemoryReg= ion - * 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 t= o not - * access unplugged (discarded) memory - especially via DMA. virtio-mem wi= ll - * 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 minim= um - * granularity within the #MemoryRegion. Listeners have to prepare for mem= ory - * 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 man= ager - * 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 ma= nager - * 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 man= ager - * 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 man= ager - * should be able to aggregate multiple sources. - */ -int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm); - /** * 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-disc= ard-manager.h new file mode 100644 index 00000000000..da55658169f --- /dev/null +++ b/include/system/ram-discard-manager.h @@ -0,0 +1,297 @@ +/* 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 popul= ated. + * 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 granul= arity + * unless it would exceed the registered section. + * + * Returns 0 on success. If the notification is rejected by the listen= er, + * an error is returned. + */ + NotifyRamPopulate notify_populate; + + /* + * @notify_discard: + * + * Notification that previously populated memory was discarded success= fully + * 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 granul= arity + * 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 =3D populate_fn; + rdl->notify_discard =3D 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 wi= thin + * 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 faile= d. + */ + int (*replay_populated)(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, void *opaque); + + /** + * @replay_discarded: + * + * Call the #ReplayRamDiscardState callback for all discarded parts wi= thin + * 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 faile= d. + */ + int (*replay_discarded)(const RamDiscardSource *rds, + MemoryRegionSection *section, + ReplayRamDiscardState replay_fn, void *opaque); +}; + +/** + * RamDiscardManager: + * + * A #RamDiscardManager coordinates which parts of specific RAM #MemoryReg= ion + * 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 t= o not + * access unplugged (discarded) memory - especially via DMA. virtio-mem wi= ll + * 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 minim= um + * granularity within the #MemoryRegion. Listeners have to prepare for mem= ory + * 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 man= ager + * 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 man= ager + * 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 man= ager + * 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 man= ager + * should be able to aggregate multiple sources. + */ +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm); + +#endif /* RAM_DISCARD_MANAGER_H */ diff --git a/system/memory.c b/system/memory.c index 0ef40912991..243e69abd75 100644 --- a/system/memory.c +++ b/system/memory.c @@ -2043,17 +2043,6 @@ RamDiscardManager *memory_region_get_ram_discard_man= ager(MemoryRegion *mr) return mr->rdm; } =20 -static RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, - RamDiscardSource *rds) -{ - RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DIS= CARD_MANAGER)); - - rdm->rds =3D rds; - rdm->mr =3D mr; - QLIST_INIT(&rdm->rdl_list); - return rdm; -} - int memory_region_add_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { @@ -2075,200 +2064,6 @@ void memory_region_del_ram_discard_source(MemoryReg= ion *mr, mr->rdm =3D NULL; } =20 -static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSou= rce *rds, - const MemoryRegion = *mr) -{ - RamDiscardSourceClass *rdsc =3D 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 *sec= tion) -{ - RamDiscardSourceClass *rdsc =3D 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 *sectio= n, - ReplayRamDiscardState repla= y_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc =3D 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 *sectio= n, - ReplayRamDiscardState repla= y_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc =3D 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_f= n, 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_f= n, opaque); -} - -static void ram_discard_manager_initfn(Object *obj) -{ - RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(obj); - - QLIST_INIT(&rdm->rdl_list); -} - -static void ram_discard_manager_finalize(Object *obj) -{ - RamDiscardManager *rdm =3D 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 =3D 0; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp =3D *rdl->section; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret =3D 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 =3D *rdl2->section; - - if (rdl2 =3D=3D 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 =3D *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 =3D 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 =3D=3D rdm->mr); - - rdl->section =3D memory_region_section_new_copy(section); - QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); - - ret =3D 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 =3D=3D rdm->mr); - - rdl->notify_discard(rdl, rdl->section); - memory_region_section_free_copy(rdl->section); - rdl->section =3D NULL; - QLIST_REMOVE(rdl, next); -} - -int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm) -{ - RamDiscardListener *rdl; - int ret =3D 0; - - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - ret =3D 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) @@ -3898,26 +3693,10 @@ static const TypeInfo iommu_memory_region_info =3D { .abstract =3D true, }; =20 -static const TypeInfo ram_discard_manager_info =3D { - .parent =3D TYPE_OBJECT, - .name =3D TYPE_RAM_DISCARD_MANAGER, - .instance_size =3D sizeof(RamDiscardManager), - .instance_init =3D ram_discard_manager_initfn, - .instance_finalize =3D ram_discard_manager_finalize, -}; - -static const TypeInfo ram_discard_source_info =3D { - .parent =3D TYPE_INTERFACE, - .name =3D TYPE_RAM_DISCARD_SOURCE, - .class_size =3D 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); } =20 type_init(memory_register_types) diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c new file mode 100644 index 00000000000..3d8c85617d7 --- /dev/null +++ b/system/ram-discard-manager.c @@ -0,0 +1,240 @@ +/* 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 RamDiscardSou= rce *rds, + const MemoryRegion = *mr) +{ + RamDiscardSourceClass *rdsc =3D 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 *sec= tion) +{ + RamDiscardSourceClass *rdsc =3D 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 *sectio= n, + ReplayRamDiscardState repla= y_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc =3D 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 *sectio= n, + ReplayRamDiscardState repla= y_fn, + void *opaque) +{ + RamDiscardSourceClass *rdsc =3D 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 =3D RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); + rdm->rds =3D rds; + rdm->mr =3D 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 =3D RAM_DISCARD_MANAGER(obj); + + QLIST_INIT(&rdm->rdl_list); +} + +static void ram_discard_manager_finalize(Object *obj) +{ + RamDiscardManager *rdm =3D 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 =3D 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + MemoryRegionSection tmp =3D *rdl->section; + + if (!memory_region_section_intersect_range(&tmp, offset, size)) { + continue; + } + ret =3D 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 =3D *rdl2->section; + + if (rdl2 =3D=3D 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 =3D *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 =3D 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 =3D=3D rdm->mr); + + rdl->section =3D memory_region_section_new_copy(section); + QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); + + ret =3D 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 =3D=3D rdm->mr); + + rdl->notify_discard(rdl, rdl->section); + memory_region_section_free_copy(rdl->section); + rdl->section =3D NULL; + QLIST_REMOVE(rdl, next); +} + +int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm) +{ + RamDiscardListener *rdl; + int ret =3D 0; + + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { + ret =3D 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 =3D { + .parent =3D TYPE_OBJECT, + .name =3D TYPE_RAM_DISCARD_MANAGER, + .instance_size =3D sizeof(RamDiscardManager), + .instance_init =3D ram_discard_manager_initfn, + .instance_finalize =3D ram_discard_manager_finalize, +}; + +static const TypeInfo ram_discard_source_info =3D { + .parent =3D TYPE_INTERFACE, + .name =3D TYPE_RAM_DISCARD_SOURCE, + .class_size =3D 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 022fe65dd83..30adf683c35 100644 --- a/rust/bindings/system-sys/lib.rs +++ b/rust/bindings/system-sys/lib.rs @@ -20,7 +20,7 @@ =20 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}; =20 #[cfg(MESON)] diff --git a/system/meson.build b/system/meson.build index 9cdfe1b3e75..cd3193d170b 100644 --- a/system/meson.build +++ b/system/meson.build @@ -14,6 +14,7 @@ system_ss.add(files( 'globals.c', 'ioport.c', 'ram-block-attributes.c', + 'ram-discard-manager.c', 'memory_mapping.c', 'memory.c', 'physmem.c', --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580753; cv=none; d=zohomail.com; s=zohoarc; b=bP3QNguCMDLbVmWdqomCMPIM+0JA/GXncUnj50imaEJPHf9gmL9O3hU0bs/+N8U7LbFmkivDxGnBtJHLzaar5SUSRrvLczYyE6p2YMEs4pziecZmJyxILRXaSqVgP5kwf2EVXXgRrDEAXmMdQcI80uCo3581Csrnxs8TNwJvIQU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580753; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=6UWN+04UBMj15nlZKWF5kruPALXdBmOnKOzox9aoPUg=; b=ed2Zr5A6qEervRuzpDC/N3pR7fudrELF2Gan3bBZ91vihGqCm6UMPiNEgGrL6h79+meoBjDaaaDMyXwDT2Wzw7iQYLqOXKVGyOfFTIIQQ4ZwDzLyoXFLA2wDRUqe4ItxJScUHMsWSa8SlDsoz9yZFLuM5USxwtTfaCOqmJp//RU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580753083415.2697130861342; Thu, 4 Jun 2026 06:45:53 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Np-0001Qi-67; Thu, 04 Jun 2026 09:45:17 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NR-0001MA-Ha for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:54 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NO-0008Rc-NI for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:53 -0400 Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-244-4NHMaoLeO_2EQx76GK54oQ-1; Thu, 04 Jun 2026 09:44:47 -0400 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id A6EAF1956096; Thu, 4 Jun 2026 13:44:45 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1D0F51955BC1; Thu, 4 Jun 2026 13:44:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580689; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6UWN+04UBMj15nlZKWF5kruPALXdBmOnKOzox9aoPUg=; b=I0uvVmunyfhV7tKZJwlBlQ7Fcfyvrw47fpTgDWg7PGAHTVkfJMPppohpqgzS8vI7PU1Z4u dK4d7wMUh9OzBtpqkAj9PJcrBpU22qMap7SSAUJ3fimpOmSrU8bXEDk08UIOxy9vvYt4HB CmH/mTTgDv+4jmVMP8Wk7kX/g8uxoVg= X-MC-Unique: 4NHMaoLeO_2EQx76GK54oQ-1 X-Mimecast-MFC-AGG-ID: 4NHMaoLeO_2EQx76GK54oQ_1780580686 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:49 +0400 Subject: [PATCH v5 03/12] system/memory: constify section arguments MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-3-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=16962; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=hsOVg7tfNvdNlKdVvwQtTFjqhtsx5tPExed/hVKen5Q=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEwupYJ9cbC8oOsNO7ZY4Axfu/K2L1+WY3rS BNTPe439L+JAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMAAKCRDa6OEJdZac 5Q8DD/994m8iedVQiISyS/qyCX7CSHhrAfQ97UVoVDDvPVhuOo4LjcDkDzCa5vBNGzRBMjoTaDV EBzuyHOoLB7n4e/ckuqePeH7p3tzM4WDc00YkopyQ6/TTURuMiTcptS4z1bKc22LCpEUkcTTI2z DKNG6VOjwifDWqIh9T9x0NLILaiHwTlfXLIF3Fnh5h3aOGTMR5Px++uveteckg3B71v6h/e5D6P OFS79xYDrERPWzpvTA487+BnFHnU31ZTVdXQVwvLZxsS8tYUpyWgAuy3HvDJ2NKRsXB1O6BFuOL DGcKFRB8MNOQYgmrBoTBNGUGQpzCxwiLITAiQB508jqubVu3XJn81VTeBo5FcZWJ56tI+F4NOKr QMK7GaJNkzw34HQs8Eck+xJcW+nUeh1IS3R/i7COmpyDkEcP55N33+efxkbIuuH0y8UMp5teIWd Iwh05F2wAt5zdpvCVrsKUSZ7EaxJRyPAOKDRthOixabQ5f/W+1Cs76PZhvl6NUNPRayiyYx4var 9XXds0ggy9OjLQX8yAdYVuleXGqH+lwegyd4lVal1u1dxfS/JEpTJ0b20XspNtEPDhjLFp7auv5 i9erQfvK3uaOZ4Guk1JNdYRqR5DQ771fm3Y656saZZ2aWIzfhjGXWcvKUgEpWEL1GAY80kVJYVI 2ghrUshzeOO7CSw== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580756881154100 The sections shouldn't be modified. Reviewed-by: Peter Xu Reviewed-by: C=C3=A9dric Le Goater Signed-off-by: Marc-Andr=C3=A9 Lureau Acked-by: David Hildenbrand (Arm) --- 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-contai= ner.h index a7d5c5ed679..b2e7f4312c3 100644 --- a/include/hw/vfio/vfio-container.h +++ b/include/hw/vfio/vfio-container.h @@ -277,7 +277,7 @@ struct VFIOIOMMUClass { }; =20 VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section); + VFIOContainer *bcontainer, const MemoryRegionSection *section); =20 void vfio_container_region_add(VFIOContainer *bcontainer, MemoryRegionSection *section, bool cpr_rema= p); diff --git a/include/hw/vfio/vfio-cpr.h b/include/hw/vfio/vfio-cpr.h index 4606da500a7..ecabe0c747d 100644 --- a/include/hw/vfio/vfio-cpr.h +++ b/include/hw/vfio/vfio-cpr.h @@ -69,7 +69,7 @@ void vfio_cpr_giommu_remap(struct VFIOContainer *bcontain= er, MemoryRegionSection *section); =20 bool vfio_cpr_ram_discard_replay_populated( - struct VFIOContainer *bcontainer, MemoryRegionSection *section); + struct VFIOContainer *bcontainer, const MemoryRegionSection *section); =20 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-disc= ard-manager.h index da55658169f..b188e09a30f 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -26,9 +26,9 @@ DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceCl= ass, =20 typedef struct RamDiscardListener RamDiscardListener; typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl, - MemoryRegionSection *section); + const MemoryRegionSection *section); typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl, - MemoryRegionSection *section); + const MemoryRegionSection *section); =20 struct RamDiscardListener { /* @@ -86,7 +86,7 @@ static inline void ram_discard_listener_init(RamDiscardLi= stener *rdl, * * Returns 0 on success, or a negative error if failed. */ -typedef int (*ReplayRamDiscardState)(MemoryRegionSection *section, +typedef int (*ReplayRamDiscardState)(const MemoryRegionSection *section, void *opaque); =20 /* @@ -151,7 +151,7 @@ struct RamDiscardSourceClass { * Returns 0 on success, or a negative error if any notification faile= d. */ int (*replay_populated)(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); =20 /** @@ -168,7 +168,7 @@ struct RamDiscardSourceClass { * Returns 0 on success, or a negative error if any notification faile= d. */ int (*replay_discarded)(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque); }; =20 @@ -237,7 +237,7 @@ bool ram_discard_manager_is_populated(const RamDiscardM= anager *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 *sectio= n, ReplayRamDiscardState replay_fn, void *opaque); =20 @@ -255,7 +255,7 @@ int ram_discard_manager_replay_populated(const RamDisca= rdManager *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 *sectio= n, ReplayRamDiscardState replay_fn, void *opaque); =20 diff --git a/hw/vfio/cpr-legacy.c b/hw/vfio/cpr-legacy.c index c431d899731..2d40d8baeaf 100644 --- a/hw/vfio/cpr-legacy.c +++ b/hw/vfio/cpr-legacy.c @@ -226,7 +226,7 @@ void vfio_cpr_giommu_remap(VFIOContainer *bcontainer, memory_region_iommu_replay(giommu->iommu_mr, &giommu->n); } =20 -static int vfio_cpr_rdm_remap(MemoryRegionSection *section, void *opaque) +static int vfio_cpr_rdm_remap(const MemoryRegionSection *section, void *op= aque) { RamDiscardListener *rdl =3D opaque; =20 @@ -242,7 +242,7 @@ static int vfio_cpr_rdm_remap(MemoryRegionSection *sect= ion, void *opaque) * directly, which calls vfio_legacy_cpr_dma_map. */ bool vfio_cpr_ram_discard_replay_populated(VFIOContainer *bcontainer, - MemoryRegionSection *section) + const MemoryRegionSection *sect= ion) { RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(secti= on->mr); VFIORamDiscardListener *vrdl =3D diff --git a/hw/vfio/listener.c b/hw/vfio/listener.c index 0b72a2cf5e5..fbff1344448 100644 --- a/hw/vfio/listener.c +++ b/hw/vfio/listener.c @@ -201,7 +201,7 @@ out: } =20 static void vfio_ram_discard_notify_discard(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *sec= tion) { VFIORamDiscardListener *vrdl =3D container_of(rdl, VFIORamDiscardListe= ner, listener); @@ -219,7 +219,7 @@ static void vfio_ram_discard_notify_discard(RamDiscardL= istener *rdl, } =20 static int vfio_ram_discard_notify_populate(RamDiscardListener *rdl, - MemoryRegionSection *section) + const MemoryRegionSection *sec= tion) { VFIORamDiscardListener *vrdl =3D container_of(rdl, VFIORamDiscardListe= ner, listener); @@ -461,7 +461,7 @@ static void vfio_device_error_append(VFIODevice *vbased= ev, Error **errp) } =20 VFIORamDiscardListener *vfio_find_ram_discard_listener( - VFIOContainer *bcontainer, MemoryRegionSection *section) + VFIOContainer *bcontainer, const MemoryRegionSection *section) { VFIORamDiscardListener *vrdl; =20 @@ -1143,8 +1143,8 @@ out: } } =20 -static int vfio_ram_discard_query_dirty_bitmap(MemoryRegionSection *sectio= n, - void *opaque) +static int vfio_ram_discard_query_dirty_bitmap(const MemoryRegionSection *= section, + void *opaque) { const hwaddr size =3D int128_get64(section->size); const hwaddr iova =3D section->offset_within_address_space; diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index be149ee9441..ec165503205 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -262,7 +262,7 @@ static int virtio_mem_for_each_plugged_range(VirtIOMEM = *vmem, void *arg, typedef int (*virtio_mem_section_cb)(MemoryRegionSection *s, void *arg); =20 static int virtio_mem_for_each_plugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, + const MemoryRegionSection *= s, void *arg, virtio_mem_section_cb cb) { @@ -294,7 +294,7 @@ static int virtio_mem_for_each_plugged_section(const Vi= rtIOMEM *vmem, } =20 static int virtio_mem_for_each_unplugged_section(const VirtIOMEM *vmem, - MemoryRegionSection *s, + const MemoryRegionSection= *s, void *arg, virtio_mem_section_cb cb) { @@ -1680,7 +1680,7 @@ static int virtio_mem_rds_replay_cb(MemoryRegionSecti= on *s, void *arg) } =20 static int virtio_mem_rds_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *s, + const MemoryRegionSection *s, ReplayRamDiscardState replay_fn, void *opaque) { @@ -1692,11 +1692,11 @@ static int virtio_mem_rds_replay_populated(const Ra= mDiscardSource *rds, =20 g_assert(s->mr =3D=3D &vmem->memdev->mr); return virtio_mem_for_each_plugged_section(vmem, s, &data, - virtio_mem_rds_replay_cb); + virtio_mem_rds_replay_cb); } =20 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 fc38ffbf8af..b2c7ec43b6a 100644 --- a/migration/ram.c +++ b/migration/ram.c @@ -860,7 +860,7 @@ static inline bool migration_bitmap_clear_dirty(RAMStat= e *rs, return ret; } =20 -static int dirty_bitmap_clear_section(MemoryRegionSection *section, +static int dirty_bitmap_clear_section(const MemoryRegionSection *section, void *opaque) { const hwaddr offset =3D section->offset_within_region; @@ -1588,7 +1588,7 @@ static inline void populate_read_range(RAMBlock *bloc= k, ram_addr_t offset, } } =20 -static inline int populate_read_section(MemoryRegionSection *section, +static inline int populate_read_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size =3D int128_get64(section->size); @@ -1663,7 +1663,7 @@ void ram_write_tracking_prepare(void) } } =20 -static inline int uffd_protect_section(MemoryRegionSection *section, +static inline int uffd_protect_section(const MemoryRegionSection *section, void *opaque) { const hwaddr size =3D int128_get64(section->size); diff --git a/system/memory_mapping.c b/system/memory_mapping.c index da708a08ab7..cacef504f68 100644 --- a/system/memory_mapping.c +++ b/system/memory_mapping.c @@ -196,7 +196,7 @@ typedef struct GuestPhysListener { } GuestPhysListener; =20 static void guest_phys_block_add_section(GuestPhysListener *g, - MemoryRegionSection *section) + const MemoryRegionSection *sectio= n) { const hwaddr target_start =3D section->offset_within_address_space; const hwaddr target_end =3D target_start + int128_get64(section->size); @@ -248,7 +248,7 @@ static void guest_phys_block_add_section(GuestPhysListe= ner *g, #endif } =20 -static int guest_phys_ram_populate_cb(MemoryRegionSection *section, +static int guest_phys_ram_populate_cb(const MemoryRegionSection *section, void *opaque) { GuestPhysListener *g =3D opaque; diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index a72924eea7d..79c7e97d9a1 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -37,7 +37,7 @@ typedef int (*ram_block_attributes_section_cb)(MemoryRegi= onSection *s, =20 static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *= attr, - MemoryRegionSection *secti= on, + const MemoryRegionSection = *section, void *arg, ram_block_attributes_secti= on_cb cb) { @@ -78,7 +78,7 @@ ram_block_attributes_for_each_populated_section(const Ram= BlockAttributes *attr, =20 static int ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *= attr, - MemoryRegionSection *secti= on, + const MemoryRegionSection = *section, void *arg, ram_block_attributes_secti= on_cb cb) { @@ -161,7 +161,7 @@ ram_block_attributes_rds_is_populated(const RamDiscardS= ource *rds, =20 static int ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *secti= on, ReplayRamDiscardState replay_fn, void *opaque) { @@ -175,7 +175,7 @@ ram_block_attributes_rds_replay_populated(const RamDisc= ardSource *rds, =20 static int ram_block_attributes_rds_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *section, + const MemoryRegionSection *secti= on, ReplayRamDiscardState replay_fn, void *opaque) { diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index 3d8c85617d7..1c9ff7fda58 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -28,7 +28,7 @@ static bool ram_discard_source_is_populated(const RamDisc= ardSource *rds, } =20 static int ram_discard_source_replay_populated(const RamDiscardSource *rds, - MemoryRegionSection *sectio= n, + const MemoryRegionSection *= section, ReplayRamDiscardState repla= y_fn, void *opaque) { @@ -39,7 +39,7 @@ static int ram_discard_source_replay_populated(const RamD= iscardSource *rds, } =20 static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, - MemoryRegionSection *sectio= n, + const MemoryRegionSection *= section, ReplayRamDiscardState repla= y_fn, void *opaque) { @@ -74,7 +74,7 @@ bool ram_discard_manager_is_populated(const RamDiscardMan= ager *rdm, } =20 int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *sectio= n, ReplayRamDiscardState replay_fn, void *opaque) { @@ -83,7 +83,7 @@ int ram_discard_manager_replay_populated(const RamDiscard= Manager *rdm, } =20 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, - MemoryRegionSection *section, + const MemoryRegionSection *sectio= n, ReplayRamDiscardState replay_fn, void *opaque) { @@ -164,7 +164,7 @@ void ram_discard_manager_notify_discard_all(RamDiscardM= anager *rdm) } } =20 -static int rdm_populate_cb(MemoryRegionSection *section, void *opaque) +static int rdm_populate_cb(const MemoryRegionSection *section, void *opaqu= e) { RamDiscardListener *rdl =3D opaque; =20 --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580814; cv=none; d=zohomail.com; s=zohoarc; b=MttfAs5MzH9MFal8zuiWmQ++30x2fka/WqWhEN0sYrCcvS8tu2cqxqcg+CdCC//Yfknnxk+OPACSMynBMcI6UgZ8+7Q+YQS9iyGt6n/eMQquQ9mkBdDPcVRrz1YMB1s4ZDSqGBe25E89dts2CM6C25PSDJSMM27EPZ3fMCkWLSw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580814; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=Xl9RCdMEAiNI/QszRDVHp8xycP9ssDJJLT37LguwrcY=; b=Bp0OksGxNhrqM5S+Ml3/k2MJvFyAl+BwJ6mp/KQ/pPE1D/PYUUOkSGsVP2NJEFBxRLoSEqWQLsJvDDtbJva5tZYa2n8d8e4SlhatnYXGy59ej4J/R9kfsgg+frwnRKvgh9sGwbYi37lRfcwrp0192jxJZpNe/9oe+BciI+KhSwo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 178058081493489.24228463533393; Thu, 4 Jun 2026 06:46:54 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8OU-0001wd-Cw; Thu, 04 Jun 2026 09:45:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NW-0001Mw-E5 for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:58 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8NU-00008F-Re for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:44:58 -0400 Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-660-jyPHo_ETP0iXAeVQpKdpDg-1; Thu, 04 Jun 2026 09:44:54 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6D4B71944D2B; Thu, 4 Jun 2026 13:44:52 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 426E61955D87; Thu, 4 Jun 2026 13:44:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580696; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Xl9RCdMEAiNI/QszRDVHp8xycP9ssDJJLT37LguwrcY=; b=hlxjgqXnOhLpoUvMyzc2bGiaEBC3hNZoFgpcOtxDR/SNcw3WZ0u+LjAEboqCr26y+Z0bwc dYS2wn9MRIBRd1th0j8b5FUVr2ua6ILneUyQAmhLWAk5gpDC0zWetHmDBWXCHFULH6shYV odf1MM4sZO6TlVmNQTuKvWxpXLmtcc0= X-MC-Unique: jyPHo_ETP0iXAeVQpKdpDg-1 X-Mimecast-MFC-AGG-ID: jyPHo_ETP0iXAeVQpKdpDg_1780580692 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:50 +0400 Subject: [PATCH v5 04/12] system/ram-discard-manager: implement replay via is_populated iteration MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-4-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=6612; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=znVW5921M7+0092ia+MeDcCo7lcpbF5fnLhhQFRfDqg=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEwCZfScNDE5Vpjil/kVwVsL+hRHm2fLwKlH OvbKXyWk8qJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMAAKCRDa6OEJdZac 5aLTD/90+rze5UF4V7k4llDujSN41MD2hldcmVC8EUhex1DIX479ntPfkHKdl7IJJZVmZ5OyPAP 8TKsnksokw9Jfa4zNKQQhlZiRQHnB6J+blGcT7yUBGq0+gwK3qTwDauxD3WE70un2a3QxSgAYaP 2KT70j3Vl69CfiCGNrMcsgBWGix7ZZX559QgfDCiQ/goXk/T1bpEtpmf/ZvMpoKwm82FpZTrvpJ bwc9Krsq0kM17lM8smI4TmcYHN4u0l4SmB2wFqm9fmuU2107d2v/E5OAjaaQERa6M5YXjuTjdP0 0JajWufgj6u2gznVtyv/DxZaMnOwEqwEMiWOWey9VHjYfoGNzEsXDVRdAvsD7FjeIIStSgcMTul bxw0isQCUOKQxaMFe6tNLuxddIUHx+yiBXogdWnh1m6gVb4tYJMPTIwR3nF8skAYPIzqldKgcB0 zGb1R9oF7zkT4v+aphWkOF36NQ3DBsEV/QlsmVuteEp4zPkz+ikN1T71IE9pIiRMDseL1bf7S7G SpfwoSzxf3P/9IYRt/FZZkr8VxbzLbTgAUBMCLUCAbo3cuzTlnSWR1GdemNGKWIl8hgLuUu36yH iaaHkgmVwEm7Jlt0UqkKV4UcmbkeC948T2UXFU9iNIZW8X+3uqZtxlF5XK3D1m6TxPq2wILJ34+ kmNQF1xPgrW6pqQ== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580817337158500 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 Signed-off-by: Marc-Andr=C3=A9 Lureau Acked-by: David Hildenbrand --- 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 1c9ff7fda58..a907ddf3708 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -27,26 +27,65 @@ static bool ram_discard_source_is_populated(const RamDi= scardSource *rds, return rdsc->is_populated(rds, section); } =20 -static int ram_discard_source_replay_populated(const RamDiscardSource *rds, - const MemoryRegionSection *= section, - ReplayRamDiscardState repla= y_fn, - void *opaque) +/* + * Iterate the section at source granularity, aggregating consecutive chun= ks + * 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 =3D RAM_DISCARD_SOURCE_GET_CLASS(rds); + uint64_t granularity, offset, size, end, pos, run_start =3D 0; + bool in_run =3D false; + int ret =3D 0; =20 - g_assert(rdsc->replay_populated); - return rdsc->replay_populated(rds, section, replay_fn, opaque); -} + granularity =3D ram_discard_source_get_min_granularity(rdm->rds, rdm->= mr); + offset =3D section->offset_within_region; + size =3D int128_get64(section->size); + end =3D offset + size; + + /* Align iteration to granularity boundaries */ + pos =3D QEMU_ALIGN_DOWN(offset, granularity); + + for (; pos < end; pos +=3D granularity) { + MemoryRegionSection chunk =3D { + .mr =3D section->mr, + .offset_within_region =3D pos, + .size =3D int128_make64(granularity), + }; + bool populated =3D ram_discard_source_is_populated(rdm->rds, &chun= k); + + if (populated =3D=3D replay_populated) { + if (!in_run) { + run_start =3D pos; + in_run =3D true; + } + } else if (in_run) { + MemoryRegionSection tmp =3D *section; + + if (memory_region_section_intersect_range(&tmp, run_start, + pos - run_start)) { + ret =3D replay_fn(&tmp, opaque); + if (ret) { + return ret; + } + } + in_run =3D false; + } + } =20 -static int ram_discard_source_replay_discarded(const RamDiscardSource *rds, - const MemoryRegionSection *= section, - ReplayRamDiscardState repla= y_fn, - void *opaque) -{ - RamDiscardSourceClass *rdsc =3D RAM_DISCARD_SOURCE_GET_CLASS(rds); + if (in_run) { + MemoryRegionSection tmp =3D *section; =20 - 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 =3D replay_fn(&tmp, opaque); + } + } + + return ret; } =20 RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, @@ -78,8 +117,7 @@ int ram_discard_manager_replay_populated(const RamDiscar= dManager *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= ); } =20 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -87,8 +125,7 @@ int ram_discard_manager_replay_discarded(const RamDiscar= dManager *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, opaqu= e); } =20 static void ram_discard_manager_initfn(Object *obj) @@ -182,8 +219,8 @@ void ram_discard_manager_register_listener(RamDiscardMa= nager *rdm, rdl->section =3D memory_region_section_new_copy(section); QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); =20 - ret =3D ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); + ret =3D 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)); @@ -208,8 +245,8 @@ int ram_discard_manager_replay_populated_to_listeners(R= amDiscardManager *rdm) int ret =3D 0; =20 QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - ret =3D ram_discard_source_replay_populated(rdm->rds, rdl->section, - rdm_populate_cb, rdl); + ret =3D ram_discard_manager_replay_populated(rdm, rdl->section, + rdm_populate_cb, rdl); if (ret) { break; } --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580772; cv=none; d=zohomail.com; s=zohoarc; b=YAvWGuxWDObTJBOwUAnf6XyS0eU0sivQPf7LlXDCRh6Qy7BcuIp+apyuSn165dtEqbiyYkiNMI1OzuYB2qjaCFPXgLSZv2piYAOo/GmNZcoofafJhkivDn1e92a5RRW+lLVD82XbaB0MLqONtC1anlriRBJvQk9nX9nNm3NqYa8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580772; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=AsFFSlmcPgz7P59Jq1uN1IgQFXLbNpyWVcPlkHq1ipM=; b=TNjcbsSteUrJeT5mc3Ixk7T2f0+ggjEEmi9lrNRyfylPfF8NFXL4wbySH+YtdxP+N5kVRCV2fL8ZWowB9oYqWCi54E32uwhCpw9Uay0lNgNbJBXI1P989d+VEeSWLSIgRY6qZ7xjsHHjYkS0Sn5y42cH1RqINhc5yeIzWVnnQtk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580772602374.8317976984889; Thu, 4 Jun 2026 06:46:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8OX-0002Dy-Vq; Thu, 04 Jun 2026 09:46:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nh-0001Qv-81 for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:13 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Ne-00009z-4W for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:08 -0400 Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-110-qfCjTpTzMWKKvnSxi9D55Q-1; Thu, 04 Jun 2026 09:45:00 -0400 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4BC6A1800347; Thu, 4 Jun 2026 13:44:58 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4D5C51955BC0; Thu, 4 Jun 2026 13:44:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580705; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=AsFFSlmcPgz7P59Jq1uN1IgQFXLbNpyWVcPlkHq1ipM=; b=ctpDd6V/DLuWWsl+IkSr4rWg2XNZJlXbMvMAxlzySRAIsrFDAZ48dRjkFzAY4BfAZ2LTc0 00zF8VF2DNSCFQnMiGGXjI6BFDjvpn4ixEJEShUHEMNXy9ngS6pLe28hyh2IGo5WMqwNRN 79yEsAhw5ZFFl49i0Li2dtqOO7wEOqA= X-MC-Unique: qfCjTpTzMWKKvnSxi9D55Q-1 X-Mimecast-MFC-AGG-ID: qfCjTpTzMWKKvnSxi9D55Q_1780580698 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:51 +0400 Subject: [PATCH v5 05/12] virtio-mem: remove replay_populated/replay_discarded implementation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-5-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=5923; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=OJfVRBp7kCu62VvYNEMbvrRpWVFwhQJwSIkKTdgTM0k=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYExU3XsLYMtzB6g9a2modKeMvbuSi/RhKIEJ xPm44PSgLuJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMQAKCRDa6OEJdZac 5QyDEAC1IKVU66N5vpwBNfqfKEeFtZnWip/EZlk8WqJ/wNXONiL0142IWp0g0BdMvjvSaigqrx1 MbcSao7FUjT055+E3UraMDXM8sm5KYWxByptwmw1nNbXjxwamOLFOefoKfpKRA+c9FSxLpDapUJ cd9W7FSxtNZU/S+5hNg4jGM14pCfiqvusB6cPNbYGIvS1Dmz0TsdwPmeDDUwobjnU188jldAEWP 9mIQkxutRAtoktRXGU1YC4MILVaMyYAvyTKX1MIQkRf5jKpMu80tkeh3hizGv7RcfpL+NhmC8MN EZLznrnEmlCIG6/f1ZuzWQ5WHZWLXTT/ruswsS3hun9WBPxE307BODeAqhE8PNKhNMolyZR0uM5 ONARGWMNe/qcEAJ+UO3rFK/kzGlwQVgYESnGX8WCJpCKf2tC4txFKSmILPfYChC3oD5e2hAFeIP w1b0knitK+Lr/snJMVVPU2Mo2osjItNbsvr3o4Fb0Ei0n+PPAy2dkhos4p3l6XsJodNJNQUdnjQ 4Zj2Gmb+ZtzukUhmb29NejpefK86U7VbwbaBBzrlC1ReRqbIdZFiJmZGVDgl7EVQx64lUMiGxRC L4zaM3CV2jLpCira/9mk15V3HwLHW5vNi6bUsso3UiG+R17O9d+4zhhiPeXhGKDHdu4y9bUxN/p yb8Qp7FxxawblhQ== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580774886158500 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 Acked-by: David Hildenbrand Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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 ec165503205..2b67b2882d2 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -259,72 +259,6 @@ static int virtio_mem_for_each_plugged_range(VirtIOMEM= *vmem, void *arg, return ret; } =20 -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 =3D 0; - - first_bit =3D s->offset_within_region / vmem->block_size; - first_bit =3D find_next_bit(vmem->bitmap, vmem->bitmap_size, first_bit= ); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp =3D *s; - - offset =3D first_bit * vmem->block_size; - last_bit =3D find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size =3D (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret =3D cb(&tmp, arg); - if (ret) { - break; - } - first_bit =3D 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 =3D 0; - - first_bit =3D s->offset_within_region / vmem->block_size; - first_bit =3D find_next_zero_bit(vmem->bitmap, vmem->bitmap_size, firs= t_bit); - while (first_bit < vmem->bitmap_size) { - MemoryRegionSection tmp =3D *s; - - offset =3D first_bit * vmem->block_size; - last_bit =3D find_next_bit(vmem->bitmap, vmem->bitmap_size, - first_bit + 1) - 1; - size =3D (last_bit - first_bit + 1) * vmem->block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - ret =3D cb(&tmp, arg); - if (ret) { - break; - } - first_bit =3D 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) { @@ -1667,50 +1601,6 @@ static bool virtio_mem_rds_is_populated(const RamDis= cardSource *rds, return virtio_mem_is_range_plugged(vmem, start_gpa, end_gpa - start_gp= a); } =20 -struct VirtIOMEMReplayData { - ReplayRamDiscardState fn; - void *opaque; -}; - -static int virtio_mem_rds_replay_cb(MemoryRegionSection *s, void *arg) -{ - struct VirtIOMEMReplayData *data =3D 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 =3D VIRTIO_MEM(rds); - struct VirtIOMEMReplayData data =3D { - .fn =3D replay_fn, - .opaque =3D opaque, - }; - - g_assert(s->mr =3D=3D &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 =3D VIRTIO_MEM(rds); - struct VirtIOMEMReplayData data =3D { - .fn =3D replay_fn, - .opaque =3D opaque, - }; - - g_assert(s->mr =3D=3D &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 =3D=3D ON_OFF_AUTO_OFF) { @@ -1766,8 +1656,6 @@ static void virtio_mem_class_init(ObjectClass *klass,= const void *data) =20 rdsc->get_min_granularity =3D virtio_mem_rds_get_min_granularity; rdsc->is_populated =3D virtio_mem_rds_is_populated; - rdsc->replay_populated =3D virtio_mem_rds_replay_populated; - rdsc->replay_discarded =3D virtio_mem_rds_replay_discarded; } =20 static const TypeInfo virtio_mem_info =3D { --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580800; cv=none; d=zohomail.com; s=zohoarc; b=aR2qXMW6tXUBqFjeGCPEMd86HNTQHUqozWzsL4X+mygO8YeN6aHD6+TYviV5V1evdIT2FaI4vsEwu9iG5Fh7xeRvKrxk6dkpk70V9bvek9QhBFMlemrmX62PB38UalZAkIE0R966IgqGYHvAPwk0k9mpGKY9Eg9QtZAA+aDEuFw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580800; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=Tbejgn6peDRmBduDPks63yG3w03Q38Tv7Pv8zIRvQpE=; b=HO/0TrQSjrzZ3KFGIvKTaLGh7jFbKBLhOqRcttcy3tjNhKisZ238NaYyDpGaryaUPNkpuSaFLpA698NXXHZEJIniO5l2S2M3TQAZcgfwDD5fZ3ilUGq1zeeB3JSpHXZjxjbHobE02l5rbJf95SPiHbVn1CXG2F47f65VCfPI05s= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580800868229.68887747566703; Thu, 4 Jun 2026 06:46:40 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8OZ-0002Nl-Dw; Thu, 04 Jun 2026 09:46:03 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nl-0001Th-Tf for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:17 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nj-0000Cg-32 for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:13 -0400 Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-302-tVQfd-0fP9O_rDjD2G2-oQ-1; Thu, 04 Jun 2026 09:45:07 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E3DB11800623; Thu, 4 Jun 2026 13:45:03 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A85A7195608E; Thu, 4 Jun 2026 13:45:02 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580710; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Tbejgn6peDRmBduDPks63yG3w03Q38Tv7Pv8zIRvQpE=; b=D2gnQ0cdcqkhC8HPwG56fl2q9Pq8El4aUkPdO8mmyD2TmrpN+LcHrRaf3xm6Y3uErnMl47 SMT+W30+y2HjZN3fqBpA40SWjYn/JbyAsurMm5g+5ucMl/W1v9GkVht+WIZTMT/QNDKAF1 QBk0zlyZuXJ79oRbhHjryJRVjwyv1Kc= X-MC-Unique: tVQfd-0fP9O_rDjD2G2-oQ-1 X-Mimecast-MFC-AGG-ID: tVQfd-0fP9O_rDjD2G2-oQ_1780580704 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:52 +0400 Subject: [PATCH v5 06/12] system/ram-discard-manager: drop replay from source interface MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-6-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=10226; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=qRl+3gxMvyFk75I9Bv7L080thrk1YE9Z2FNe7f5N5Uk=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEx7sBa/J6S+/iE3nEPk5PnbbAFGsZ+LDNRB 1Ub54gNP4OJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMQAKCRDa6OEJdZac 5QTRD/4vDMyjTz552zfO/WoGfhzA0Z/VKR7hzGdkbtQ+K6ksYAVSVkBwyqjNmgdE2RQC5WvIemN 4K1EEVEY3wBcSyZDGYn2dvNMBomOc5EkqAbbCmUgznHb1MKXzgjoSw4vYz4pMfeZdae6F11b/jY tTgzv8Jn9qcBsOcOTP0aH33ceNsjEiraol4IuyYzt7j8lO1ZD56NzuXLIuyXs2ov4YVTFVpHdBM vjUTQJAGO4M7x0eg+52CSCLuVPP7/MY03yQUYasyMPvrvU82vs1mN7W0dRzpTHps1I4dUke3bp4 ApvJj2QdjD6uzHVTRfIEMynQA2ILFPuyb0GAlD4Fm2Y7McHlSJhBLwEpGEbLBRNHb6MID5tOuXE +PaWyhQTTpivjST/j7SGDhxQhS4iekW4TPOo/IgBdNobtv5tuP7o29tokaxfml7YqgsuDflos00 MsUaf86xUx4cMPGu3tyYT99g9Cd0GU0Hs+dHRV/VCCNcVwu0x6pj/mYUh94RFZppexmTjs0ZEoX eARL+wwXRaVko0qrOME4tWHYl34ZATdvirs7xBwWb2G35R2TG0DuRPkCiAAETnYzv0d99OeTXBE QvYCVdv2upv+Ollnw6kYiJ5NpJ8HBUVmfo2nZ0bXs54UDV3fyvf3Kdbo1AQ58N9gI7QzF9+0yXv h/2Z1aErX8LvBsQ== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580803196154100 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 Acked-by: David Hildenbrand Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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-disc= ard-manager.h index b188e09a30f..b5dbcb4a82d 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -77,8 +77,8 @@ static inline void ram_discard_listener_init(RamDiscardLi= stener *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 @@ -134,42 +134,6 @@ struct RamDiscardSourceClass { */ bool (*is_populated)(const RamDiscardSource *rds, const MemoryRegionSection *section); - - /** - * @replay_populated: - * - * Call the #ReplayRamDiscardState callback for all populated parts wi= thin - * 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 faile= d. - */ - int (*replay_populated)(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); - - /** - * @replay_discarded: - * - * Call the #ReplayRamDiscardState callback for all discarded parts wi= thin - * 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 faile= d. - */ - int (*replay_discarded)(const RamDiscardSource *rds, - const MemoryRegionSection *section, - ReplayRamDiscardState replay_fn, void *opaque); }; =20 /** @@ -226,8 +190,10 @@ bool ram_discard_manager_is_populated(const RamDiscard= Manager *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 @@ -244,8 +210,10 @@ int ram_discard_manager_replay_populated(const RamDisc= ardManager *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 79c7e97d9a1..718c7075cec 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -32,106 +32,6 @@ ram_block_attributes_get_block_size(void) return qemu_real_host_page_size(); } =20 -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_secti= on_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size =3D ram_block_attributes_get_block_size(); - int ret =3D 0; - - first_bit =3D section->offset_within_region / block_size; - first_bit =3D find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp =3D *section; - - offset =3D first_bit * block_size; - last_bit =3D find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size =3D (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret =3D cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit =3D 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_secti= on_cb cb) -{ - unsigned long first_bit, last_bit; - uint64_t offset, size; - const size_t block_size =3D ram_block_attributes_get_block_size(); - int ret =3D 0; - - first_bit =3D section->offset_within_region / block_size; - first_bit =3D find_next_zero_bit(attr->bitmap, attr->bitmap_size, - first_bit); - - while (first_bit < attr->bitmap_size) { - MemoryRegionSection tmp =3D *section; - - offset =3D first_bit * block_size; - last_bit =3D find_next_bit(attr->bitmap, attr->bitmap_size, - first_bit + 1) - 1; - size =3D (last_bit - first_bit + 1) * block_size; - - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - break; - } - - ret =3D cb(&tmp, arg); - if (ret) { - error_report("%s: Failed to notify RAM discard listener: %s", - __func__, strerror(-ret)); - break; - } - - first_bit =3D 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 =3D arg; - - return data->fn(section, data->opaque); -} - /* RamDiscardSource interface implementation */ static uint64_t ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds, @@ -159,34 +59,6 @@ ram_block_attributes_rds_is_populated(const RamDiscardS= ource *rds, return first_discarded_bit > last_bit; } =20 -static int -ram_block_attributes_rds_replay_populated(const RamDiscardSource *rds, - const MemoryRegionSection *secti= on, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); - RamBlockAttributesReplayData data =3D { .fn =3D replay_fn, .opaque =3D= opaque }; - - g_assert(section->mr =3D=3D 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 *secti= on, - ReplayRamDiscardState replay_fn, - void *opaque) -{ - RamBlockAttributes *attr =3D RAM_BLOCK_ATTRIBUTES(rds); - RamBlockAttributesReplayData data =3D { .fn =3D replay_fn, .opaque =3D= opaque }; - - g_assert(section->mr =3D=3D 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 off= set, uint64_t size) @@ -346,6 +218,4 @@ static void ram_block_attributes_class_init(ObjectClass= *klass, =20 rdsc->get_min_granularity =3D ram_block_attributes_rds_get_min_granula= rity; rdsc->is_populated =3D ram_block_attributes_rds_is_populated; - rdsc->replay_populated =3D ram_block_attributes_rds_replay_populated; - rdsc->replay_discarded =3D ram_block_attributes_rds_replay_discarded; } --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580797; cv=none; d=zohomail.com; s=zohoarc; b=lOVjME4V8BR7HRUEM6tQjwym5UjIwECIniZ9NyLCS487QL9mKJwXRAkb6p5aUn8LMC4+4+xFEaOcVCwbBycFTVyWzLjMGceuDK7d0Dm/AD4rH3pY3cXkJQWFVQEtLXnLWS+cDd5WJ/z2WW5rpel2+VZ8MLGUyP20Y2mzvOFEAQ4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580797; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=oVuWwx/6qxQwYdjEeInQ0KK9hztI99OxX7jrSOeU0do=; b=ZX24SM8sI8wrWpdrmhWvMq5shA/IQJhrCmy3VwHO0MoFhtsoN771Qx2nTFrTdDXZZ1rH+EngzBg66sCqlTQSWL8GxTFRoyZunrW6EPhjESwnQ/wugYxjVa3tznMivouwZvcH24aTOotcAu0uvjVyMYcPL5iN8TfjTRa1aMC84DQ= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 178058079725675.84899836209149; Thu, 4 Jun 2026 06:46:37 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8OU-0001wU-A3; Thu, 04 Jun 2026 09:45:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nv-0001cg-8g for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:31 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Np-0000RA-Ep for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:22 -0400 Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-394-OOUSDyGcO76Rr9PycUGhoA-1; Thu, 04 Jun 2026 09:45:13 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1ACB71956050; Thu, 4 Jun 2026 13:45:11 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 5842E1954193; Thu, 4 Jun 2026 13:45:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580715; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oVuWwx/6qxQwYdjEeInQ0KK9hztI99OxX7jrSOeU0do=; b=ZSvyh/+KCurNoegbMpZv4DGY+i+0YuQ/D9jRS8rdC0F5zLNG1L3QgT8QM/T1lL4FpflaJi cIebuVSs5iUQuS+zHo++lEbGhqoMCeH7dfirO1SkthRhlMsdoUhE679E6iLd71WuBXk3xS OtPcrN0A81iKG2NIp81CVwzE3JbhDBU= X-MC-Unique: OOUSDyGcO76Rr9PycUGhoA-1 X-Mimecast-MFC-AGG-ID: OOUSDyGcO76Rr9PycUGhoA_1780580711 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:53 +0400 Subject: [PATCH v5 07/12] system/memory: implement RamDiscardManager multi-source aggregation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-7-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=33104; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=pDiv+J28EyuPDxZjvLyAIHlbkllsBm+7dyWpXpjqKD0=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYExUF7WRPE4gUXur0UjSeWDDP/UbUh/GYYrW 9gkTgFVOTqJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMQAKCRDa6OEJdZac 5edmD/46P48+oYbyfIEr5EURVw9OvFZVCwAFT06gmYeWm+3Kb0StjJCXUszxrckcA5W26Kp1tAk VFHAsqcJvRCdn698v9C1r3ILftNtXNIfqxUcD36dpzsIttyU8WCyTdOU19lL5V7CCekBTQjgt0R IQ6sIPxOPdT+C7RDE8YPZ38Eskt1Ep/lmHjbTCIpDuBgKy29tFmQNQk+uK0lRqXpoVlSRnlUV9q DDYsLWOUuP0fN9BkEwaITI+nbhSVSmi3n8aWn9QfB74Mwq8fTu4PjR0fan9YnLSi8CxW5RxzzrP B4SaXtuo1zaWu/8FYYGo2+/WveHlKskfOId0z2/c+jWbCpcIf+eIYG9Qa2mWbT+IuWtIltEyRVG 5FwlnBrYZt3Dtanu7V/HvFHyjK6ytxEfJMsOJBNm0r/49bSb58uMr/gh1HBsWLp8Xbr87Pc6i60 sp3BXGQFTQEQzqupCVLERTv7Y4rmdKvHb7D90vusrdXRkDB4EmJprmZKEXC2k3ClR7SI5vJ4pF4 cipvMiLRXQT778dYHg0cqNFA32r8ip74EkKmtcV+WTgw1wf9FqVILh20zfbp9c/k7XI6BLkb56i 6J6KtIT5tOITNoLHNO4v3zVxXZpUdg/NvFezUkvubZ7WX1emJd1BumfTBnu59UmWcYSp/WDk0Mv CzIl2cu+w1FD2BA== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580799006154100 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 Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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 28a75dac4ae..7857164078f 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -2243,8 +2243,10 @@ int memory_region_add_ram_discard_source(MemoryRegio= n *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, RamDiscardSour= ce *source); +int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSourc= e *source); =20 /** * memory_region_find: translate an address/size relative to a diff --git a/include/system/ram-discard-manager.h b/include/system/ram-disc= ard-manager.h index b5dbcb4a82d..05d3d31b55a 100644 --- a/include/system/ram-discard-manager.h +++ b/include/system/ram-discard-manager.h @@ -170,30 +170,96 @@ 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; =20 - RamDiscardSource *rds; MemoryRegion *mr; + QLIST_HEAD(, RamDiscardSourceEntry) source_list; + uint64_t min_granularity; QLIST_HEAD(, RamDiscardListener) rdl_list; }; =20 -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 reg= ion + * 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 notificat= ion. + * + * 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); + =20 uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *= rdm, const MemoryRegion *mr); =20 +/** + * 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); =20 /** * 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 @@ -210,10 +276,7 @@ int ram_discard_manager_replay_populated(const RamDisc= ardManager *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 @@ -234,31 +297,61 @@ void ram_discard_manager_register_listener(RamDiscard= Manager *rdm, void ram_discard_manager_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl); =20 -/* - * Note: later refactoring should take the source into account and the man= ager - * 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); =20 -/* - * Note: later refactoring should take the source into account and the man= ager - * 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); =20 -/* - * Note: later refactoring should take the source into account and the man= ager - * 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); =20 -/* +/** + * 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 man= ager - * 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 *r= dm); =20 diff --git a/hw/virtio/virtio-mem.c b/hw/virtio/virtio-mem.c index 2b67b2882d2..35e03ed7599 100644 --- a/hw/virtio/virtio-mem.c +++ b/hw/virtio/virtio-mem.c @@ -264,7 +264,8 @@ static void virtio_mem_notify_unplug(VirtIOMEM *vmem, u= int64_t offset, { RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(&vmem= ->memdev->mr); =20 - ram_discard_manager_notify_discard(rdm, offset, size); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(vmem), + offset, size); } =20 static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint64_t offset, @@ -272,7 +273,8 @@ static int virtio_mem_notify_plug(VirtIOMEM *vmem, uint= 64_t offset, { RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(&vmem= ->memdev->mr); =20 - return ram_discard_manager_notify_populate(rdm, offset, size); + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(vme= m), + offset, size); } =20 static void virtio_mem_notify_unplug_all(VirtIOMEM *vmem) @@ -283,7 +285,7 @@ static void virtio_mem_notify_unplug_all(VirtIOMEM *vme= m) return; } =20 - ram_discard_manager_notify_discard_all(rdm); + ram_discard_manager_notify_discard_all(rdm, RAM_DISCARD_SOURCE(vmem)); } =20 static bool virtio_mem_is_range_plugged(const VirtIOMEM *vmem, diff --git a/system/memory.c b/system/memory.c index 243e69abd75..7c75eef1cec 100644 --- a/system/memory.c +++ b/system/memory.c @@ -2047,21 +2047,22 @@ int memory_region_add_ram_discard_source(MemoryRegi= on *mr, RamDiscardSource *source) { g_assert(memory_region_is_ram(mr)); - if (mr->rdm) { - return -EBUSY; + + if (!mr->rdm) { + mr->rdm =3D ram_discard_manager_new(mr); } =20 - mr->rdm =3D ram_discard_manager_new(mr, RAM_DISCARD_SOURCE(source)); - return 0; + return ram_discard_manager_add_source(mr->rdm, source); } =20 -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 =3D=3D source); + g_assert(mr->rdm); + + return ram_discard_manager_del_source(mr->rdm, source); =20 - object_unref(mr->rdm); - mr->rdm =3D NULL; + /* if there is no source and no listener left, we could free rdm */ } =20 /* Called with rcu_read_lock held. */ diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index 718c7075cec..59ec7a28eb0 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -90,7 +90,8 @@ ram_block_attributes_notify_discard(RamBlockAttributes *a= ttr, { RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(attr-= >ram_block->mr); =20 - ram_discard_manager_notify_discard(rdm, offset, size); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(attr), + offset, size); } =20 static int @@ -99,7 +100,8 @@ ram_block_attributes_notify_populate(RamBlockAttributes = *attr, { RamDiscardManager *rdm =3D memory_region_get_ram_discard_manager(attr-= >ram_block->mr); =20 - return ram_discard_manager_notify_populate(rdm, offset, size); + return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(att= r), + offset, size); } =20 int ram_block_attributes_state_change(RamBlockAttributes *attr, diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index a907ddf3708..7da91bf648a 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -7,6 +7,7 @@ =20 #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qemu/queue.h" #include "system/memory.h" =20 static uint64_t ram_discard_source_get_min_granularity(const RamDiscardSou= rce *rds, @@ -28,20 +29,21 @@ static bool ram_discard_source_is_populated(const RamDi= scardSource *rds, } =20 /* - * Iterate the section at source granularity, aggregating consecutive chun= ks - * 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 =3D 0; bool in_run =3D false; int ret =3D 0; =20 - granularity =3D ram_discard_source_get_min_granularity(rdm->rds, rdm->= mr); + granularity =3D ram_discard_source_get_min_granularity(source, mr); offset =3D section->offset_within_region; size =3D int128_get64(section->size); end =3D offset + size; @@ -55,7 +57,7 @@ static int replay_by_populated_state(const RamDiscardMana= ger *rdm, .offset_within_region =3D pos, .size =3D int128_make64(granularity), }; - bool populated =3D ram_discard_source_is_populated(rdm->rds, &chun= k); + bool populated =3D ram_discard_source_is_populated(source, &chunk); =20 if (populated =3D=3D replay_populated) { if (!in_run) { @@ -88,28 +90,338 @@ static int replay_by_populated_state(const RamDiscardM= anager *rdm, return ret; } =20 -RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr, - RamDiscardSource *rds) +RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr) { RamDiscardManager *rdm; =20 rdm =3D RAM_DISCARD_MANAGER(object_new(TYPE_RAM_DISCARD_MANAGER)); - rdm->rds =3D rds; rdm->mr =3D mr; - QLIST_INIT(&rdm->rdl_list); return rdm; } =20 +static void ram_discard_manager_update_granularity(RamDiscardManager *rdm) +{ + RamDiscardSourceEntry *entry; + uint64_t granularity =3D 0; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + uint64_t src_granularity; + + src_granularity =3D + ram_discard_source_get_min_granularity(entry->rds, rdm->mr); + g_assert(src_granularity !=3D 0); + if (granularity =3D=3D 0) { + granularity =3D src_granularity; + } else { + granularity =3D MIN(granularity, src_granularity); + } + } + rdm->min_granularity =3D granularity; +} + +static RamDiscardSourceEntry * +ram_discard_manager_find_source(RamDiscardManager *rdm, RamDiscardSource *= rds) +{ + RamDiscardSourceEntry *entry; + + QLIST_FOREACH(entry, &rdm->source_list, next) { + if (entry->rds =3D=3D rds) { + return entry; + } + } + return NULL; +} + +static int rdl_populate_cb(const MemoryRegionSection *section, void *opaqu= e) +{ + RamDiscardListener *rdl =3D opaque; + MemoryRegionSection tmp =3D *rdl->section; + + g_assert(section->mr =3D=3D rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_regi= on, + int128_get64(section->size)= )) { + return 0; + } + + return rdl->notify_populate(rdl, &tmp); +} + +static int rdl_discard_cb(const MemoryRegionSection *section, void *opaque) +{ + RamDiscardListener *rdl =3D opaque; + MemoryRegionSection tmp =3D *rdl->section; + + g_assert(section->mr =3D=3D rdl->section->mr); + + if (!memory_region_section_intersect_range(&tmp, + section->offset_within_regi= on, + 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 =3D=3D 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 popul= ated. + * If replay_populated is false: replay regions where ANY source is discar= ded. + */ +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 =3D rdm->min_granularity; + uint64_t offset, end_offset; + uint64_t run_start =3D 0; + bool in_run =3D false; + int ret =3D 0; + + if (QLIST_EMPTY(&rdm->source_list)) { + if (replay_populated) { + return replay_fn(section, user_opaque); + } + return 0; + } + + g_assert(granularity !=3D 0); + + offset =3D section->offset_within_region; + end_offset =3D offset + int128_get64(section->size); + + while (offset < end_offset) { + MemoryRegionSection subsection =3D { + .mr =3D section->mr, + .offset_within_region =3D offset, + .size =3D int128_make64(MIN(granularity, end_offset - offset)), + }; + bool all_populated; + bool included; + + all_populated =3D rdm_is_all_populated_skip(rdm, &subsection, + skip_source); + included =3D replay_populated ? all_populated : !all_populated; + + if (included) { + if (!in_run) { + run_start =3D offset; + in_run =3D true; + } + } else { + if (in_run) { + MemoryRegionSection run_section =3D { + .mr =3D section->mr, + .offset_within_region =3D run_start, + .size =3D int128_make64(offset - run_start), + }; + ret =3D replay_fn(&run_section, user_opaque); + if (ret) { + return ret; + } + in_run =3D false; + } + } + if (granularity > end_offset - offset) { + break; + } + offset +=3D granularity; + } + + if (in_run) { + MemoryRegionSection run_section =3D { + .mr =3D section->mr, + .offset_within_region =3D run_start, + .size =3D int128_make64(end_offset - run_start), + }; + ret =3D replay_fn(&run_section, user_opaque); + } + + return ret; +} + +static int add_source_check_discard_cb(const MemoryRegionSection *section, + void *opaque) +{ + SourceNotifyCtx *ctx =3D 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 =3D 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 =3D 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 =3D { + .rdm =3D rdm, + .rdl =3D rdl, + /* no need to set source */ + }; + ret =3D 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 =3D { + .rdm =3D rdm, + .rdl =3D rdl2, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + del_source_check_populate_cb, + &ctx); + if (rdl =3D=3D rdl2) { + break; + } + } + + return ret; + } + + entry =3D g_new0(RamDiscardSourceEntry, 1); + entry->rds =3D 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 =3D 0; + + entry =3D 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 =3D { + .rdm =3D rdm, + .rdl =3D rdl, + .source =3D source, + }; + /* + * From the previously discarded regions, check if any + * regions become populated. + */ + ret =3D 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 =3D { + .rdm =3D rdm, + .rdl =3D rdl2, + .source =3D source, + }; + replay_source_by_state(source, rdm->mr, rdl2->section, + false, + add_source_check_discard_cb, + &ctx); + if (rdl =3D=3D 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 =3D=3D rdm->mr); + return rdm->min_granularity; } =20 +/* + * Aggregated query: returns true only if ALL sources report populated (AN= D). + */ 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; } =20 int ram_discard_manager_replay_populated(const RamDiscardManager *rdm, @@ -117,7 +429,8 @@ int ram_discard_manager_replay_populated(const RamDisca= rdManager *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); } =20 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm, @@ -125,14 +438,17 @@ int ram_discard_manager_replay_discarded(const RamDis= cardManager *rdm, ReplayRamDiscardState replay_fn, void *opaque) { - return replay_by_populated_state(rdm, section, false, replay_fn, opaqu= e); + return replay_by_populated_state(rdm, section, NULL, false, + replay_fn, opaque); } =20 static void ram_discard_manager_initfn(Object *obj) { RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(obj); =20 + QLIST_INIT(&rdm->source_list); QLIST_INIT(&rdm->rdl_list); + rdm->min_granularity =3D 0; } =20 static void ram_discard_manager_finalize(Object *obj) @@ -140,74 +456,91 @@ static void ram_discard_manager_finalize(Object *obj) RamDiscardManager *rdm =3D RAM_DISCARD_MANAGER(obj); =20 g_assert(QLIST_EMPTY(&rdm->rdl_list)); + g_assert(QLIST_EMPTY(&rdm->source_list)); } =20 int ram_discard_manager_notify_populate(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size) { RamDiscardListener *rdl, *rdl2; + MemoryRegionSection section =3D { + .mr =3D rdm->mr, + .offset_within_region =3D offset, + .size =3D int128_make64(size), + }; int ret =3D 0; =20 - QLIST_FOREACH(rdl, &rdm->rdl_list, next) { - MemoryRegionSection tmp =3D *rdl->section; + g_assert(ram_discard_manager_find_source(rdm, source)); =20 - if (!memory_region_section_intersect_range(&tmp, offset, size)) { - continue; - } - ret =3D 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 =3D replay_by_populated_state(rdm, §ion, source, true, + rdl_populate_cb, rdl); if (ret) { break; } } =20 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 =3D *rdl2->section; - + replay_by_populated_state(rdm, §ion, source, true, + rdl_discard_cb, rdl2); if (rdl2 =3D=3D rdl) { break; } - if (!memory_region_section_intersect_range(&tmp, offset, size)= ) { - continue; - } - rdl2->notify_discard(rdl2, &tmp); } } return ret; } =20 void ram_discard_manager_notify_discard(RamDiscardManager *rdm, + RamDiscardSource *source, uint64_t offset, uint64_t size) { RamDiscardListener *rdl; - + MemoryRegionSection section =3D { + .mr =3D rdm->mr, + .offset_within_region =3D offset, + .size =3D 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 =3D *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); } } =20 -void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm) +void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm, + RamDiscardSource *source) { RamDiscardListener *rdl; =20 + g_assert(ram_discard_manager_find_source(rdm, source)); + QLIST_FOREACH(rdl, &rdm->rdl_list, next) { rdl->notify_discard(rdl, rdl->section); } } =20 -static int rdm_populate_cb(const MemoryRegionSection *section, void *opaqu= e) -{ - RamDiscardListener *rdl =3D opaque; - - return rdl->notify_populate(rdl, section); -} - void ram_discard_manager_register_listener(RamDiscardManager *rdm, RamDiscardListener *rdl, MemoryRegionSection *section) @@ -220,7 +553,7 @@ void ram_discard_manager_register_listener(RamDiscardMa= nager *rdm, QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); =20 ret =3D 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)); @@ -246,7 +579,7 @@ int ram_discard_manager_replay_populated_to_listeners(R= amDiscardManager *rdm) =20 QLIST_FOREACH(rdl, &rdm->rdl_list, next) { ret =3D ram_discard_manager_replay_populated(rdm, rdl->section, - rdm_populate_cb, rdl); + rdl_populate_cb, rdl); if (ret) { break; } --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580790; cv=none; d=zohomail.com; s=zohoarc; b=WTXs/A6yB7v4XlWzFJQvkRBTIqKpsG2H9JoLc9IRZff56aLHXamS1BBTsQuhF/fr4O8Ez0QNW0hYkavaIWtpTDSn4U05mYCiCQTJYJfGV78ahWoW8jqJF/eLgwkmRnyGD05KwMiVBF/mBJQUNljJjcMeHHZKZw27FiG1+CBeZjI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580790; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=oqupxWTIX5yPgysg6IEDJFTjLZcEKlZDanCawBFqwAg=; b=FteEmMh79lDi9+jAiSiZXUtdUFTY5wZirQVBwbTuMgsHBKQhar96t6unAoFUZMspI+RIxWmvXTEEwWiLXJnfqGG2fB9zkgUKl5V0UI6ucgd6oQbB4hfak79B0UcoY5szWEduTFKD5Ol5FoYIY/Dnbxj7ucrl9x20Vd+f0IkueBs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580790293535.1192768335776; Thu, 4 Jun 2026 06:46:30 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8OY-0002Ef-2W; Thu, 04 Jun 2026 09:46:02 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nw-0001cy-Gt for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:33 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nu-0000fF-Ev for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:24 -0400 Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-522-4cS6VzVNOreDKCoOMiHX5Q-1; Thu, 04 Jun 2026 09:45:18 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6332E195605C; Thu, 4 Jun 2026 13:45:16 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1D58A195608E; Thu, 4 Jun 2026 13:45:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580721; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oqupxWTIX5yPgysg6IEDJFTjLZcEKlZDanCawBFqwAg=; b=WW3rbOfCNNo81P9ODFuUjYgJy52B7TZyZFCavKoX7XIGPyF3QcY6yPDgFA4CmFWQnSZP2J RXofC3JBaL1TJjGKnmmXv/rPpe4vaUdQgBfnZJtEYhh4Mri5JxV7A4kXyW1WbXMAYRZ6bk 6LzakwId5Abv/6SfS0+KbrUpVmljRNI= X-MC-Unique: 4cS6VzVNOreDKCoOMiHX5Q-1 X-Mimecast-MFC-AGG-ID: 4cS6VzVNOreDKCoOMiHX5Q_1780580716 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:54 +0400 Subject: [PATCH v5 08/12] system/physmem: destroy ram block attributes before RCU-deferred reclaim MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-8-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=1538; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=nqr3DSm+dfPDdioJsSfk7uqz6VCxaipCaowmGHTPhEg=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYExpJKPAltEoHR0AycDz6wXPYowJyhmgItD+ pJnRKPtWBOJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMQAKCRDa6OEJdZac 5ZVbD/9RXRL6kPcKr9tBpgbhBXOJb1mXRRVSgQowhljff63fZrCncrc96zBbrCDRjKBw3FJpFm6 gKL4xh1oElSnyrPXmvAM3Jo6oQM+5ZbfV6bunKlyglq4BdGoDpfT864Dw98f+szgqgpfqkaR+vz uxnDKu3hKV5T/1Jz7UqSyy2q0v8FBAJ6Rt0MQREtmgzkcxVNQhnwFdtwKBHmN/jgHXeVOYcIg/e WgBEPIS4Zv72yqwggL6MZczKoE8s9QtOdmDJ06c67A8ANtzjm428fBSqBkSgGH4C77+c0bx+BAn 9fZx6eDVA8mCvN8Dx+9l+ibIhqI5Iz2Ys13HNdKaHY1XlC8cwafN5K39HSiVR+q1aUTuibN1Itj nhefdefIRCPbvzmBkoZRLxG9s5A0UJU4pAyDu34zXUlfnw9a6yDx5XwwtdjCRB28iww4FMkpj0g Gr66kraMI8cl3zQp3b2WR9ckQw7Hy/Eo8GTwNiFOOQa7hSUEgQoH97CfzGl1UiwZ8Yp8de2768G dJFczf8S+0YwPlKav/Sll7iDRvG0LSlziyWf2zCtbelq+MWXDtFM8+y6C2RTEOKqVpbHxOzrFLq nqq0oYN6lA1G2oDbkjVF3qP/CgdMIi+/j2uo6E5Ilr+lH+xjA44QvsBJUQanUGzaVzfiIVzw+Ko zTs6FqVbjDbzUyQ== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580792863154100 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 Signed-off-by: Marc-Andr=C3=A9 Lureau --- system/physmem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/physmem.c b/system/physmem.c index 7bcbf875736..ddb67ba2359 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -2583,7 +2583,6 @@ static void reclaim_ramblock(RAMBlock *block) } =20 if (block->guest_memfd >=3D 0) { - ram_block_attributes_destroy(block->attributes); close(block->guest_memfd); ram_block_coordinated_discard_require(false); } @@ -2612,6 +2611,7 @@ void qemu_ram_free(RAMBlock *block) /* Write list before version */ smp_wmb(); ram_list.version++; + g_clear_pointer(&block->attributes, ram_block_attributes_destroy); call_rcu(block, reclaim_ramblock, rcu); qemu_mutex_unlock_ramlist(); } --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580833; cv=none; d=zohomail.com; s=zohoarc; b=T3OfSWh+2DiS+/5tH9svS5cpfN7Qw1hGNg7xYpTCogoIGcGZCBL1zNR5MTB+pIsB+L3nplnD8VysgmoKBDpr/bZfrq06phT4OQ0L4/xZzif8L9V257MnzfgIV12FFrlc776Fz6s/Y2IAFZGtNaN7VpfiPoyVrhkW16nYgqVlb50= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580833; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=aH6Eqk16uEEVgv59pgvAwDG2vlInhXcrzcoLd+raoTs=; b=ZhtCwg6FoG5Gc8j8V5XUnnRgtWz5X5vh+TzEYc4wnBd3kriDzFK8juAw2GNe+sH1LtxusT9MCQy5yvuivqpnx5G4h3HtNSa4U6c4ccY2N9RS7JwjHcp6zGgU1KjZL+o7/zfhJnr8NcT+ZXzDJVj+Wmg6tzfKFm13CB5d2oQqgUU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580833055297.7984047517971; Thu, 4 Jun 2026 06:47:13 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Oe-0002sS-Po; Thu, 04 Jun 2026 09:46:08 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8O1-0001ei-Vw for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:35 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8Nx-0000lv-S1 for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:29 -0400 Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-294-SMMecOxFN9Cyl8NUcN-big-1; Thu, 04 Jun 2026 09:45:23 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 0B1CF19560AF; Thu, 4 Jun 2026 13:45:22 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 8E4AF195608E; Thu, 4 Jun 2026 13:45:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580725; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aH6Eqk16uEEVgv59pgvAwDG2vlInhXcrzcoLd+raoTs=; b=ROrHLvCoW0CpIAImftiYXLU0XsqWfk9JUo9YPhOFr2uecanEvVSZoJJQ1xwfTzj/5yvPRE 13/Qta/tULBmdGDqIkuQE09vs7YXstiQfHszEwaQlg1kzd/FL3qxpmva32MeCq/2+CZ9qi eklYZ86iOmAsV7MyRr8yVj25W+zglkA= X-MC-Unique: SMMecOxFN9Cyl8NUcN-big-1 X-Mimecast-MFC-AGG-ID: SMMecOxFN9Cyl8NUcN-big_1780580722 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:55 +0400 Subject: [PATCH v5 09/12] system/memory: add RamDiscardManager reference counting and cleanup MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-9-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=2561; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=xibSwiLSpvPuooM/HvA6wtCkxyx7qnL4hNrMlkAzRkA=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYExpKbg/LwepGCYR9pYZ3/BEmPej2pbcVndO mQaZhcwNLuJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMQAKCRDa6OEJdZac 5W+5D/47xePRGJNbZvGYkwXmXgP0LZbMiUUhMv2DI+QEsRdpF7wOIA5clZ51zIN808rcijNf2yW XQsNfnrzJ4nsSZh2MQKJ5eQmL4h2D0KqIgLyHHX1Og+bthiXns7vbefF3daipOOQl52+DtEG3rz MZmkhjwnNKXQ+Ckz9HfDnZxdoO/wmyjrbM6DfQlGuUzgIYaDYWJXuH1BNlKUG+r23c4/hFt7uwH U3M0JWTeuqArKobCd7z+kTfn+BSa9ucotKNXW7ugSG3UmGsTQnhjXeQre0oHAp9FuWE6fj/9ISA h6C+s5s00KJCmhrP+98roNSCnBZwUUNSHM3dJnodTvO5oNciRBXMcwFZNEaU1HzAjPKc6ByB1Ve vEA37D4yYw0N7ItCcf9gPivAdvQ3v6+FxVP2ocs/amrlvu6ocCCfOX/AHQhcTj2lj2Txy+lyRzb 1yCQ/ByUGixYict5RAcNYVzjGVc4CXrm/kPKHCnREqFnBr8CgLNWEmR1/w6JjbA78LRD9Iwa771 CAHY1TCaBNuTYePrV1x5gZz8tG3KggN4gf+AB96ULI8X3ewK+1D++eYup0mTgKS4jInRDAQt3G3 ShnsdBQb6qgPsctZIkQXMbz/yGAyNa1DE27cuQxu1AUUwAK+OM45cKfrOgbF7pzFBYBvur4tQ1l IvPTyGf4HWkc6kQ== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580835544154100 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 Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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 7c75eef1cec..ee1a1a1cd53 100644 --- a/system/memory.c +++ b/system/memory.c @@ -1755,6 +1755,7 @@ 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); } =20 Object *memory_region_owner(const MemoryRegion *mr) @@ -2058,11 +2059,18 @@ int memory_region_add_ram_discard_source(MemoryRegi= on *mr, int memory_region_del_ram_discard_source(MemoryRegion *mr, RamDiscardSource *source) { + int ret; g_assert(mr->rdm); =20 - return ram_discard_manager_del_source(mr->rdm, source); - - /* if there is no source and no listener left, we could free rdm */ + ret =3D ram_discard_manager_del_source(mr->rdm, source); + if (ret !=3D 0) { + return ret; + } + if (QLIST_EMPTY(&mr->rdm->source_list) && QLIST_EMPTY(&mr->rdm->rdl_li= st)) { + object_unref(mr->rdm); + mr->rdm =3D NULL; + } + return 0; } =20 /* Called with rcu_read_lock held. */ diff --git a/system/ram-discard-manager.c b/system/ram-discard-manager.c index 7da91bf648a..4e8816e5a2f 100644 --- a/system/ram-discard-manager.c +++ b/system/ram-discard-manager.c @@ -549,6 +549,7 @@ void ram_discard_manager_register_listener(RamDiscardMa= nager *rdm, =20 g_assert(section->mr =3D=3D rdm->mr); =20 + object_ref(rdm); rdl->section =3D memory_region_section_new_copy(section); QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next); =20 @@ -570,6 +571,7 @@ void ram_discard_manager_unregister_listener(RamDiscard= Manager *rdm, memory_region_section_free_copy(rdl->section); rdl->section =3D NULL; QLIST_REMOVE(rdl, next); + object_unref(rdm); } =20 int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *r= dm) --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580835; cv=none; d=zohomail.com; s=zohoarc; b=Yt9KuKd/PFwuFfKxgAlcYN1vPZgP02eLUz35HUY6KYCw8l5GvIVjAANx7C+b3wpHgdpPET+jdh98B744khbiQRRQ4bsiW+Q4lXyUmZmdae4rF6t8KlEhtbz8VYzwalYZA0/bp5egkmhqRrUfs/qIIzCjWc5XX1babpMqv9DN4JE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580835; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=jg1iWOZ1gqOxmOnlkFA7nAIWqYbRDnV4xGUvud/zfD4=; b=H0bjEYX4PWmPaGAq6C1p6/iqYEPQkzF0pExVQ9yFicqgxDZOXRnqcdVOfEFL76SXLhmWF+j/Qf8pcc0ePTaZ8qtUenc20oHRI8Fp6Y+4QHxVMlPAfwCxiPJHN/ZF9Dd73+KMVCDKRMIt861W+OFnxvZayjjfKNtWiRHDipB4qFY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580835566531.8826573481373; Thu, 4 Jun 2026 06:47:15 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Oe-0002sh-Rd; Thu, 04 Jun 2026 09:46:08 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OI-0001mf-HF for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:48 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OC-000159-1x for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:45 -0400 Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-315-uLSwx0iNOTOSMK600RDNjw-1; Thu, 04 Jun 2026 09:45:30 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DF896180059B; Thu, 4 Jun 2026 13:45:28 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1B78C195608E; Thu, 4 Jun 2026 13:45:25 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580737; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=jg1iWOZ1gqOxmOnlkFA7nAIWqYbRDnV4xGUvud/zfD4=; b=e5acZ+/ZY1SZETBTEDzwd8qvGzmWUIM1946j+tGXIt1pxFYnexeKcGNZXMv312tIZJhlfJ 1UbgKV0RLxC7nlnaBCIMkLarBNGhXeO6K40Lqbt9VgXyMK/wTSnQLS/svmWT4+p0LgkIaV szffoqGjdNJ4lbamhAa3V4a05Dw6Bec= X-MC-Unique: uLSwx0iNOTOSMK600RDNjw-1 X-Mimecast-MFC-AGG-ID: uLSwx0iNOTOSMK600RDNjw_1780580729 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:56 +0400 Subject: [PATCH v5 10/12] tests: add unit tests for RamDiscardManager multi-source aggregation MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-10-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=49735; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=+NdtVljm9XvBbSFH5VtQavnqZUu5X6pJ/qvYq5fuG3A=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEyEQ7egPLrbteO/Awa1MT/GrTYkd8YLNiB3 NBpl2AO5aKJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMgAKCRDa6OEJdZac 5QdwD/9zvqh5Eb9ASgQ7hSx9ZKgq6U9ZZ7/AI8j2m9abtpxuZmIofjTXYwisfkY76Kl8JEA9LxH fyNz7KOj4UMBiVNMfCK7TCMSIHXeUUkQSurra/zEt8UN2j5QLDMNdAWgBCBJzp9iZK8RR3NBdAR 7H40AIVmBs2n90qprMGo3Kv/B+ybrtMpb9z0Y98xECGSOFKHOmF30SbcRDQiNtv2a2e9mp3S4ub FFLrYSvy8aeg3XQ3S2xk6jgy3XTOtO5f8dKXWq8fWAAhIHe9+odOZY5AzZop+WOQexl3FA+3cPQ 1UMYn8SqhaytGJkToVoFoFdeXuO06lWeF4MF5ju6Jeq77EjqTpabHDbBwnvB//k1DnwzF5PfMjA K8NvlakPtGHjnkhrEoztU3HuGHJX4QBByDpL59CggfLdAUWJyF0dSjwyJ9wIbEeyGvGUFw4nVCV yhZEMQ6TJFyBO15QHrg0p341iSwJfG8nXhhpH6vipcOKLNy5zfd8M1VayMG2u2t2Zcmg0du/efk CL3c0YwS1TkNXCkxWwoK/I4JvoIcUJjvMt866YBwj3fwN/5CLpqpGZfnRWgc5v+rVwXGNAmXPui r9MwPQNKfw4p6Kh1+JWd6bXK+3/3RzTng48Vk4LuIGKpqPpUEdUiwJtETlJTIuEtLbPB5OYfF88 vXJycaSP1TPk7Ew== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -24 X-Spam_score: -2.5 X-Spam_bar: -- X-Spam_report: (-2.5 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580837384158500 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=C3=A9 Lureau --- 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(-) diff --git a/MAINTAINERS b/MAINTAINERS index 25dbb9f1a77..0cb909c0671 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3359,6 +3359,8 @@ 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 =20 Memory devices M: David Hildenbrand 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 00000000000..74b126fd3f9 --- /dev/null +++ b/tests/unit/test-ram-discard-manager-stubs.c @@ -0,0 +1,48 @@ +/* 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 =3D 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 =3D 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 =3D g_new(MemoryRegionSection, 1); + *copy =3D *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-di= scard-manager.c new file mode 100644 index 00000000000..3d39a1e94ba --- /dev/null +++ b/tests/unit/test-ram-discard-manager.c @@ -0,0 +1,1235 @@ +/* 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 =3D TEST_RAM_DISCARD_SOURCE(rds); + + g_assert(mr =3D=3D src->mr); + return src->granularity; +} + +static bool test_rds_is_populated(const RamDiscardSource *rds, + const MemoryRegionSection *section) +{ + TestRamDiscardSource *src =3D TEST_RAM_DISCARD_SOURCE(rds); + uint64_t offset =3D section->offset_within_region; + uint64_t size =3D int128_get64(section->size); + uint64_t first_bit =3D offset / src->granularity; + uint64_t last_bit =3D (offset + size - 1) / src->granularity; + unsigned long found; + + g_assert(section->mr =3D=3D src->mr); + + /* Check if any bit in range is zero (discarded) */ + found =3D 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 =3D RAM_DISCARD_SOURCE_CLASS(klass); + + rdsc->get_min_granularity =3D test_rds_get_min_granularity; + rdsc->is_populated =3D test_rds_is_populated; +} + +static const TypeInfo test_rds_info =3D { + .name =3D TYPE_TEST_RAM_DISCARD_SOURCE, + .parent =3D TYPE_OBJECT, + .instance_size =3D sizeof(TestRamDiscardSource), + .class_init =3D test_rds_class_init, + .interfaces =3D (const InterfaceInfo[]) { + { TYPE_RAM_DISCARD_SOURCE }, + { } + }, +}; + +static TestRamDiscardSource *test_source_new(MemoryRegion *mr, + uint64_t granularity) +{ + TestRamDiscardSource *src; + uint64_t region_size =3D memory_region_size(mr); + + src =3D TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURC= E)); + src->mr =3D mr; + src->granularity =3D granularity; + src->bitmap_size =3D DIV_ROUND_UP(region_size, granularity); + src->bitmap =3D 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 =3D offset / src->granularity; + uint64_t nbits =3D 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 =3D offset / src->granularity; + uint64_t nbits =3D 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 =3D container_of(rdl, TestListener, rdl); + + tl->populate_call_num++; + if (tl->fail_on_populate > 0 && + tl->populate_call_num >=3D tl->fail_on_populate) { + return -ENOMEM; + } + + tl->populate_count++; + tl->last_populate_offset =3D section->offset_within_region; + tl->last_populate_size =3D int128_get64(section->size); + return 0; +} + +static void test_listener_discard(RamDiscardListener *rdl, + const MemoryRegionSection *section) +{ + TestListener *tl =3D container_of(rdl, TestListener, rdl); + + tl->discard_count++; + tl->last_discard_offset =3D section->offset_within_region; + tl->last_discard_size =3D 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 =3D g_new0(MemoryRegion, 1); + test_mr->size =3D int128_make64(TEST_REGION_SIZE); + test_mr->ram =3D 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 =3D NULL; +} + +static void test_single_source_basic(void) +{ + TestRamDiscardSource *src; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + g_assert_null(rdm); + + /* Add source */ + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + =3D=3D, GRANULARITY_4K); + + /* Initially all discarded */ + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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 =3D int128_make64(GRANULARITY_4K * 4); + g_assert_true(ram_discard_manager_is_populated(rdm, §ion)); + + /* Check section that spans populated and discarded */ + section.size =3D 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 =3D { 0, }; + int ret; + + test_setup(); + + src =3D 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 =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + g_assert_nonnull(rdm); + + /* Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 2); + + /* Notify populate for new range */ + tl.populate_count =3D 0; + test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + GRANULARITY_4K * 16, + GRANULARITY_4K * 2); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl.populate_count, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_offset, =3D=3D, GRANULARITY_4K * 16); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, GRANULARITY_4K * 2); + + /* Notify discard */ + tl.discard_count =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_discard_offset, =3D=3D, 0); + g_assert_cmpuint(tl.last_discard_size, =3D=3D, 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 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Add first source */ + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + + /* Add second source */ + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D 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), + =3D=3D, GRANULARITY_4K); + + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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) =3D 4K. + */ +static void test_two_sources_different_granularity(void) +{ + TestRamDiscardSource *src_4k, *src_2m; + RamDiscardManager *rdm; + MemoryRegionSection section; + int ret; + + test_setup(); + + src_4k =3D test_source_new(test_mr, GRANULARITY_4K); + src_2m =3D test_source_new(test_mr, GRANULARITY_2M); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_4k)); + g_assert_cmpint(ret, =3D=3D, 0); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src_2m)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr), + =3D=3D, GRANULARITY_4K); + + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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 bloc= k */ + 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_4= k) */ + section.offset_within_region =3D 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_2= m)); + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4= k)); + + 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 =3D { 0, }; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D test_source_new(test_mr, GRANULARITY_4K); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 0); + + /* Populate in src1 only - no notification (src2 still discarded) */ + test_source_populate(src1, 0, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c1), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl.populate_count, =3D=3D, 0); + + /* Populate same range in src2 - now should notify */ + test_source_populate(src2, 0, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c2), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl.populate_count, =3D=3D, 1); + + /* Discard from src1 - should notify discard immediately */ + tl.discard_count =3D 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, =3D=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src1 */ + test_source_populate(src1, 0, GRANULARITY_4K * 8); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpint(tl.last_populate_offset, =3D=3D, 0); + g_assert_cmpint(tl.last_populate_size, =3D=3D, 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 region= s */ + tl.discard_count =3D 0; + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 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 bey= ond + * 4K*8 were already discarded in src1, so adding src2 doesn't change = them. + */ + g_assert_cmpint(tl.discard_count, =3D=3D, 1); + g_assert_cmpint(tl.last_discard_offset, =3D=3D, GRANULARITY_4K * 4); + g_assert_cmpint(tl.last_discard_size, =3D=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D 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 =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, GRANULARITY_4K * 4); + + /* Remove src2 - blocks 4-8 should become populated */ + tl.populate_count =3D 0; + memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2)= ); + + /* Listener should be notified about newly populated region (4K*4 to 4= K*8) */ + g_assert_cmpint(tl.populate_count, >=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Populate some range in src */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + /* 1. Add source */ + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* 2. Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, GRANULARITY_4K * 8); + + /* 3. Remove source */ + tl.populate_count =3D 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 popul= ated. + */ + g_assert_cmpint(tl.populate_count, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_offset, =3D=3D, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, TEST_REGION_SIZE - GRA= NULARITY_4K * 8); + + /* 4. Add source back */ + tl.discard_count =3D 0; + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + + /* + * Now we have 1 source again. The range (32K to end) is discarded aga= in. + * Listener should be notified about this discard. + */ + g_assert_cmpint(tl.discard_count, =3D=3D, 1); + g_assert_cmpuint(tl.last_discard_offset, =3D=3D, GRANULARITY_4K * 8); + g_assert_cmpuint(tl.last_discard_size, =3D=3D, TEST_REGION_SIZE - GRAN= ULARITY_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 =3D test_source_new(test_mr, GRANULARITY_4K); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + + /* Adding same source again should fail */ + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, -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 =3D { 0, }, tl2 =3D { 0, }; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register two listeners */ + test_listener_init(&tl1); + test_listener_init(&tl2); + tl2.fail_on_populate =3D 1; /* Second listener fails on first populat= e */ + + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D int128_make64(TEST_REGION_SIZE); + + /* + * Register tl2 first so it's visited second (QLIST_INSERT_HEAD revers= es + * 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 =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, =3D=3D, -ENOMEM); + + /* First listener should have received populate then discard (rollback= ) */ + g_assert_cmpint(tl1.populate_count, =3D=3D, 1); + g_assert_cmpint(tl1.discard_count, =3D=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D 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 =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener - should only get notified about intersection */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_offset, =3D=3D, GRANULARITY_4K * 4); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Add sources */ + ret =3D memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOUR= CE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + ret =3D memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOUR= CE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D 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 =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c1), + 0, GRANULARITY_4K); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl.populate_count, =3D=3D, 1); + + /* Now Discard src1 -> Aggregate Discarded */ + tl.discard_count =3D 0; + test_source_discard(src1, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, G= RANULARITY_4K); + g_assert_cmpint(tl.discard_count, =3D=3D, 1); + + /* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */ + /* Listener should NOT receive another discard notification for the sa= me range. */ + test_source_discard(src2, 0, GRANULARITY_4K); + ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, G= RANULARITY_4K); + + g_assert_cmpint(tl.discard_count, =3D=3D, 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 =3D { 0, }; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Populate blocks 0-7 */ + test_source_populate(src, 0, GRANULARITY_4K * 8); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D 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 =3D test_mr; + section.offset_within_region =3D GRANULARITY_4K * 2; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_offset, =3D=3D, GRANULARITY_4K * 2); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, GRANULARITY_4K * 4); + + /* Discard block 0 - outside listener's section, no notification */ + tl.discard_count =3D 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, =3D=3D, 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_discard_offset, =3D=3D, GRANULARITY_4K * 3); + + /* Discard spanning boundary (blocks 5-6) - only block 5 in section */ + tl.discard_count =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_discard_offset, =3D=3D, GRANULARITY_4K * 5); + g_assert_cmpuint(tl.last_discard_size, =3D=3D, 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 =3D { 0, }, tl2 =3D { 0, }; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-3 */ + test_listener_init(&tl1); + section1.mr =3D test_mr; + section1.offset_within_region =3D 0; + section1.size =3D 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 =3D test_mr; + section2.offset_within_region =3D GRANULARITY_4K * 8; + section2.size =3D 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, =3D=3D, 0); + g_assert_cmpint(tl2.populate_count, =3D=3D, 0); + + /* Populate blocks 0-3 - only tl1 should be notified */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 1); + g_assert_cmpint(tl2.populate_count, =3D=3D, 0); + + /* Populate blocks 8-11 - only tl2 should be notified */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 1); + g_assert_cmpint(tl2.populate_count, =3D=3D, 1); + + /* Populate blocks 4-7 (gap) - neither listener should be notified */ + test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 1); + g_assert_cmpint(tl2.populate_count, =3D=3D, 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 =3D { 0, }, tl2 =3D { 0, }; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Listener 1: blocks 0-7 */ + test_listener_init(&tl1); + section1.mr =3D test_mr; + section1.offset_within_region =3D 0; + section1.size =3D 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 =3D test_mr; + section2.offset_within_region =3D GRANULARITY_4K * 4; + section2.size =3D 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 =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + GRANULARITY_4K * 4, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 1); + g_assert_cmpint(tl2.populate_count, =3D=3D, 1); + + /* Populate blocks 0-3 - only tl1 */ + test_source_populate(src, 0, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + 0, GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 2); + g_assert_cmpint(tl2.populate_count, =3D=3D, 1); + + /* Populate blocks 8-11 - only tl2 */ + test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4); + ret =3D ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(sr= c), + GRANULARITY_4K * 8, + GRANULARITY_4K * 4); + g_assert_cmpint(ret, =3D=3D, 0); + g_assert_cmpint(tl1.populate_count, =3D=3D, 2); + g_assert_cmpint(tl2.populate_count, =3D=3D, 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 =3D { 0, }; + uint64_t last_offset; + int ret; + + test_setup(); + + src =3D test_source_new(test_mr, GRANULARITY_4K); + + /* Populate last 4 blocks of the region */ + last_offset =3D TEST_REGION_SIZE - GRANULARITY_4K * 4; + test_source_populate(src, last_offset, GRANULARITY_4K * 4); + + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src)); + g_assert_cmpint(ret, =3D=3D, 0); + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + /* Register listener for exactly the last 4 blocks */ + test_listener_init(&tl); + section.mr =3D test_mr; + section.offset_within_region =3D last_offset; + section.size =3D 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, =3D=3D, 1); + g_assert_cmpuint(tl.last_populate_offset, =3D=3D, last_offset); + g_assert_cmpuint(tl.last_populate_size, =3D=3D, GRANULARITY_4K * 4); + + /* Discard exactly at boundary */ + tl.discard_count =3D 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, =3D=3D, 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 =3D opaque; + *count +=3D 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 =3D 0; + int ret; + + test_setup(); + + src1 =3D test_source_new(test_mr, GRANULARITY_4K); + src2 =3D 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 =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src1)); + g_assert_cmpint(ret, =3D=3D, 0); + ret =3D memory_region_add_ram_discard_source(test_mr, + RAM_DISCARD_SOURCE(src2)); + g_assert_cmpint(ret, =3D=3D, 0); + + rdm =3D memory_region_get_ram_discard_manager(test_mr); + + section.mr =3D test_mr; + section.offset_within_region =3D 0; + section.size =3D int128_make64(GRANULARITY_4K * 8); + + /* Count discarded blocks */ + ret =3D ram_discard_manager_replay_discarded(rdm, §ion, + count_discarded_blocks, &co= unt); + + g_assert_cmpint(ret, =3D=3D, 0); + /* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) =3D 6 blo= cks */ + g_assert_cmpint(count, =3D=3D, 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-granularit= y", + 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-liste= ner", + test_remove_source_with_listener); + g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listen= er", + 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 de64f9501f2..f3c4ad0fc58 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -137,7 +137,13 @@ 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 +=3D {'test-util-filemonitor': []} --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580846; cv=none; d=zohomail.com; s=zohoarc; b=f//shseebu+UBzh51RSe5YuyPPUjaSPChKJQsPl9VCMd735UPPcuj6bkkm4StG2LejMrygNWUSVQYbs1xbRJsCWgrfobvJha81elECZHXDYsLyRFocYDcbwm9AurUj+vsUVPmjxakTvxtpm7rmeTooCj1/Zs6X2HrK9Sy+NEn/A= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580846; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=3CqFxIqjMMdGXqKKnL+tbt++sUy9MTe8z+qAn52XsHg=; b=MFWjSmxGVZi7sRV0OXdsHxoNmsVRYGo0RqFUly05Zwr8qfMFO5wSbep9ahjpkkcm7gN36yOt7uIYhNhkunDx6je2zV8j1Vhwxw8gdFBgxy7/j/iHTotUoch9PcaCBjLcwkkFhqeUtSP691cj3HxPJwJQM78xypAI7cLArZaZKEc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580846870860.1687806894357; Thu, 4 Jun 2026 06:47:26 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Of-0002ua-BU; Thu, 04 Jun 2026 09:46:09 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OF-0001k7-AV for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:46 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OC-00019V-Lu for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:42 -0400 Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-655-hIXEbtZ2Oea0dXoPKwf-Ow-1; Thu, 04 Jun 2026 09:45:36 -0400 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 58F601956089; Thu, 4 Jun 2026 13:45:34 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id AB63E1955BC0; Thu, 4 Jun 2026 13:45:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580739; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3CqFxIqjMMdGXqKKnL+tbt++sUy9MTe8z+qAn52XsHg=; b=fiz31SKDegD+fOqJ0/PzntGCF0GGX3FXbAajRagBAFCtYOog6TF3m7LmNOHpjQeemFp5zn E6NZqPomhnB4spEM77ZgidS/vgLBNgmK2TmacgBS8gZsfjD5fcp1dFk9jZY041BVbFAKf0 9yYLpocCJq+MEEwEpAU3JDsCx2+P3sI= X-MC-Unique: hIXEbtZ2Oea0dXoPKwf-Ow-1 X-Mimecast-MFC-AGG-ID: hIXEbtZ2Oea0dXoPKwf-Ow_1780580734 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:57 +0400 Subject: [PATCH v5 11/12] system/physmem: make ram_block_discard_range() handle guest_memfd MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-11-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , Xiaoyao Li , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=6549; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=BfzRj9+PxZBuWnruTJpFaGdqesXc0dlzBlWko+GiOlg=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEyCdxsURB1AYlGa1B17jLxB6W6D7m5G2RE6 ymjLAOxw0GJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMgAKCRDa6OEJdZac 5YorD/90sr8de39/rv34Sc0bwAgHrajghrQW4um+atQdLhevyScbCk2Py7uBuN+8Q0DTwHaAIUp Nw8xKH6ECa8QVvDckPABb3h2R28R+Rze6X9UjJaHervUzFnq42+14pGBYANu0FoJ58Wn4CZaWAN cdoypI9gjaVmL+mCIDnigSCE16CncY4t21VUoo+5GP0BKA3KMZGdy5f0ZdNbkFBUbdfkbEqEQWG K89z9z2wdPQz++ikpN+Mhxw9MaVPrntmEYCFgxUGeBdx0Pfo0IPLecLUzGBQdZUNHcFlxG5Vxuj 1kxDFITQSCgBGy5z6WfAgPsIt1SwRtloKxzBty/k0VQYLbpv5QXziDziqe1rek1QMyuXeHOFq/C YSGsTOOXh7/EiKigMWc7IzOdbeCzvHION07ftzeO7KQVXwSbYPKm4VNf9RUPA0kFYOMpHv3c9Gi UwoE65+qVYF8idk0iUTUxZBGySLpxX8wcIude6+Svu11qh9j6xcdvyBrjLNb6KYu8rdkC6mNEmD MBH7MPtT4uzBhZ412faz4qX+y/wmsLP6RD/pu4XzbLYlJE+auZTEqPkX2umeCzjCkNN57DIsuOd XKUs9YLkAdZIkGZGSJuiwXmxs6W0aHsa8F+qc7b/KL27Y1u+LAfQE6Gxj81rQZy9TAWIpQRhqac JPteGbOCY/PZV8A== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580847744154100 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 >=3D 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 Reviewed-by: Xiaoyao Li Signed-off-by: Marc-Andr=C3=A9 Lureau --- 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 2b38718fe5f..f0639287bf0 100644 --- a/include/system/ramblock.h +++ b/include/system/ramblock.h @@ -103,7 +103,8 @@ struct RamBlockAttributes { =20 /* @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); =20 diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c index 96f90ebb240..dcfb99cdf74 100644 --- a/accel/kvm/kvm-all.c +++ b/accel/kvm/kvm-all.c @@ -3426,7 +3426,7 @@ int kvm_convert_memory(hwaddr start, hwaddr size, boo= l to_private) */ goto out_unref; } - ret =3D ram_block_discard_range(rb, offset, size); + ret =3D ram_block_discard_shared_range(rb, offset, size); } else { ret =3D ram_block_discard_guest_memfd_range(rb, offset, size); } diff --git a/system/physmem.c b/system/physmem.c index ddb67ba2359..0ff80a74917 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -4095,7 +4095,7 @@ int qemu_ram_foreach_block(RAMBlockIterFunc func, voi= d *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 l= ength) { int ret =3D -1; =20 @@ -4144,7 +4144,7 @@ int ram_block_discard_range(RAMBlock *rb, uint64_t of= fset, 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. @@ -4200,8 +4200,9 @@ int ram_block_discard_range(RAMBlock *rb, uint64_t of= fset, 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, le= ngth, + need_madvise, need_fallocate, + ret); } else { error_report("%s: Overrun block '%s' (%" PRIu64 "/%zx/" RAM_ADDR_F= MT")", __func__, rb->idstr, offset, length, rb->max_length); @@ -4211,6 +4212,22 @@ err: return ret; } =20 +int ram_block_discard_range(RAMBlock *rb, uint64_t offset, size_t length) +{ + int ret; + + ret =3D ram_block_discard_shared_range(rb, offset, length); + if (ret) { + return ret; + } + + if (rb->guest_memfd >=3D 0) { + ret =3D 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 4714c9d514e..fcb11aa67e4 100644 --- a/target/i386/kvm/tdx.c +++ b/target/i386/kvm/tdx.c @@ -385,7 +385,7 @@ static void tdx_finalize_vm(Notifier *notifier, void *u= nused) * KVM_MEMORY_MAPPING. It becomes useless. */ ram_block =3D 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); =20 tdx_vm_ioctl(KVM_TDX_FINALIZE_VM, 0, NULL, &error_fatal); CONFIDENTIAL_GUEST_SUPPORT(tdx_guest)->ready =3D true; diff --git a/system/trace-events b/system/trace-events index e6e1b612798..51b4a4679a2 100644 --- a/system/trace-events +++ b/system/trace-events @@ -32,7 +32,7 @@ global_dirty_changed(unsigned int bitmask) "bitmask 0x%"P= RIx32 address_space_map(void *as, uint64_t addr, uint64_t len, bool is_write, ui= nt32_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, u= int64_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 fa= llocate: %d ret: %d" +ram_block_discard_shared_range(const char *rbname, void *hva, size_t lengt= h, 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" =20 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 eid= x 0x%08x section %u" --=20 2.54.0 From nobody Sun Jun 7 22:19:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1780580834; cv=none; d=zohomail.com; s=zohoarc; b=KSpmyTQc2ICl4niPvW19S2YizEfZwa1KoZDLif/8fcRHWA9quZVd1m9S7TrbIR+yJ1xVlYQjc27FmZY/kg6DF9IZdAlZlB9AubhdDCzvQCQOCA2QbcribCnLBLygxa3YHEXYJYpCgo3fjfiHQRXuBdghAS/GECSG5psBOijo2Qg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780580834; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=4Qk2+qTC/ZptS126mlZBTuBUK9Xm//0eYY52so2sEX8=; b=cWrysgLXJ73qx5G0sjKpSYy77LUdIbBB3zB7+xYZwKKHUKba/CqKdQH0/ACK7fPUQKfnYWu3sipLo1bjrh9v3WqdtsfgPbS8KCpQJz9k0vc5R/CpDQI1C0/ou3j/D+IWqZfcLCHNTnHnmk8bbGNHyckHDRXprXxnmhftx+/UN4c= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780580834644494.3369553953412; Thu, 4 Jun 2026 06:47:14 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV8Oa-0002TX-IY; Thu, 04 Jun 2026 09:46:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OK-0001ne-Hw for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:50 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV8OI-0001J7-8L for qemu-devel@nongnu.org; Thu, 04 Jun 2026 09:45:47 -0400 Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-594-sHB9DW_8M3WnPz0-V5_KEw-1; Thu, 04 Jun 2026 09:45:43 -0400 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id AB340195609F; Thu, 4 Jun 2026 13:45:40 +0000 (UTC) Received: from localhost (unknown [10.44.24.6]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 7AC7D180049F; Thu, 4 Jun 2026 13:45:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1780580745; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4Qk2+qTC/ZptS126mlZBTuBUK9Xm//0eYY52so2sEX8=; b=HM8b+27c5Eh87eJ3n8oFd2TYaQRuegujUEYu/i+UNfJRAipL0CEdlI0RaxEjUfY6tsAQFm ICkScPxA4mmUJYSJYe4TD5kjvSbbZtFryxwiTPeKVKg9DqjOHF7uC0nOIkjMS8RmELv6rh rSOB/z/n33+SjVY3wSH9YHV1xrXuzl4= X-MC-Unique: sHB9DW_8M3WnPz0-V5_KEw-1 X-Mimecast-MFC-AGG-ID: sHB9DW_8M3WnPz0-V5_KEw_1780580740 From: =?utf-8?q?Marc-Andr=C3=A9_Lureau?= Date: Thu, 04 Jun 2026 17:43:58 +0400 Subject: [PATCH v5 12/12] RFC: monitor: add 'info ramblock-attributes' command MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260604-rdm5-v5-12-5768e6a0943d@redhat.com> References: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> In-Reply-To: <20260604-rdm5-v5-0-5768e6a0943d@redhat.com> To: qemu-devel@nongnu.org Cc: Zhenzhong Duan , "Michael S. Tsirkin" , David Hildenbrand , Paolo Bonzini , Peter Xu , =?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= , qemu-rust@nongnu.org, Alex Williamson , =?utf-8?q?C=C3=A9dric_Le_Goater?= , "Maciej S. Szmigiero" , Fabiano Rosas , Mark Kanda , Ben Chaney , Marcelo Tosatti , kvm@vger.kernel.org, "Dr. David Alan Gilbert" , Zhao Liu , Eric Blake , Markus Armbruster , =?utf-8?q?Marc-Andr=C3=A9_Lureau?= X-Developer-Signature: v=1; a=openpgp-sha256; l=8557; i=marcandre.lureau@redhat.com; h=from:subject:message-id; bh=SyRGQ/9CVUTbXvPLmC2d6rfZWAqfBUeosfx/J9loDVU=; b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBqIYEys+d/G6REnNvFNdV9GPUy6EU6lmfCxV5SS 1VqWaOGX/+JAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaiGBMgAKCRDa6OEJdZac 5X3QD/43KopmhpfJ7O2wtHVsi5AGEmd4A9DFBDSptNQo0ExXfj6Y6RgUxJo+VvgETkPrWvRdyrN 6ZeB3Fzsdo239PHHhh6WPt1F4tjEVic0JEcd1ke8AoNTR6WpyGFNECIg6zrV3sjuBTIZtT12Dy4 +KNDTXhxmSaBg/ZBjbtXZwRzVuO3e3CUTy/x6ifUXEcxVlLAIksh1uV7+nPpU+UhfWKgcptukMM NlpO3cIGV6oVoNKu9YcPqQlHAA3ylXUaFoSzblN/8J3BXjIoxk+NhgBhfLsr8oawVvMfQJ9VZvl qhnE+utnN4xch1AcWYL/tz5i2o6cosT2KQuRmiwdr7zQp5BRSXstTMZ+nHhonYm1vJtnhZN6Ipp tyUTUiF/q9GmSy4gBEuIconz1dYLx1wBf2ONRp3sA6rc1tc8xEBJ2VOkCdr/ZGZQLxGTNOCAd9b cLfgWQIZEZZmJBzc4k6/y/fPaXrxFh3jLczA+71ycYj2YRFUY932oeXyhJDqxBDIY8of07Ei+iv VdQBVjdu0dWQVLb7eQaXnbo77vlH2UHgIbCiri9h94JYwW3eTZXtvoCDn92WxbQgdZmbHilGKdq HyEEyZ9bjUm2gVgbKPWDGzlgKOg2YF/tBgxE78feJ1U0X6dcVcBYgXpeY7KK7OTYn0mW1kn72lR /A4ZnGstMoJ/6Xg== X-Developer-Key: i=marcandre.lureau@redhat.com; a=openpgp; fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: 8 X-Spam_score: 0.8 X-Spam_bar: / X-Spam_report: (0.8 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.445, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_SBL_CSS=3.335, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1780580835624154100 Add a new 'info ramblock-attributes' HMP command and the corresponding 'x-query-ramblock-attributes' QMP command to display the shared/private memory attributes for ram blocks. The QMP command returns structured data (RamBlockAttributesInfo list with per-range shared/populated attributes), while HMP formats it for human consumption. This is useful for debugging confidential guests (TDX, SNP) to inspect which memory regions are shared vs private, and their population state when a RamDiscardManager is present (e.g. virtio-mem). Signed-off-by: Marc-Andr=C3=A9 Lureau Acked-by: Dr. David Alan Gilbert --- qapi/machine.json | 56 ++++++++++++++++++++++++++++++++++ include/monitor/hmp.h | 1 + hw/core/machine-hmp-cmds.c | 32 +++++++++++++++++++ system/ram-block-attributes.c | 71 +++++++++++++++++++++++++++++++++++++++= ++++ hmp-commands-info.hx | 13 ++++++++ 5 files changed, 173 insertions(+) diff --git a/qapi/machine.json b/qapi/machine.json index 685e4e29b87..a1af5c61176 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -1738,6 +1738,62 @@ 'returns': 'HumanReadableText', 'features': [ 'unstable' ] } =20 +## +# @RamBlockAttributeRange: +# +# A contiguous range within a ram block with uniform attributes. +# +# @start: start offset in bytes within the ram block +# +# @length: length in bytes of the range +# +# @shared: true if the range is shared, false if private +# +# @populated: true if the entire range is fully populated across all +# RamDiscardManager sources; false if any sub-block is discarded. +# Only present when a RamDiscardManager is managing the block. +# +# Since: 11.1 +## +{ 'struct': 'RamBlockAttributeRange', + 'data': { 'start': 'uint64', + 'length': 'uint64', + 'shared': 'bool', + '*populated': 'bool' } } + +## +# @RamBlockAttributesInfo: +# +# Shared/private memory attributes for a ram block. +# +# @name: the ram block identifier +# +# @ranges: list of attribute ranges +# +# Since: 11.1 +## +{ 'struct': 'RamBlockAttributesInfo', + 'data': { 'name': 'str', + 'ranges': [ 'RamBlockAttributeRange' ] } } + +## +# @x-query-ramblock-attributes: +# +# Query ram block shared/private attributes. This is useful +# to debug confidential guests. +# +# Features: +# +# @unstable: This command is meant for debugging. +# +# Returns: list of ram block attributes +# +# Since: 11.1 +## +{ 'command': 'x-query-ramblock-attributes', + 'returns': [ 'RamBlockAttributesInfo' ], + 'features': [ 'unstable' ] } + ## # @x-query-roms: # diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h index 9258a049bff..c069d5de8d1 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -137,6 +137,7 @@ void hmp_info_dump(Monitor *mon, const QDict *qdict); void hmp_hotpluggable_cpus(Monitor *mon, const QDict *qdict); void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict); void hmp_info_memory_size_summary(Monitor *mon, const QDict *qdict); +void hmp_info_ramblock_attributes(Monitor *mon, const QDict *qdict); void hmp_info_replay(Monitor *mon, const QDict *qdict); void hmp_replay_break(Monitor *mon, const QDict *qdict); void hmp_replay_delete_break(Monitor *mon, const QDict *qdict); diff --git a/hw/core/machine-hmp-cmds.c b/hw/core/machine-hmp-cmds.c index 46846f741a2..122e1a0f735 100644 --- a/hw/core/machine-hmp-cmds.c +++ b/hw/core/machine-hmp-cmds.c @@ -24,6 +24,7 @@ #include "qapi/string-output-visitor.h" #include "qemu/error-report.h" #include "system/numa.h" +#include "system/ramblock.h" #include "hw/core/boards.h" =20 void hmp_info_cpus(Monitor *mon, const QDict *qdict) @@ -388,3 +389,34 @@ void hmp_info_memory_size_summary(Monitor *mon, const = QDict *qdict) } hmp_handle_error(mon, err); } + +void hmp_info_ramblock_attributes(Monitor *mon, const QDict *qdict) +{ + Error *err =3D NULL; + g_autoptr(RamBlockAttributesInfoList) list =3D NULL; + RamBlockAttributesInfoList *it; + + list =3D qmp_x_query_ramblock_attributes(&err); + if (hmp_handle_error(mon, err)) { + return; + } + + for (it =3D list; it; it =3D it->next) { + RamBlockAttributesInfo *rba =3D it->value; + RamBlockAttributeRangeList *r; + + monitor_printf(mon, "%s:\n", rba->name); + for (r =3D rba->ranges; r; r =3D r->next) { + RamBlockAttributeRange *range =3D r->value; + const char *shared =3D range->shared ? "shared" : "private"; + const char *pop =3D range->has_populated ? + (range->populated ? "+populated" : "-populated") : ""; + + monitor_printf(mon, + " 0x%016" PRIx64 "-0x%016" PRIx64 " %s%s\n", + range->start, + range->start + range->length - 1, + shared, pop); + } + } +} diff --git a/system/ram-block-attributes.c b/system/ram-block-attributes.c index 59ec7a28eb0..fb657ffca35 100644 --- a/system/ram-block-attributes.c +++ b/system/ram-block-attributes.c @@ -11,6 +11,7 @@ =20 #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qapi/qapi-commands-machine.h" #include "system/ramblock.h" #include "trace.h" =20 @@ -221,3 +222,73 @@ static void ram_block_attributes_class_init(ObjectClas= s *klass, rdsc->get_min_granularity =3D ram_block_attributes_rds_get_min_granula= rity; rdsc->is_populated =3D ram_block_attributes_rds_is_populated; } + +RamBlockAttributesInfoList *qmp_x_query_ramblock_attributes(Error **errp) +{ + RamBlockAttributesInfoList *head =3D NULL, **tail =3D &head; + RAMBlock *block; + size_t rba_block_size =3D ram_block_attributes_get_block_size(); + + RCU_READ_LOCK_GUARD(); + + RAMBLOCK_FOREACH(block) { + RamBlockAttributesInfo *rba; + RamBlockAttributeRangeList **range_tail; + RamBlockAttributes *attr =3D block->attributes; + RamDiscardManager *rdm; + unsigned long pos; + + if (!attr) { + continue; + } + + rdm =3D memory_region_get_ram_discard_manager(block->mr); + g_assert(rdm); + + rba =3D g_new0(RamBlockAttributesInfo, 1); + rba->name =3D g_strdup(block->idstr); + range_tail =3D &rba->ranges; + + pos =3D 0; + while (pos < attr->bitmap_size) { + bool is_shared =3D test_bit(pos, attr->bitmap); + unsigned long next; + uint64_t start_offset, length; + RamBlockAttributeRange *range; + + if (is_shared) { + next =3D find_next_zero_bit(attr->bitmap, + attr->bitmap_size, pos); + } else { + next =3D find_next_bit(attr->bitmap, + attr->bitmap_size, pos); + } + + start_offset =3D (uint64_t)pos * rba_block_size; + length =3D (uint64_t)(next - pos) * rba_block_size; + + range =3D g_new0(RamBlockAttributeRange, 1); + range->start =3D start_offset; + range->length =3D length; + range->shared =3D is_shared; + + { + MemoryRegionSection section =3D { + .mr =3D block->mr, + .offset_within_region =3D start_offset, + .size =3D int128_make64(length), + }; + range->has_populated =3D true; + range->populated =3D + ram_discard_manager_is_populated(rdm, §ion); + } + + QAPI_LIST_APPEND(range_tail, range); + pos =3D next; + } + + QAPI_LIST_APPEND(tail, rba); + } + + return head; +} diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx index 82134eb6c21..f119b9cfebc 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx @@ -761,6 +761,19 @@ SRST Dump all the ramblocks of the system. ERST =20 + { + .name =3D "ramblock-attributes", + .args_type =3D "", + .params =3D "", + .help =3D "Display ramblock shared/private attributes", + .cmd =3D hmp_info_ramblock_attributes, + }, + +SRST + ``info ramblock-attributes`` + Display the shared/private memory attributes for ram blocks. +ERST + { .name =3D "hotpluggable-cpus", .args_type =3D "", --=20 2.54.0