master
h 358 lines 13.2 KB
Raw
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3 * RAM Discard Manager
4 *
5 * Copyright Red Hat, Inc. 2026
6 */
7
8 #ifndef RAM_DISCARD_MANAGER_H
9 #define RAM_DISCARD_MANAGER_H
10
11 #include "qemu/typedefs.h"
12 #include "qom/object.h"
13 #include "qemu/queue.h"
14
15 #define TYPE_RAM_DISCARD_MANAGER "ram-discard-manager"
16 typedef struct RamDiscardManagerClass RamDiscardManagerClass;
17 typedef struct RamDiscardManager RamDiscardManager;
18 DECLARE_OBJ_CHECKERS(RamDiscardManager, RamDiscardManagerClass,
19 RAM_DISCARD_MANAGER, TYPE_RAM_DISCARD_MANAGER);
20
21 #define TYPE_RAM_DISCARD_SOURCE "ram-discard-source"
22 typedef struct RamDiscardSourceClass RamDiscardSourceClass;
23 typedef struct RamDiscardSource RamDiscardSource;
24 DECLARE_OBJ_CHECKERS(RamDiscardSource, RamDiscardSourceClass,
25 RAM_DISCARD_SOURCE, TYPE_RAM_DISCARD_SOURCE);
26
27 typedef struct RamDiscardListener RamDiscardListener;
28 typedef int (*NotifyRamPopulate)(RamDiscardListener *rdl,
29 const MemoryRegionSection *section);
30 typedef void (*NotifyRamDiscard)(RamDiscardListener *rdl,
31 const MemoryRegionSection *section);
32
33 struct RamDiscardListener {
34 /*
35 * @notify_populate:
36 *
37 * Notification that previously discarded memory is about to get populated.
38 * Listeners are able to object. If any listener objects, already
39 * successfully notified listeners are notified about a discard again.
40 *
41 * @rdl: the #RamDiscardListener getting notified
42 * @section: the #MemoryRegionSection to get populated. The section
43 * is aligned within the memory region to the minimum granularity
44 * unless it would exceed the registered section.
45 *
46 * Returns 0 on success. If the notification is rejected by the listener,
47 * an error is returned.
48 */
49 NotifyRamPopulate notify_populate;
50
51 /*
52 * @notify_discard:
53 *
54 * Notification that previously populated memory was discarded successfully
55 * and listeners should drop all references to such memory and prevent
56 * new population (e.g., unmap).
57 *
58 * @rdl: the #RamDiscardListener getting notified
59 * @section: the #MemoryRegionSection to get discarded. The section
60 * is aligned within the memory region to the minimum granularity
61 * unless it would exceed the registered section.
62 */
63 NotifyRamDiscard notify_discard;
64
65 MemoryRegionSection *section;
66 QLIST_ENTRY(RamDiscardListener) next;
67 };
68
69 static inline void ram_discard_listener_init(RamDiscardListener *rdl,
70 NotifyRamPopulate populate_fn,
71 NotifyRamDiscard discard_fn)
72 {
73 rdl->notify_populate = populate_fn;
74 rdl->notify_discard = discard_fn;
75 }
76
77 /**
78 * typedef ReplayRamDiscardState:
79 *
80 * The callback handler used by ram_discard_manager_replay_populated() and
81 * ram_discard_manager_replay_discarded() to invoke on populated/discarded
82 * parts.
83 *
84 * @section: the #MemoryRegionSection of populated/discarded part
85 * @opaque: pointer to forward to the callback
86 *
87 * Returns 0 on success, or a negative error if failed.
88 */
89 typedef int (*ReplayRamDiscardState)(const MemoryRegionSection *section,
90 void *opaque);
91
92 /*
93 * RamDiscardSourceClass:
94 *
95 * A #RamDiscardSource provides information about which parts of a specific
96 * RAM #MemoryRegion are currently populated (accessible) vs discarded.
97 *
98 * This is an interface that state providers (like virtio-mem or
99 * RamBlockAttributes) implement to provide discard state information. A
100 * #RamDiscardManager wraps sources and manages listener registrations and
101 * notifications.
102 */
103 struct RamDiscardSourceClass {
104 /* private */
105 InterfaceClass parent_class;
106
107 /* public */
108
109 /**
110 * @get_min_granularity:
111 *
112 * Get the minimum granularity in which listeners will get notified
113 * about changes within the #MemoryRegion via the #RamDiscardSource.
114 *
115 * @rds: the #RamDiscardSource
116 * @mr: the #MemoryRegion
117 *
118 * Returns the minimum granularity.
119 */
120 uint64_t (*get_min_granularity)(const RamDiscardSource *rds,
121 const MemoryRegion *mr);
122
123 /**
124 * @is_populated:
125 *
126 * Check whether the given #MemoryRegionSection is completely populated
127 * (i.e., no parts are currently discarded) via the #RamDiscardSource.
128 * There are no alignment requirements.
129 *
130 * @rds: the #RamDiscardSource
131 * @section: the #MemoryRegionSection
132 *
133 * Returns whether the given range is completely populated.
134 */
135 bool (*is_populated)(const RamDiscardSource *rds,
136 const MemoryRegionSection *section);
137 };
138
139 /**
140 * RamDiscardManager:
141 *
142 * A #RamDiscardManager coordinates which parts of specific RAM #MemoryRegion
143 * regions are currently populated to be used/accessed by the VM, notifying
144 * after parts were discarded (freeing up memory) and before parts will be
145 * populated (consuming memory), to be used/accessed by the VM.
146 *
147 * A #RamDiscardManager can only be set for a RAM #MemoryRegion while the
148 * #MemoryRegion isn't mapped into an address space yet (either directly
149 * or via an alias); it cannot change while the #MemoryRegion is
150 * mapped into an address space.
151 *
152 * The #RamDiscardManager is intended to be used by technologies that are
153 * incompatible with discarding of RAM (e.g., VFIO, which may pin all
154 * memory inside a #MemoryRegion), and require proper coordination to only
155 * map the currently populated parts, to hinder parts that are expected to
156 * remain discarded from silently getting populated and consuming memory.
157 * Technologies that support discarding of RAM don't have to bother and can
158 * simply map the whole #MemoryRegion.
159 *
160 * An example #RamDiscardSource is virtio-mem, which logically (un)plugs
161 * memory within an assigned RAM #MemoryRegion, coordinated with the VM.
162 * Logically unplugging memory consists of discarding RAM. The VM agreed to not
163 * access unplugged (discarded) memory - especially via DMA. virtio-mem will
164 * properly coordinate with listeners before memory is plugged (populated),
165 * and after memory is unplugged (discarded).
166 *
167 * Listeners are called in multiples of the minimum granularity (unless it
168 * would exceed the registered range) and changes are aligned to the minimum
169 * granularity within the #MemoryRegion. Listeners have to prepare for memory
170 * becoming discarded in a different granularity than it was populated and the
171 * other way around.
172 */
173
174 typedef struct RamDiscardSourceEntry RamDiscardSourceEntry;
175
176 struct RamDiscardSourceEntry {
177 RamDiscardSource *rds;
178 QLIST_ENTRY(RamDiscardSourceEntry) next;
179 };
180
181 struct RamDiscardManager {
182 Object parent;
183
184 MemoryRegion *mr;
185 QLIST_HEAD(, RamDiscardSourceEntry) source_list;
186 uint64_t min_granularity;
187 QLIST_HEAD(, RamDiscardListener) rdl_list;
188 };
189
190 RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr);
191
192 /**
193 * ram_discard_manager_add_source:
194 *
195 * Register a #RamDiscardSource with the #RamDiscardManager. The manager
196 * aggregates state from all registered sources using AND semantics: a region
197 * is considered populated only if ALL sources report it as populated.
198 *
199 * If listeners are already registered, they will be notified about any
200 * regions that become discarded due to adding this source. Specifically,
201 * for each region that the new source reports as discarded, if all other
202 * sources reported it as populated, listeners receive a discard notification.
203 *
204 * If any listener rejects the notification (returns an error), previously
205 * notified listeners are rolled back with populate notifications and the
206 * source is not added.
207 *
208 * @rdm: the #RamDiscardManager
209 * @source: the #RamDiscardSource to add
210 *
211 * Returns: 0 on success, -EBUSY if @source is already registered, or a
212 * negative error code if a listener rejected the state change.
213 */
214 int ram_discard_manager_add_source(RamDiscardManager *rdm,
215 RamDiscardSource *source);
216
217 /**
218 * ram_discard_manager_del_source:
219 *
220 * Unregister a #RamDiscardSource from the #RamDiscardManager.
221 *
222 * If listeners are already registered, they will be notified about any
223 * regions that become populated due to removing this source. Specifically,
224 * for each region that the removed source reported as discarded, if all
225 * remaining sources report it as populated, listeners receive a populate
226 * notification.
227 *
228 * If any listener rejects the notification (returns an error), previously
229 * notified listeners are rolled back with discard notifications and the
230 * source is not removed.
231 *
232 * @rdm: the #RamDiscardManager
233 * @source: the #RamDiscardSource to remove
234 *
235 * Returns: 0 on success, -ENOENT if @source is not registered, or a
236 * negative error code if a listener rejected the state change.
237 */
238 int ram_discard_manager_del_source(RamDiscardManager *rdm,
239 RamDiscardSource *source);
240
241
242 uint64_t ram_discard_manager_get_min_granularity(const RamDiscardManager *rdm,
243 const MemoryRegion *mr);
244
245 /**
246 * ram_discard_manager_is_populated:
247 *
248 * Check if the given memory region section is populated.
249 * If the manager has no sources, it is considered populated.
250 *
251 * @rdm: the #RamDiscardManager
252 * @section: the #MemoryRegionSection to check
253 *
254 * Returns: true if the section is populated, false otherwise.
255 */
256 bool ram_discard_manager_is_populated(const RamDiscardManager *rdm,
257 const MemoryRegionSection *section);
258
259 /**
260 * ram_discard_manager_replay_populated:
261 *
262 * Call @replay_fn on regions that are populated in all sources.
263 *
264 * @rdm: the #RamDiscardManager
265 * @section: the #MemoryRegionSection
266 * @replay_fn: the #ReplayRamDiscardState callback
267 * @opaque: pointer to forward to the callback
268 *
269 * Returns 0 on success, or a negative error if any notification failed.
270 */
271 int ram_discard_manager_replay_populated(const RamDiscardManager *rdm,
272 const MemoryRegionSection *section,
273 ReplayRamDiscardState replay_fn,
274 void *opaque);
275
276 /**
277 * ram_discard_manager_replay_discarded:
278 *
279 * Call @replay_fn on regions that are discarded in any sources.
280 *
281 * @rdm: the #RamDiscardManager
282 * @section: the #MemoryRegionSection
283 * @replay_fn: the #ReplayRamDiscardState callback
284 * @opaque: pointer to forward to the callback
285 *
286 * Returns 0 on success, or a negative error if any notification failed.
287 */
288 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm,
289 const MemoryRegionSection *section,
290 ReplayRamDiscardState replay_fn,
291 void *opaque);
292
293 void ram_discard_manager_register_listener(RamDiscardManager *rdm,
294 RamDiscardListener *rdl,
295 MemoryRegionSection *section);
296
297 void ram_discard_manager_unregister_listener(RamDiscardManager *rdm,
298 RamDiscardListener *rdl);
299
300 /**
301 * ram_discard_manager_notify_populate:
302 *
303 * Notify listeners that a region is about to be populated by a source.
304 * For multi-source aggregation, only notifies when all sources agree
305 * the region is populated (intersection).
306 *
307 * @rdm: the #RamDiscardManager
308 * @source: the #RamDiscardSource that is populating
309 * @offset: offset within the memory region
310 * @size: size of the region being populated
311 *
312 * Returns 0 on success, or a negative error if any listener rejects.
313 */
314 int ram_discard_manager_notify_populate(RamDiscardManager *rdm,
315 RamDiscardSource *source,
316 uint64_t offset, uint64_t size);
317
318 /**
319 * ram_discard_manager_notify_discard:
320 *
321 * Notify listeners that a region has been discarded by a source.
322 * For multi-source aggregation, always notifies immediately
323 * (union semantics - any source discarding makes region discarded).
324 *
325 * @rdm: the #RamDiscardManager
326 * @source: the #RamDiscardSource that is discarding
327 * @offset: offset within the memory region
328 * @size: size of the region being discarded
329 */
330 void ram_discard_manager_notify_discard(RamDiscardManager *rdm,
331 RamDiscardSource *source,
332 uint64_t offset, uint64_t size);
333
334 /**
335 * ram_discard_manager_notify_discard_all:
336 *
337 * Notify listeners that all regions have been discarded by a source.
338 *
339 * @rdm: the #RamDiscardManager
340 * @source: the #RamDiscardSource that is discarding
341 */
342 void ram_discard_manager_notify_discard_all(RamDiscardManager *rdm,
343 RamDiscardSource *source);
344
345 /**
346 * ram_discard_manager_replay_populated_to_listeners:
347 *
348 * Replay populated sections to all registered listeners.
349 * For multi-source aggregation, only replays regions where all sources
350 * are populated (intersection).
351 *
352 * @rdm: the #RamDiscardManager
353 *
354 * Returns 0 on success, or a negative error if any notification failed.
355 */
356 int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm);
357
358 #endif /* RAM_DISCARD_MANAGER_H */