Files
xen/tools/tests/pdx/test-pdx.c
T
Weiqi Wang c161c11875 xen/pdx: fix offset-compression merge of a contained range
When sorting and merging overlapping ranges in
pfn_pdx_compression_setup(), the merged range is set to end where the
second range ends.  If the second range is fully contained in the first,
this truncates the first range, and the tail of it is then neither
compressible nor translated correctly.

Keep the end of the merged range as the maximum of both ends.

On x86 the ranges come from the SRAT memory affinity entries, and
overlapping entries for the same node are tolerated with a warning by the
NUMA code.  The caller's subsequent coverage check catches the truncated
range, so the effect is that PDX compression is disabled with a "RAM
region ... not covered" message rather than memory being mistranslated.

Add a test case that fails without this change.

Found with the ESBMC bounded model checker, and confirmed by running the
failing case natively against the unmodified code.

Fixes: c5c45bcbd6 ("pdx: introduce a new compression algorithm based on region offsets")
Assisted-by: Claude Code:claude-opus-5-5 # finding the issue with ESBMC, patch creation
Signed-off-by: Weiqi Wang <weiqi.wang-2@postgrad.manchester.ac.uk>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
2026-10-12 09:41:55 +02:00

302 lines
10 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Unit tests for PDX compression.
*
* Copyright (C) 2025 Cloud Software Group
*/
#include "harness.h"
#include "../../xen/common/pdx.c"
struct range {
/* Ranges are defined as [start, end). */
unsigned long start, end;
};
static void print_ranges(const struct range *r)
{
unsigned int i;
printf("Ranges:\n");
for ( i = 0; i < MAX_RANGES; i++ )
{
if ( !r[i].start && !r[i].end )
break;
printf(" %013lx-%013lx\n", r[i].start, r[i].end);
}
}
int main(int argc, char **argv)
{
static const struct {
struct range ranges[MAX_RANGES];
bool compress;
} tests[] = {
#ifdef __LP64__
/*
* Only for targets where unsigned long is 64bits, otherwise compiler
* will complain about truncation from 'long long' -> 'long' conversion.
*
* Real memory map from a 4s Intel GNR. Not compressible using PDX
* mask compression.
*/
{
.ranges = {
{ .start = 0, .end = 0x80000UL },
{ .start = 0x0100000UL, .end = 0x8080000UL },
{ .start = 0x63e80000UL, .end = 0x6be80000UL },
{ .start = 0xc7e80000UL, .end = 0xcfe80000UL },
{ .start = 0x12be80000UL, .end = 0x133e80000UL },
},
#ifdef CONFIG_PDX_OFFSET_COMPRESSION
.compress = true,
#else
.compress = false,
#endif
},
/* Simple hole. */
{
.ranges = {
{ .start = 0,
.end = (1UL << MAX_ORDER) * 1 },
{ .start = (1UL << (MAX_ORDER * 2)) | 0,
.end = (1UL << (MAX_ORDER * 2)) | (1UL << MAX_ORDER) * 1 },
},
.compress = true,
},
/* Simple hole, unsorted ranges. */
{
.ranges = {
{ .start = (1UL << (MAX_ORDER * 2)) | 0,
.end = (1UL << (MAX_ORDER * 2)) | (1UL << MAX_ORDER) * 1 },
{ .start = 0,
.end = (1UL << MAX_ORDER) * 1 },
},
.compress = true,
},
/* Two ranges with the same high bit set. */
{
.ranges = {
{ .start = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 0,
.end = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 1 },
{ .start = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 3,
.end = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 4 },
},
.compress = true,
},
#endif
/* Range contained in a previous one. */
{
.ranges = {
/* Overlapping ranges. */
{ .start = 0, .end = (1UL << MAX_ORDER) * 2 },
{ .start = 0, .end = (1UL << MAX_ORDER) * 1 },
/* Extra range to force offset compression to be engaged. */
{ .start = (1UL << MAX_ORDER) * 3,
.end = (1UL << MAX_ORDER) * 4 },
},
#ifdef CONFIG_PDX_OFFSET_COMPRESSION
.compress = true,
#else
.compress = false,
#endif
},
/* PDX compression, 2 ranges covered by the lower mask. */
{
.ranges = {
{ .start = 0,
.end = (1 << MAX_ORDER) * 1 },
{ .start = (1 << MAX_ORDER) * 2,
.end = (1 << MAX_ORDER) * 3 },
{ .start = (1 << MAX_ORDER) * 20,
.end = (1 << MAX_ORDER) * 22 },
},
.compress = true,
},
/* Single range not starting at 0. */
{
.ranges = {
{ .start = (1 << MAX_ORDER) * 10,
.end = (1 << MAX_ORDER) * 11 },
},
.compress = true,
},
/* Resulting PDX region size leads to no compression. */
{
.ranges = {
{ .start = 0,
.end = (1 << MAX_ORDER) * 1 },
{ .start = (1 << MAX_ORDER) * 2,
.end = (1 << MAX_ORDER) * 3 },
{ .start = (1 << MAX_ORDER) * 4,
.end = (1 << MAX_ORDER) * 7 },
{ .start = (1 << MAX_ORDER) * 8,
.end = (1 << MAX_ORDER) * 12 },
},
.compress = false,
},
/* AMD Versal Gen 2 ARM board. */
{
.ranges = {
{ .start = 0, .end = 0x80000UL },
{ .start = 0x800000UL, .end = 0x880000UL },
{ .start = 0x50000000UL, .end = 0x50080000UL },
{ .start = 0x60000000UL, .end = 0x60080000UL },
{ .start = 0x70000000UL, .end = 0x70080000UL },
},
.compress = true,
},
/* Unsorted ranges, lower one not starting at 0. */
{
.ranges = {
{ .start = (1UL << (35 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 2,
.end = (1UL << (35 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 3 },
{ .start = (1 << MAX_ORDER) * 2,
.end = (1 << MAX_ORDER) * 3 },
},
.compress = true,
},
/* AMD Naples Epyc 7281 2 sockets, 8 NUMA nodes. */
{
.ranges = {
{ .start = 0, .end = 0xa0UL },
{ .start = 0x100UL, .end = 0xb0000UL },
{ .start = 0x100000UL, .end = 0x430000UL },
{ .start = 0x430000UL, .end = 0x830000UL },
{ .start = 0x830000UL, .end = 0xc30000UL },
{ .start = 0xc30000UL, .end = 0x1030000UL },
{ .start = 0x1030000UL, .end = 0x1430000UL },
{ .start = 0x1430000UL, .end = 0x1830000UL },
{ .start = 0x1830000UL, .end = 0x1c30000UL },
{ .start = 0x1c30000UL, .end = 0x2030000UL },
},
.compress = false,
},
/* 2-node 2GB per-node QEMU layout. */
{
.ranges = {
{ .start = 0, .end = 0x80000UL },
{ .start = 0x100000UL, .end = 0x180000UL },
},
.compress = true,
},
/* Not compressible, smaller than MAX_ORDER. */
{
.ranges = {
{ .start = 0, .end = 1 },
{ .start = 0x100UL, .end = 0x101UL },
},
.compress = false,
},
/* Compressible, requires adjusting size to (1 << MAX_ORDER). */
{
.ranges = {
{ .start = 0, .end = 1 },
{ .start = 0x100000UL, .end = 0x100001UL },
},
.compress = true,
},
/* 2s Intel CLX with contiguous ranges, no compression. */
{
.ranges = {
{ .start = 0 , .end = 0x180000UL },
{ .start = 0x180000UL, .end = 0x3040000UL },
},
.compress = false,
},
/*
* Dell R740, dual socket. Merging of ranges causes mask differences
* in PDX offset mode. Useful for checking mask calculations.
*/
{
.ranges = {
{ .start = 0x0000000UL, .end = 0x0080000UL },
{ .start = 0x0100000UL, .end = 0x3070000UL },
{ .start = 0x3070000UL, .end = 0x3870000UL },
{ .start = 0x3870000UL, .end = 0x6870000UL },
{ .start = 0x6870000UL, .end = 0x7070000UL },
},
.compress = false,
},
};
int ret_code = EXIT_SUCCESS;
for ( unsigned int i = 0 ; i < ARRAY_SIZE(tests); i++ )
{
unsigned int j;
pfn_pdx_compression_reset();
for ( j = 0; j < ARRAY_SIZE(tests[i].ranges); j++ )
{
unsigned long size = tests[i].ranges[j].end -
tests[i].ranges[j].start;
if ( !tests[i].ranges[j].start && !tests[i].ranges[j].end )
break;
pfn_pdx_add_region(pfn_to_paddr(tests[i].ranges[j].start),
pfn_to_paddr(size));
}
if ( pfn_pdx_compression_setup(0) != tests[i].compress )
{
printf("PFN compression diverge, expected %scompressible\n",
tests[i].compress ? "" : "un");
print_ranges(tests[i].ranges);
ret_code = EXIT_FAILURE;
continue;
}
if ( !tests[i].compress )
continue;
for ( j = 0; j < ARRAY_SIZE(tests[i].ranges); j++ )
{
unsigned long start = tests[i].ranges[j].start;
unsigned long end = tests[i].ranges[j].end;
if ( !start && !end )
break;
if ( !pdx_is_region_compressible(pfn_to_paddr(start), 1) ||
!pdx_is_region_compressible(pfn_to_paddr(end - 1), 1) )
{
printf(
"PFN compression invalid, pages %#lx and %#lx should be compressible\n",
start, end - 1);
print_ranges(tests[i].ranges);
ret_code = EXIT_FAILURE;
}
if ( start != pdx_to_pfn(pfn_to_pdx(start)) ||
end - 1 != pdx_to_pfn(pfn_to_pdx(end - 1)) )
{
printf("Compression is not bi-directional:\n");
printf(" PFN %#lx -> PDX %#lx -> PFN %#lx\n",
start, pfn_to_pdx(start), pdx_to_pfn(pfn_to_pdx(start)));
printf(" PFN %#lx -> PDX %#lx -> PFN %#lx\n",
end - 1, pfn_to_pdx(end - 1),
pdx_to_pfn(pfn_to_pdx(end - 1)));
print_ranges(tests[i].ranges);
ret_code = EXIT_FAILURE;
}
}
}
return ret_code;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/