xref: /kvm-unit-tests/s390x/migration-skey.c (revision e6e1a20143625acb08a3f8ae25ec06846356d891)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * Storage Key migration tests
4  *
5  * There are two variants of this test:
6  * - sequential: set storage keys on some pages, migrates the VM and then
7  *   verifies that the storage keys are still as we expect.
8  * - parallel: start migration and set and check storage keys on some
9  *   pages while migration is in process.
10  *
11  * Copyright IBM Corp. 2022
12  *
13  * Authors:
14  *  Nico Boehr <nrb@linux.ibm.com>
15  */
16 
17 #include <libcflat.h>
18 #include <migrate.h>
19 #include <asm/facility.h>
20 #include <asm/page.h>
21 #include <asm/mem.h>
22 #include <asm/barrier.h>
23 #include <hardware.h>
24 #include <smp.h>
25 
26 struct verify_result {
27 	bool verify_failed;
28 	union skey expected_key;
29 	union skey actual_key;
30 	unsigned long page_mismatch_idx;
31 	unsigned long page_mismatch_addr;
32 };
33 
34 #define NUM_PAGES 128
35 static uint8_t pagebuf[NUM_PAGES * PAGE_SIZE] __attribute__((aligned(PAGE_SIZE)));
36 
37 static struct verify_result result;
38 
39 static unsigned int thread_iters;
40 static bool thread_should_exit;
41 static bool thread_exited;
42 
43 static enum {
44 	TEST_INVALID,
45 	TEST_SEQUENTIAL,
46 	TEST_PARALLEL
47 } arg_test_to_run;
48 
49 /*
50  * Set storage key test pattern on pagebuf with a seed for the storage keys.
51  *
52  * Each page's storage key is generated by taking the page's index in pagebuf,
53  * XOR-ing that with the given seed and then multipling the result with two.
54  *
55  * Only the lower seven bits of the seed are considered.
56  */
set_test_pattern(unsigned char seed)57 static void set_test_pattern(unsigned char seed)
58 {
59 	unsigned char key_to_set;
60 	unsigned long i;
61 
62 	for (i = 0; i < NUM_PAGES; i++) {
63 		/*
64 		 * Storage keys are 7 bit, lowest bit is always returned as zero
65 		 * by iske.
66 		 * This loop will set all 7 bits which means we set fetch
67 		 * protection as well as reference and change indication for
68 		 * some keys.
69 		 */
70 		key_to_set = (i ^ seed) * 2;
71 		set_storage_key(pagebuf + i * PAGE_SIZE, key_to_set, 1);
72 	}
73 }
74 
75 /*
76  * Verify storage keys on pagebuf.
77  * Storage keys must have been set by set_test_pattern on pagebuf before.
78  * set_test_pattern must have been called with the same seed value.
79  *
80  * If storage keys match the expected result, will return a verify_result
81  * with verify_failed false. All other fields are then invalid.
82  * If there is a mismatch, returned struct will have verify_failed true and will
83  * be filled with the details on the first mismatch encountered.
84  */
verify_test_pattern(unsigned char seed)85 static struct verify_result verify_test_pattern(unsigned char seed)
86 {
87 	union skey expected_key, actual_key;
88 	struct verify_result result = {
89 		.verify_failed = true
90 	};
91 	uint8_t *cur_page;
92 	unsigned long i;
93 
94 	for (i = 0; i < NUM_PAGES; i++) {
95 		cur_page = pagebuf + i * PAGE_SIZE;
96 		actual_key.val = get_storage_key(cur_page);
97 		expected_key.val = (i ^ seed) * 2;
98 
99 		/*
100 		 * The PoP neither gives a guarantee that the reference bit is
101 		 * accurate nor that it won't be cleared by hardware. Hence we
102 		 * don't rely on it and just clear the bits to avoid compare
103 		 * errors.
104 		 */
105 		actual_key.str.rf = 0;
106 		expected_key.str.rf = 0;
107 
108 		if (actual_key.val != expected_key.val) {
109 			result.expected_key.val = expected_key.val;
110 			result.actual_key.val = actual_key.val;
111 			result.page_mismatch_idx = i;
112 			result.page_mismatch_addr = (unsigned long)cur_page;
113 			return result;
114 		}
115 	}
116 
117 	result.verify_failed = false;
118 	return result;
119 }
120 
report_verify_result(const struct verify_result * result)121 static void report_verify_result(const struct verify_result * result)
122 {
123 	if (result->verify_failed)
124 		report_fail("page skey mismatch: first page idx = %lu, addr = 0x%lx, "
125 			    "expected_key = 0x%02x, actual_key = 0x%02x",
126 			    result->page_mismatch_idx, result->page_mismatch_addr,
127 			    result->expected_key.val, result->actual_key.val);
128 	else
129 		report_pass("skeys match");
130 }
131 
test_skey_migration_sequential(void)132 static void test_skey_migration_sequential(void)
133 {
134 	report_prefix_push("sequential");
135 
136 	set_test_pattern(0);
137 
138 	migrate_once();
139 
140 	result = verify_test_pattern(0);
141 	report_verify_result(&result);
142 
143 	report_prefix_pop();
144 }
145 
set_skeys_thread(void)146 static void set_skeys_thread(void)
147 {
148 	while (!READ_ONCE(thread_should_exit)) {
149 		set_test_pattern(thread_iters);
150 
151 		result = verify_test_pattern(thread_iters);
152 
153 		/*
154 		 * Always increment even if the verify fails. This ensures primary CPU knows where
155 		 * we left off and can do an additional verify round after migration finished.
156 		 */
157 		thread_iters++;
158 
159 		if (result.verify_failed)
160 			break;
161 	}
162 
163 	WRITE_ONCE(thread_exited, 1);
164 }
165 
test_skey_migration_parallel(void)166 static void test_skey_migration_parallel(void)
167 {
168 	report_prefix_push("parallel");
169 
170 	if (smp_query_num_cpus() == 1) {
171 		report_skip("need at least 2 cpus for this test");
172 		migrate_skip();
173 		goto error;
174 	}
175 
176 	smp_cpu_setup(1, PSW_WITH_CUR_MASK(set_skeys_thread));
177 
178 	migrate_once();
179 
180 	WRITE_ONCE(thread_should_exit, 1);
181 
182 	while (!READ_ONCE(thread_exited))
183 		;
184 
185 	/* Ensure we read result and thread_iters below from memory after thread exited */
186 	mb();
187 	report_info("thread completed %u iterations", thread_iters);
188 
189 	report_prefix_push("during migration");
190 	report_verify_result(&result);
191 	report_prefix_pop();
192 
193 	/*
194 	 * Verification of skeys occurs on the thread. We don't know if we
195 	 * were still migrating during the verification.
196 	 * To be sure, make another verification round after the migration
197 	 * finished to catch skeys which might not have been migrated
198 	 * correctly.
199 	 */
200 	report_prefix_push("after migration");
201 	assert(thread_iters > 0);
202 	result = verify_test_pattern(thread_iters - 1);
203 	report_verify_result(&result);
204 	report_prefix_pop();
205 
206 error:
207 	report_prefix_pop();
208 }
209 
print_usage(void)210 static void print_usage(void)
211 {
212 	report_info("Usage: migration-skey [--parallel|--sequential]");
213 }
214 
parse_args(int argc,char ** argv)215 static void parse_args(int argc, char **argv)
216 {
217 	if (argc < 2) {
218 		/* default to sequential since it only needs one cpu */
219 		arg_test_to_run = TEST_SEQUENTIAL;
220 		return;
221 	}
222 
223 	if (!strcmp("--parallel", argv[1]))
224 		arg_test_to_run = TEST_PARALLEL;
225 	else if (!strcmp("--sequential", argv[1]))
226 		arg_test_to_run = TEST_SEQUENTIAL;
227 	else
228 		arg_test_to_run = TEST_INVALID;
229 }
230 
main(int argc,char ** argv)231 int main(int argc, char **argv)
232 {
233 	report_prefix_push("migration-skey");
234 
235 	if (test_facility(169)) {
236 		report_skip("storage key removal facility is active");
237 		migrate_skip();
238 		goto error;
239 	}
240 
241 	parse_args(argc, argv);
242 
243 	switch (arg_test_to_run) {
244 	case TEST_SEQUENTIAL:
245 		test_skey_migration_sequential();
246 		break;
247 	case TEST_PARALLEL:
248 		test_skey_migration_parallel();
249 		break;
250 	default:
251 		print_usage();
252 		migrate_skip();
253 		break;
254 	}
255 
256 error:
257 	report_prefix_pop();
258 	return report_summary();
259 }
260