1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * Unit tests for PDX compression.
4  *
5  * Copyright (C) 2025 Cloud Software Group
6  */
7 
8 #include "harness.h"
9 
10 #include "../../xen/common/pdx.c"
11 
12 struct range {
13     /* Ranges are defined as [start, end). */
14     unsigned long start, end;
15 };
16 
print_ranges(const struct range * r)17 static void print_ranges(const struct range *r)
18 {
19     unsigned int i;
20 
21     printf("Ranges:\n");
22 
23     for ( i = 0; i < MAX_RANGES; i++ )
24     {
25         if ( !r[i].start && !r[i].end )
26             break;
27 
28         printf(" %013lx-%013lx\n", r[i].start, r[i].end);
29     }
30 }
31 
main(int argc,char ** argv)32 int main(int argc, char **argv)
33 {
34     static const struct {
35         struct range ranges[MAX_RANGES];
36         bool compress;
37     } tests[] = {
38 #ifdef __LP64__
39         /*
40          * Only for targets where unsigned long is 64bits, otherwise compiler
41          * will complain about truncation from 'long long' -> 'long' conversion.
42          *
43          * Real memory map from a 4s Intel GNR.  Not compressible using PDX
44          * mask compression.
45          */
46         {
47             .ranges = {
48                 { .start =           0,   .end =     0x80000UL },
49                 { .start =   0x0100000UL, .end =   0x8080000UL },
50                 { .start =  0x63e80000UL, .end =  0x6be80000UL },
51                 { .start =  0xc7e80000UL, .end =  0xcfe80000UL },
52                 { .start = 0x12be80000UL, .end = 0x133e80000UL },
53             },
54 #ifdef CONFIG_PDX_OFFSET_COMPRESSION
55             .compress = true,
56 #else
57             .compress = false,
58 #endif
59         },
60         /* Simple hole. */
61         {
62             .ranges = {
63                 { .start =                                                 0,
64                   .end   =                            (1UL << MAX_ORDER) * 1 },
65                 { .start = (1UL << (MAX_ORDER * 2)) |                      0,
66                   .end   = (1UL << (MAX_ORDER * 2)) | (1UL << MAX_ORDER) * 1 },
67             },
68             .compress = true,
69         },
70         /* Simple hole, unsorted ranges. */
71         {
72             .ranges = {
73                 { .start = (1UL << (MAX_ORDER * 2)) |                      0,
74                   .end   = (1UL << (MAX_ORDER * 2)) | (1UL << MAX_ORDER) * 1 },
75                 { .start =                                                 0,
76                   .end   =                            (1UL << MAX_ORDER) * 1 },
77             },
78             .compress = true,
79         },
80         /* PDX compression, 2 ranges covered by the lower mask. */
81         {
82             .ranges = {
83                 { .start =                    0,
84                   .end   = (1 << MAX_ORDER) * 1 },
85                 { .start = (1 << MAX_ORDER) * 2,
86                   .end   = (1 << MAX_ORDER) * 3 },
87                 { .start = (1 << MAX_ORDER) * 20,
88                   .end   = (1 << MAX_ORDER) * 22 },
89             },
90             .compress = true,
91         },
92         /* Single range not starting at 0. */
93         {
94             .ranges = {
95                 { .start = (1 << MAX_ORDER) * 10,
96                   .end   = (1 << MAX_ORDER) * 11 },
97             },
98             .compress = true,
99         },
100         /* Resulting PDX region size leads to no compression. */
101         {
102             .ranges = {
103                 { .start =                    0,
104                   .end   = (1 << MAX_ORDER) * 1 },
105                 { .start = (1 << MAX_ORDER) * 2,
106                   .end   = (1 << MAX_ORDER) * 3 },
107                 { .start = (1 << MAX_ORDER) * 4,
108                   .end   = (1 << MAX_ORDER) * 7 },
109                 { .start = (1 << MAX_ORDER) * 8,
110                   .end   = (1 << MAX_ORDER) * 12 },
111             },
112             .compress = false,
113         },
114         /* AMD Versal Gen 2 ARM board. */
115         {
116             .ranges = {
117                 { .start =          0,   .end =    0x80000UL },
118                 { .start =   0x800000UL, .end =   0x880000UL },
119                 { .start = 0x50000000UL, .end = 0x50080000UL },
120                 { .start = 0x60000000UL, .end = 0x60080000UL },
121                 { .start = 0x70000000UL, .end = 0x70080000UL },
122             },
123             .compress = true,
124         },
125         /* Unsorted ranges, lower one not starting at 0. */
126         {
127         .ranges = {
128                 { .start = (1UL << (35 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 2,
129                   .end =   (1UL << (35 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 3 },
130                 { .start = (1 << MAX_ORDER) * 2,
131                   .end =   (1 << MAX_ORDER) * 3 },
132             },
133             .compress = true,
134         },
135         /* Two ranges with the same high bit set. */
136         {
137         .ranges = {
138                 { .start = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 0,
139                   .end =   (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 1 },
140                 { .start = (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 3,
141                   .end =   (1UL << (51 - PAGE_SHIFT)) + (1 << MAX_ORDER) * 4 },
142             },
143             .compress = true,
144         },
145 #endif
146         /* AMD Naples Epyc 7281 2 sockets, 8 NUMA nodes. */
147         {
148             .ranges = {
149                 { .start =         0,   .end =      0xa0UL },
150                 { .start =     0x100UL, .end =   0xb0000UL },
151                 { .start =  0x100000UL, .end =  0x430000UL },
152                 { .start =  0x430000UL, .end =  0x830000UL },
153                 { .start =  0x830000UL, .end =  0xc30000UL },
154                 { .start =  0xc30000UL, .end = 0x1030000UL },
155                 { .start = 0x1030000UL, .end = 0x1430000UL },
156                 { .start = 0x1430000UL, .end = 0x1830000UL },
157                 { .start = 0x1830000UL, .end = 0x1c30000UL },
158                 { .start = 0x1c30000UL, .end = 0x2030000UL },
159             },
160             .compress = false,
161         },
162         /* 2-node 2GB per-node QEMU layout. */
163         {
164             .ranges = {
165                 { .start =        0,   .end =  0x80000UL },
166                 { .start = 0x100000UL, .end = 0x180000UL },
167             },
168             .compress = true,
169         },
170         /* Not compressible, smaller than MAX_ORDER. */
171         {
172             .ranges = {
173                 { .start =     0,   .end =     1   },
174                 { .start = 0x100UL, .end = 0x101UL },
175             },
176             .compress = false,
177         },
178         /* Compressible, requires adjusting size to (1 << MAX_ORDER). */
179         {
180             .ranges = {
181                 { .start =        0,   .end =        1   },
182                 { .start = 0x100000UL, .end = 0x100001UL },
183             },
184             .compress = true,
185         },
186         /* 2s Intel CLX with contiguous ranges, no compression. */
187         {
188             .ranges = {
189                 { .start =        0  , .end =  0x180000UL },
190                 { .start = 0x180000UL, .end = 0x3040000UL },
191             },
192             .compress = false,
193         },
194     };
195     int ret_code = EXIT_SUCCESS;
196 
197     for ( unsigned int i = 0 ; i < ARRAY_SIZE(tests); i++ )
198     {
199         unsigned int j;
200 
201         pfn_pdx_compression_reset();
202 
203         for ( j = 0; j < ARRAY_SIZE(tests[i].ranges); j++ )
204         {
205             unsigned long size = tests[i].ranges[j].end -
206                                  tests[i].ranges[j].start;
207 
208             if ( !tests[i].ranges[j].start && !tests[i].ranges[j].end )
209                 break;
210 
211             pfn_pdx_add_region(tests[i].ranges[j].start << PAGE_SHIFT,
212                                size << PAGE_SHIFT);
213         }
214 
215         if ( pfn_pdx_compression_setup(0) != tests[i].compress )
216         {
217             printf("PFN compression diverge, expected %scompressible\n",
218                    tests[i].compress ? "" : "un");
219             print_ranges(tests[i].ranges);
220 
221             ret_code = EXIT_FAILURE;
222             continue;
223         }
224 
225         if ( !tests[i].compress )
226             continue;
227 
228         for ( j = 0; j < ARRAY_SIZE(tests[i].ranges); j++ )
229         {
230             unsigned long start = tests[i].ranges[j].start;
231             unsigned long end = tests[i].ranges[j].end;
232 
233             if ( !start && !end )
234                 break;
235 
236             if ( !pdx_is_region_compressible(start << PAGE_SHIFT, 1) ||
237                  !pdx_is_region_compressible((end - 1) << PAGE_SHIFT, 1) )
238             {
239                 printf(
240     "PFN compression invalid, pages %#lx and %#lx should be compressible\n",
241                        start, end - 1);
242                 print_ranges(tests[i].ranges);
243                 ret_code = EXIT_FAILURE;
244             }
245 
246             if ( start != pdx_to_pfn(pfn_to_pdx(start)) ||
247                  end - 1 != pdx_to_pfn(pfn_to_pdx(end - 1)) )
248             {
249                 printf("Compression is not bi-directional:\n");
250                 printf(" PFN %#lx -> PDX %#lx -> PFN %#lx\n",
251                        start, pfn_to_pdx(start), pdx_to_pfn(pfn_to_pdx(start)));
252                 printf(" PFN %#lx -> PDX %#lx -> PFN %#lx\n",
253                        end - 1, pfn_to_pdx(end - 1),
254                        pdx_to_pfn(pfn_to_pdx(end - 1)));
255                 print_ranges(tests[i].ranges);
256                 ret_code = EXIT_FAILURE;
257             }
258         }
259     }
260 
261     return ret_code;
262 }
263 
264 /*
265  * Local variables:
266  * mode: C
267  * c-file-style: "BSD"
268  * c-basic-offset: 4
269  * indent-tabs-mode: nil
270  * End:
271  */
272