Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
1236 lines
43 KiB
C
1236 lines
43 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/bitmap.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qapi/error.h"
|
|
#include "qom/object.h"
|
|
#include "qom/qom-qobject.h"
|
|
#include "glib.h"
|
|
#include "system/memory.h"
|
|
|
|
#define TYPE_TEST_RAM_DISCARD_SOURCE "test-ram-discard-source"
|
|
|
|
OBJECT_DECLARE_SIMPLE_TYPE(TestRamDiscardSource, TEST_RAM_DISCARD_SOURCE)
|
|
|
|
struct TestRamDiscardSource {
|
|
Object parent;
|
|
|
|
MemoryRegion *mr;
|
|
uint64_t granularity;
|
|
unsigned long *bitmap;
|
|
uint64_t bitmap_size;
|
|
};
|
|
|
|
static uint64_t test_rds_get_min_granularity(const RamDiscardSource *rds,
|
|
const MemoryRegion *mr)
|
|
{
|
|
TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds);
|
|
|
|
g_assert(mr == src->mr);
|
|
return src->granularity;
|
|
}
|
|
|
|
static bool test_rds_is_populated(const RamDiscardSource *rds,
|
|
const MemoryRegionSection *section)
|
|
{
|
|
TestRamDiscardSource *src = TEST_RAM_DISCARD_SOURCE(rds);
|
|
uint64_t offset = section->offset_within_region;
|
|
uint64_t size = int128_get64(section->size);
|
|
uint64_t first_bit = offset / src->granularity;
|
|
uint64_t last_bit = (offset + size - 1) / src->granularity;
|
|
unsigned long found;
|
|
|
|
g_assert(section->mr == src->mr);
|
|
|
|
/* Check if any bit in range is zero (discarded) */
|
|
found = find_next_zero_bit(src->bitmap, last_bit + 1, first_bit);
|
|
return found > last_bit;
|
|
}
|
|
|
|
static void test_rds_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass);
|
|
|
|
rdsc->get_min_granularity = test_rds_get_min_granularity;
|
|
rdsc->is_populated = test_rds_is_populated;
|
|
}
|
|
|
|
static const TypeInfo test_rds_info = {
|
|
.name = TYPE_TEST_RAM_DISCARD_SOURCE,
|
|
.parent = TYPE_OBJECT,
|
|
.instance_size = sizeof(TestRamDiscardSource),
|
|
.class_init = test_rds_class_init,
|
|
.interfaces = (const InterfaceInfo[]) {
|
|
{ TYPE_RAM_DISCARD_SOURCE },
|
|
{ }
|
|
},
|
|
};
|
|
|
|
static TestRamDiscardSource *test_source_new(MemoryRegion *mr,
|
|
uint64_t granularity)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
uint64_t region_size = memory_region_size(mr);
|
|
|
|
src = TEST_RAM_DISCARD_SOURCE(object_new(TYPE_TEST_RAM_DISCARD_SOURCE));
|
|
src->mr = mr;
|
|
src->granularity = granularity;
|
|
src->bitmap_size = DIV_ROUND_UP(region_size, granularity);
|
|
src->bitmap = bitmap_new(src->bitmap_size);
|
|
|
|
return src;
|
|
}
|
|
|
|
static void test_source_free(TestRamDiscardSource *src)
|
|
{
|
|
g_free(src->bitmap);
|
|
object_unref(OBJECT(src));
|
|
}
|
|
|
|
static void test_source_populate(TestRamDiscardSource *src,
|
|
uint64_t offset, uint64_t size)
|
|
{
|
|
uint64_t first_bit = offset / src->granularity;
|
|
uint64_t nbits = size / src->granularity;
|
|
|
|
bitmap_set(src->bitmap, first_bit, nbits);
|
|
}
|
|
|
|
static void test_source_discard(TestRamDiscardSource *src,
|
|
uint64_t offset, uint64_t size)
|
|
{
|
|
uint64_t first_bit = offset / src->granularity;
|
|
uint64_t nbits = size / src->granularity;
|
|
|
|
bitmap_clear(src->bitmap, first_bit, nbits);
|
|
}
|
|
|
|
typedef struct TestListener {
|
|
RamDiscardListener rdl;
|
|
int populate_count;
|
|
int discard_count;
|
|
uint64_t last_populate_offset;
|
|
uint64_t last_populate_size;
|
|
uint64_t last_discard_offset;
|
|
uint64_t last_discard_size;
|
|
int fail_on_populate; /* Return error on Nth populate */
|
|
int populate_call_num;
|
|
} TestListener;
|
|
|
|
static int test_listener_populate(RamDiscardListener *rdl,
|
|
const MemoryRegionSection *section)
|
|
{
|
|
TestListener *tl = container_of(rdl, TestListener, rdl);
|
|
|
|
tl->populate_call_num++;
|
|
if (tl->fail_on_populate > 0 &&
|
|
tl->populate_call_num >= tl->fail_on_populate) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tl->populate_count++;
|
|
tl->last_populate_offset = section->offset_within_region;
|
|
tl->last_populate_size = int128_get64(section->size);
|
|
return 0;
|
|
}
|
|
|
|
static void test_listener_discard(RamDiscardListener *rdl,
|
|
const MemoryRegionSection *section)
|
|
{
|
|
TestListener *tl = container_of(rdl, TestListener, rdl);
|
|
|
|
tl->discard_count++;
|
|
tl->last_discard_offset = section->offset_within_region;
|
|
tl->last_discard_size = int128_get64(section->size);
|
|
}
|
|
|
|
static void test_listener_init(TestListener *tl)
|
|
{
|
|
ram_discard_listener_init(&tl->rdl,
|
|
test_listener_populate,
|
|
test_listener_discard);
|
|
}
|
|
|
|
#define TEST_REGION_SIZE (16 * 1024 * 1024) /* 16 MB */
|
|
#define GRANULARITY_4K (4 * 1024)
|
|
#define GRANULARITY_2M (2 * 1024 * 1024)
|
|
|
|
static MemoryRegion *test_mr;
|
|
|
|
static void test_setup(void)
|
|
{
|
|
test_mr = g_new0(MemoryRegion, 1);
|
|
test_mr->size = int128_make64(TEST_REGION_SIZE);
|
|
test_mr->ram = true;
|
|
}
|
|
|
|
static void test_teardown(void)
|
|
{
|
|
g_clear_pointer(&test_mr->rdm, object_unref);
|
|
object_unparent(OBJECT(test_mr));
|
|
g_free(test_mr);
|
|
test_mr = NULL;
|
|
}
|
|
|
|
static void test_single_source_basic(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
g_assert_null(rdm);
|
|
|
|
/* Add source */
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
g_assert_nonnull(rdm);
|
|
|
|
g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
|
|
==, GRANULARITY_4K);
|
|
|
|
/* Initially all discarded */
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(GRANULARITY_4K);
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate a range in source */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 4);
|
|
|
|
/* Now should be populated */
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Check larger section */
|
|
section.size = int128_make64(GRANULARITY_4K * 4);
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Check section that spans populated and discarded */
|
|
section.size = int128_make64(GRANULARITY_4K * 8);
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
static void test_single_source_listener(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Populate some ranges before adding listener */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 4);
|
|
test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
g_assert_nonnull(rdm);
|
|
|
|
/* Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Should have been notified about populated regions */
|
|
g_assert_cmpint(tl.populate_count, ==, 2);
|
|
|
|
/* Notify populate for new range */
|
|
tl.populate_count = 0;
|
|
test_source_populate(src, GRANULARITY_4K * 16, GRANULARITY_4K * 2);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 16,
|
|
GRANULARITY_4K * 2);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 16);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 2);
|
|
|
|
/* Notify discard */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src, 0, GRANULARITY_4K * 4);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
|
|
0, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_discard_offset, ==, 0);
|
|
g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K * 4);
|
|
|
|
/* Unregister listener */
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
static void test_two_sources_same_granularity(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Add first source */
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Add second source */
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
g_assert_nonnull(rdm);
|
|
|
|
/* Check granularity */
|
|
g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
|
|
==, GRANULARITY_4K);
|
|
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(GRANULARITY_4K);
|
|
|
|
/* Both discarded -> aggregated discarded */
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate in src1 only */
|
|
test_source_populate(src1, 0, GRANULARITY_4K);
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate in src2 only */
|
|
test_source_discard(src1, 0, GRANULARITY_4K);
|
|
test_source_populate(src2, 0, GRANULARITY_4K);
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate in both -> aggregated populated */
|
|
test_source_populate(src1, 0, GRANULARITY_4K);
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Remove sources */
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Two sources with different granularities (4K and 2M).
|
|
* The aggregated granularity should be GCD(4K, 2M) = 4K.
|
|
*/
|
|
static void test_two_sources_different_granularity(void)
|
|
{
|
|
TestRamDiscardSource *src_4k, *src_2m;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src_4k = test_source_new(test_mr, GRANULARITY_4K);
|
|
src_2m = test_source_new(test_mr, GRANULARITY_2M);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src_4k));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src_2m));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
g_assert_cmpuint(ram_discard_manager_get_min_granularity(rdm, test_mr),
|
|
==, GRANULARITY_4K);
|
|
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(GRANULARITY_4K);
|
|
|
|
/* Both discarded */
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate 4K in src_4k, but src_2m still discarded the whole 2M block */
|
|
test_source_populate(src_4k, 0, GRANULARITY_4K);
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate 2M in src_2m (which includes the 4K block) */
|
|
test_source_populate(src_2m, 0, GRANULARITY_2M);
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Check a 4K block at offset 4K (populated in src_2m but not in src_4k) */
|
|
section.offset_within_region = GRANULARITY_4K;
|
|
g_assert_false(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
/* Populate it in src_4k */
|
|
test_source_populate(src_4k, GRANULARITY_4K, GRANULARITY_4K);
|
|
g_assert_true(ram_discard_manager_is_populated(rdm, §ion));
|
|
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_2m));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src_4k));
|
|
|
|
test_source_free(src_2m);
|
|
test_source_free(src_4k);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Notification with two sources.
|
|
* Populate notification should only fire when all sources are populated.
|
|
*/
|
|
static void test_two_sources_notification(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* No populate notifications yet (all discarded) */
|
|
g_assert_cmpint(tl.populate_count, ==, 0);
|
|
|
|
/* Populate in src1 only - no notification (src2 still discarded) */
|
|
test_source_populate(src1, 0, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1),
|
|
0, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl.populate_count, ==, 0);
|
|
|
|
/* Populate same range in src2 - now should notify */
|
|
test_source_populate(src2, 0, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src2),
|
|
0, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
|
|
/* Discard from src1 - should notify discard immediately */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src1, 0, GRANULARITY_4K * 2);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1),
|
|
0, GRANULARITY_4K * 2);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Adding source with existing listener.
|
|
* When a new source is added, listeners should be notified about
|
|
* regions that become discarded.
|
|
*/
|
|
static void test_add_source_with_listener(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Populate some range in src1 */
|
|
test_source_populate(src1, 0, GRANULARITY_4K * 8);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Should have been notified about populated region */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpint(tl.last_populate_offset, ==, 0);
|
|
g_assert_cmpint(tl.last_populate_size, ==, GRANULARITY_4K * 8);
|
|
|
|
/* src2 has part of the region populated, part discarded */
|
|
/* src2 has 0-4 populated, 4-8 discarded */
|
|
test_source_populate(src2, 0, GRANULARITY_4K * 4);
|
|
|
|
/* Add src2 - listener should be notified about newly discarded regions */
|
|
tl.discard_count = 0;
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/*
|
|
* The range 4K*4 to 4K*8 was populated in src1 but discarded in src2,
|
|
* so it becomes aggregated-discarded. Listener should be notified.
|
|
* Only this range should trigger a discard notification - regions beyond
|
|
* 4K*8 were already discarded in src1, so adding src2 doesn't change them.
|
|
*/
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
g_assert_cmpint(tl.last_discard_offset, ==, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(tl.last_discard_size, ==, GRANULARITY_4K * 4);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Removing source with existing listener.
|
|
* When a source is removed, listeners should be notified about
|
|
* regions that become populated.
|
|
*/
|
|
static void test_remove_source_with_listener(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* src1: all of first 8 blocks populated */
|
|
test_source_populate(src1, 0, GRANULARITY_4K * 8);
|
|
/* src2: only first 4 blocks populated */
|
|
test_source_populate(src2, 0, GRANULARITY_4K * 4);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Only first 4 blocks are aggregated-populated */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
|
|
|
|
/* Remove src2 - blocks 4-8 should become populated */
|
|
tl.populate_count = 0;
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
|
|
/* Listener should be notified about newly populated region (4K*4 to 4K*8) */
|
|
g_assert_cmpint(tl.populate_count, >=, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Add a source, register a listener, remove the source, then add it back.
|
|
* This checks the transition from 0 sources (all populated) to 1 source
|
|
* (partially discarded) with an active listener.
|
|
*/
|
|
static void test_readd_source_with_listener(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Populate some range in src */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 8);
|
|
|
|
/* 1. Add source */
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* 2. Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Listener notified about populated region (0 - 32K) */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 8);
|
|
|
|
/* 3. Remove source */
|
|
tl.populate_count = 0;
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
|
|
/*
|
|
* With 0 sources, everything is populated.
|
|
* The range that was discarded in src (from 32K to end) becomes populated.
|
|
*/
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 8);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8);
|
|
|
|
/* 4. Add source back */
|
|
tl.discard_count = 0;
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/*
|
|
* Now we have 1 source again. The range (32K to end) is discarded again.
|
|
* Listener should be notified about this discard.
|
|
*/
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 8);
|
|
g_assert_cmpuint(tl.last_discard_size, ==, TEST_REGION_SIZE - GRANULARITY_4K * 8);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Duplicate source registration should fail.
|
|
*/
|
|
static void test_duplicate_source(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Adding same source again should fail */
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, -EBUSY);
|
|
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Populate notification rollback on listener error.
|
|
*/
|
|
static void test_populate_rollback(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl1 = { 0, }, tl2 = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register two listeners */
|
|
test_listener_init(&tl1);
|
|
test_listener_init(&tl2);
|
|
tl2.fail_on_populate = 1; /* Second listener fails on first populate */
|
|
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
|
|
/*
|
|
* Register tl2 first so it's visited second (QLIST_INSERT_HEAD reverses
|
|
* registration order). This ensures tl1 receives populate before tl2
|
|
* fails.
|
|
*/
|
|
ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion);
|
|
ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion);
|
|
|
|
/* Try to populate - should fail and roll back */
|
|
test_source_populate(src, 0, GRANULARITY_4K);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
0, GRANULARITY_4K);
|
|
g_assert_cmpint(ret, ==, -ENOMEM);
|
|
|
|
/* First listener should have received populate then discard (rollback) */
|
|
g_assert_cmpint(tl1.populate_count, ==, 1);
|
|
g_assert_cmpint(tl1.discard_count, ==, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
|
|
ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Replay populated with two sources (intersection).
|
|
*/
|
|
static void test_replay_populated_intersection(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/*
|
|
* src1: blocks 0-7 populated
|
|
* src2: blocks 4-11 populated
|
|
* Intersection: blocks 4-7
|
|
*/
|
|
test_source_populate(src1, 0, GRANULARITY_4K * 8);
|
|
test_source_populate(src2, GRANULARITY_4K * 4, GRANULARITY_4K * 8);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener - should only get notified about intersection */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Should have been notified about blocks 4-7 (intersection) */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 4);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Empty region (no sources).
|
|
*/
|
|
static void test_no_sources(void)
|
|
{
|
|
test_setup();
|
|
|
|
/* No sources - should have no manager */
|
|
g_assert_null(memory_region_get_ram_discard_manager(test_mr));
|
|
g_assert_false(memory_region_has_ram_discard_manager(test_mr));
|
|
|
|
test_teardown();
|
|
}
|
|
|
|
static void test_redundant_discard(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Add sources */
|
|
ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
ret = memory_region_add_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(TEST_REGION_SIZE);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Populate intersection (0-4K) in both sources */
|
|
test_source_populate(src1, 0, GRANULARITY_4K);
|
|
test_source_populate(src2, 0, GRANULARITY_4K);
|
|
|
|
/* Notify populate src1 - should trigger listener populate */
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src1),
|
|
0, GRANULARITY_4K);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
|
|
/* Now Discard src1 -> Aggregate Discarded */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src1, 0, GRANULARITY_4K);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src1), 0, GRANULARITY_4K);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
|
|
/* Now Discard src2 -> Aggregate Discarded (Already Discarded!) */
|
|
/* Listener should NOT receive another discard notification for the same range. */
|
|
test_source_discard(src2, 0, GRANULARITY_4K);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src2), 0, GRANULARITY_4K);
|
|
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Listener with partial section coverage.
|
|
* Listener should only receive notifications for its registered range.
|
|
*/
|
|
static void test_partial_listener_section(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Populate blocks 0-7 */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 8);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener for only blocks 2-5 (not the full region) */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = GRANULARITY_4K * 2;
|
|
section.size = int128_make64(GRANULARITY_4K * 4);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Should be notified only about blocks 2-5 (intersection) */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_offset, ==, GRANULARITY_4K * 2);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
|
|
|
|
/* Discard block 0 - outside listener's section, no notification */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src, 0, GRANULARITY_4K);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
|
|
0, GRANULARITY_4K);
|
|
g_assert_cmpint(tl.discard_count, ==, 0);
|
|
|
|
/* Discard block 3 - inside listener's section */
|
|
test_source_discard(src, GRANULARITY_4K * 3, GRANULARITY_4K);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 3, GRANULARITY_4K);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 3);
|
|
|
|
/* Discard spanning boundary (blocks 5-6) - only block 5 in section */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src, GRANULARITY_4K * 5, GRANULARITY_4K * 2);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 5, GRANULARITY_4K * 2);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_discard_offset, ==, GRANULARITY_4K * 5);
|
|
g_assert_cmpuint(tl.last_discard_size, ==, GRANULARITY_4K);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Multiple listeners with different (non-overlapping) sections.
|
|
*/
|
|
static void test_multiple_listeners_different_sections(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section1, section2;
|
|
TestListener tl1 = { 0, }, tl2 = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Listener 1: blocks 0-3 */
|
|
test_listener_init(&tl1);
|
|
section1.mr = test_mr;
|
|
section1.offset_within_region = 0;
|
|
section1.size = int128_make64(GRANULARITY_4K * 4);
|
|
ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1);
|
|
|
|
/* Listener 2: blocks 8-11 */
|
|
test_listener_init(&tl2);
|
|
section2.mr = test_mr;
|
|
section2.offset_within_region = GRANULARITY_4K * 8;
|
|
section2.size = int128_make64(GRANULARITY_4K * 4);
|
|
ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2);
|
|
|
|
/* Initially all discarded - no populate notifications */
|
|
g_assert_cmpint(tl1.populate_count, ==, 0);
|
|
g_assert_cmpint(tl2.populate_count, ==, 0);
|
|
|
|
/* Populate blocks 0-3 - only tl1 should be notified */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
0, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 1);
|
|
g_assert_cmpint(tl2.populate_count, ==, 0);
|
|
|
|
/* Populate blocks 8-11 - only tl2 should be notified */
|
|
test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 8,
|
|
GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 1);
|
|
g_assert_cmpint(tl2.populate_count, ==, 1);
|
|
|
|
/* Populate blocks 4-7 (gap) - neither listener should be notified */
|
|
test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 4,
|
|
GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 1);
|
|
g_assert_cmpint(tl2.populate_count, ==, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
|
|
ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Multiple listeners with overlapping sections.
|
|
*/
|
|
static void test_overlapping_listener_sections(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section1, section2;
|
|
TestListener tl1 = { 0, }, tl2 = { 0, };
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Listener 1: blocks 0-7 */
|
|
test_listener_init(&tl1);
|
|
section1.mr = test_mr;
|
|
section1.offset_within_region = 0;
|
|
section1.size = int128_make64(GRANULARITY_4K * 8);
|
|
ram_discard_manager_register_listener(rdm, &tl1.rdl, §ion1);
|
|
|
|
/* Listener 2: blocks 4-11 (overlaps with tl1 on blocks 4-7) */
|
|
test_listener_init(&tl2);
|
|
section2.mr = test_mr;
|
|
section2.offset_within_region = GRANULARITY_4K * 4;
|
|
section2.size = int128_make64(GRANULARITY_4K * 8);
|
|
ram_discard_manager_register_listener(rdm, &tl2.rdl, §ion2);
|
|
|
|
/* Populate blocks 4-7 (overlap region) - both should be notified */
|
|
test_source_populate(src, GRANULARITY_4K * 4, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 4,
|
|
GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 1);
|
|
g_assert_cmpint(tl2.populate_count, ==, 1);
|
|
|
|
/* Populate blocks 0-3 - only tl1 */
|
|
test_source_populate(src, 0, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
0, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 2);
|
|
g_assert_cmpint(tl2.populate_count, ==, 1);
|
|
|
|
/* Populate blocks 8-11 - only tl2 */
|
|
test_source_populate(src, GRANULARITY_4K * 8, GRANULARITY_4K * 4);
|
|
ret = ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(src),
|
|
GRANULARITY_4K * 8,
|
|
GRANULARITY_4K * 4);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
g_assert_cmpint(tl1.populate_count, ==, 2);
|
|
g_assert_cmpint(tl2.populate_count, ==, 2);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl2.rdl);
|
|
ram_discard_manager_unregister_listener(rdm, &tl1.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
/*
|
|
* Test: Listener at exact memory region boundaries.
|
|
*/
|
|
static void test_boundary_section(void)
|
|
{
|
|
TestRamDiscardSource *src;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
TestListener tl = { 0, };
|
|
uint64_t last_offset;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/* Populate last 4 blocks of the region */
|
|
last_offset = TEST_REGION_SIZE - GRANULARITY_4K * 4;
|
|
test_source_populate(src, last_offset, GRANULARITY_4K * 4);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
/* Register listener for exactly the last 4 blocks */
|
|
test_listener_init(&tl);
|
|
section.mr = test_mr;
|
|
section.offset_within_region = last_offset;
|
|
section.size = int128_make64(GRANULARITY_4K * 4);
|
|
ram_discard_manager_register_listener(rdm, &tl.rdl, §ion);
|
|
|
|
/* Should receive notification for the populated range */
|
|
g_assert_cmpint(tl.populate_count, ==, 1);
|
|
g_assert_cmpuint(tl.last_populate_offset, ==, last_offset);
|
|
g_assert_cmpuint(tl.last_populate_size, ==, GRANULARITY_4K * 4);
|
|
|
|
/* Discard exactly at boundary */
|
|
tl.discard_count = 0;
|
|
test_source_discard(src, last_offset, GRANULARITY_4K * 4);
|
|
ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(src),
|
|
last_offset, GRANULARITY_4K * 4);
|
|
g_assert_cmpint(tl.discard_count, ==, 1);
|
|
|
|
ram_discard_manager_unregister_listener(rdm, &tl.rdl);
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src));
|
|
test_source_free(src);
|
|
test_teardown();
|
|
}
|
|
|
|
static int count_discarded_blocks(const MemoryRegionSection *section,
|
|
void *opaque)
|
|
{
|
|
int *count = opaque;
|
|
*count += int128_get64(section->size) / GRANULARITY_4K;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Test: replay_discarded with two sources (union semantics).
|
|
*/
|
|
static void test_replay_discarded(void)
|
|
{
|
|
TestRamDiscardSource *src1, *src2;
|
|
RamDiscardManager *rdm;
|
|
MemoryRegionSection section;
|
|
int count = 0;
|
|
int ret;
|
|
|
|
test_setup();
|
|
|
|
src1 = test_source_new(test_mr, GRANULARITY_4K);
|
|
src2 = test_source_new(test_mr, GRANULARITY_4K);
|
|
|
|
/*
|
|
* src1: blocks 0-3 populated, rest discarded
|
|
* src2: blocks 2-5 populated, rest discarded
|
|
* Aggregated populated: blocks 2-3 (intersection)
|
|
* Aggregated discarded: blocks 0-1, 4-5, 6+ (union of discarded)
|
|
*/
|
|
test_source_populate(src1, 0, GRANULARITY_4K * 4);
|
|
test_source_populate(src2, GRANULARITY_4K * 2, GRANULARITY_4K * 4);
|
|
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src1));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
ret = memory_region_add_ram_discard_source(test_mr,
|
|
RAM_DISCARD_SOURCE(src2));
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
rdm = memory_region_get_ram_discard_manager(test_mr);
|
|
|
|
section.mr = test_mr;
|
|
section.offset_within_region = 0;
|
|
section.size = int128_make64(GRANULARITY_4K * 8);
|
|
|
|
/* Count discarded blocks */
|
|
ret = ram_discard_manager_replay_discarded(rdm, §ion,
|
|
count_discarded_blocks, &count);
|
|
|
|
g_assert_cmpint(ret, ==, 0);
|
|
/* Discarded: blocks 0-1 (2), blocks 4-5 (2), blocks 6-7 (2) = 6 blocks */
|
|
g_assert_cmpint(count, ==, 6);
|
|
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src2));
|
|
memory_region_del_ram_discard_source(test_mr, RAM_DISCARD_SOURCE(src1));
|
|
|
|
test_source_free(src2);
|
|
test_source_free(src1);
|
|
test_teardown();
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
module_call_init(MODULE_INIT_QOM);
|
|
type_register_static(&test_rds_info);
|
|
|
|
g_test_add_func("/ram-discard-manager/single-source/basic",
|
|
test_single_source_basic);
|
|
g_test_add_func("/ram-discard-manager/single-source/listener",
|
|
test_single_source_listener);
|
|
g_test_add_func("/ram-discard-manager/two-sources/same-granularity",
|
|
test_two_sources_same_granularity);
|
|
g_test_add_func("/ram-discard-manager/two-sources/different-granularity",
|
|
test_two_sources_different_granularity);
|
|
g_test_add_func("/ram-discard-manager/two-sources/notification",
|
|
test_two_sources_notification);
|
|
g_test_add_func("/ram-discard-manager/dynamic/add-source-with-listener",
|
|
test_add_source_with_listener);
|
|
g_test_add_func("/ram-discard-manager/dynamic/remove-source-with-listener",
|
|
test_remove_source_with_listener);
|
|
g_test_add_func("/ram-discard-manager/dynamic/readd-source-with-listener",
|
|
test_readd_source_with_listener);
|
|
g_test_add_func("/ram-discard-manager/edge/duplicate-source",
|
|
test_duplicate_source);
|
|
g_test_add_func("/ram-discard-manager/edge/populate-rollback",
|
|
test_populate_rollback);
|
|
g_test_add_func("/ram-discard-manager/edge/replay-intersection",
|
|
test_replay_populated_intersection);
|
|
g_test_add_func("/ram-discard-manager/edge/no-sources",
|
|
test_no_sources);
|
|
g_test_add_func("/ram-discard-manager/multi-source/redundant-discard",
|
|
test_redundant_discard);
|
|
g_test_add_func("/ram-discard-manager/listener/partial-section",
|
|
test_partial_listener_section);
|
|
g_test_add_func("/ram-discard-manager/listener/multiple-different",
|
|
test_multiple_listeners_different_sections);
|
|
g_test_add_func("/ram-discard-manager/listener/overlapping",
|
|
test_overlapping_listener_sections);
|
|
g_test_add_func("/ram-discard-manager/edge/boundary-section",
|
|
test_boundary_section);
|
|
g_test_add_func("/ram-discard-manager/multi-source/replay-discarded",
|
|
test_replay_discarded);
|
|
|
|
return g_test_run();
|
|
}
|