xref: /linux/tools/testing/selftests/kvm/s390/keyop.c (revision ca220141fa8ebae09765a242076b2b77338106b0)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Test for s390x KVM_S390_KEYOP
4  *
5  * Copyright IBM Corp. 2026
6  *
7  * Authors:
8  *  Claudio Imbrenda <imbrenda@linux.ibm.com>
9  */
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/ioctl.h>
14 
15 #include <linux/bits.h>
16 
17 #include "test_util.h"
18 #include "kvm_util.h"
19 #include "kselftest.h"
20 #include "processor.h"
21 
22 #define BUF_PAGES 128UL
23 #define GUEST_PAGES 256UL
24 
25 #define BUF_START_GFN	(GUEST_PAGES - BUF_PAGES)
26 #define BUF_START_ADDR	(BUF_START_GFN << PAGE_SHIFT)
27 
28 #define KEY_BITS_ACC	0xf0
29 #define KEY_BIT_F	0x08
30 #define KEY_BIT_R	0x04
31 #define KEY_BIT_C	0x02
32 
33 #define KEY_BITS_RC	(KEY_BIT_R | KEY_BIT_C)
34 #define KEY_BITS_ALL	(KEY_BITS_ACC | KEY_BIT_F | KEY_BITS_RC)
35 
36 static unsigned char tmp[BUF_PAGES];
37 static unsigned char old[BUF_PAGES];
38 static unsigned char expected[BUF_PAGES];
39 
40 static int _get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
41 {
42 	struct kvm_s390_skeys skeys_ioctl = {
43 		.start_gfn = BUF_START_GFN,
44 		.count = BUF_PAGES,
45 		.skeydata_addr = (unsigned long)skeys,
46 	};
47 
48 	return __vm_ioctl(vcpu->vm, KVM_S390_GET_SKEYS, &skeys_ioctl);
49 }
50 
51 static void get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
52 {
53 	int r = _get_skeys(vcpu, skeys);
54 
55 	TEST_ASSERT(!r, "Failed to get storage keys, r=%d", r);
56 }
57 
58 static void set_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
59 {
60 	struct kvm_s390_skeys skeys_ioctl = {
61 		.start_gfn = BUF_START_GFN,
62 		.count = BUF_PAGES,
63 		.skeydata_addr = (unsigned long)skeys,
64 	};
65 	int r;
66 
67 	r = __vm_ioctl(vcpu->vm, KVM_S390_SET_SKEYS, &skeys_ioctl);
68 	TEST_ASSERT(!r, "Failed to set storage keys, r=%d", r);
69 }
70 
71 static int do_keyop(struct kvm_vcpu *vcpu, int op, unsigned long page_idx, unsigned char skey)
72 {
73 	struct kvm_s390_keyop keyop = {
74 		.guest_addr = BUF_START_ADDR + page_idx * PAGE_SIZE,
75 		.key = skey,
76 		.operation = op,
77 	};
78 	int r;
79 
80 	r = __vm_ioctl(vcpu->vm, KVM_S390_KEYOP, &keyop);
81 	TEST_ASSERT(!r, "Failed to perform keyop, r=%d", r);
82 	TEST_ASSERT((keyop.key & 1) == 0,
83 		    "Last bit of key is 1, should be 0! page %lu, new key=%#x, old key=%#x",
84 		    page_idx, skey, keyop.key);
85 
86 	return keyop.key;
87 }
88 
89 static void fault_in_buffer(struct kvm_vcpu *vcpu, int where, int cur_loc)
90 {
91 	unsigned long i;
92 	int r;
93 
94 	if (where != cur_loc)
95 		return;
96 
97 	for (i = 0; i < BUF_PAGES; i++) {
98 		r = ioctl(vcpu->fd, KVM_S390_VCPU_FAULT, BUF_START_ADDR + i * PAGE_SIZE);
99 		TEST_ASSERT(!r, "Faulting in buffer page %lu, r=%d", i, r);
100 	}
101 }
102 
103 static inline void set_pattern(unsigned char skeys[])
104 {
105 	int i;
106 
107 	for (i = 0; i < BUF_PAGES; i++)
108 		skeys[i] = i << 1;
109 }
110 
111 static void dump_sk(const unsigned char skeys[], const char *descr)
112 {
113 	int i, j;
114 
115 	fprintf(stderr, "# %s:\n", descr);
116 	for (i = 0; i < BUF_PAGES; i += 32) {
117 		fprintf(stderr, "# %3d: ", i);
118 		for (j = 0; j < 32; j++)
119 			fprintf(stderr, "%02x ", skeys[i + j]);
120 		fprintf(stderr, "\n");
121 	}
122 }
123 
124 static inline void compare(const unsigned char what[], const unsigned char expected[],
125 			   const char *descr, int fault_in_loc)
126 {
127 	int i;
128 
129 	for (i = 0; i < BUF_PAGES; i++) {
130 		if (expected[i] != what[i]) {
131 			dump_sk(expected, "Expected");
132 			dump_sk(what, "Got");
133 		}
134 		TEST_ASSERT(expected[i] == what[i],
135 			    "%s! fault-in location %d, page %d, expected %#x, got %#x",
136 			    descr, fault_in_loc, i, expected[i], what[i]);
137 	}
138 }
139 
140 static inline void clear_all(void)
141 {
142 	memset(tmp, 0, BUF_PAGES);
143 	memset(old, 0, BUF_PAGES);
144 	memset(expected, 0, BUF_PAGES);
145 }
146 
147 static void test_init(struct kvm_vcpu *vcpu, int fault_in)
148 {
149 	/* Set all storage keys to zero */
150 	fault_in_buffer(vcpu, fault_in, 1);
151 	set_skeys(vcpu, expected);
152 
153 	fault_in_buffer(vcpu, fault_in, 2);
154 	get_skeys(vcpu, tmp);
155 	compare(tmp, expected, "Setting keys not zero", fault_in);
156 
157 	/* Set storage keys to a sequential pattern */
158 	fault_in_buffer(vcpu, fault_in, 3);
159 	set_pattern(expected);
160 	set_skeys(vcpu, expected);
161 
162 	fault_in_buffer(vcpu, fault_in, 4);
163 	get_skeys(vcpu, tmp);
164 	compare(tmp, expected, "Setting storage keys failed", fault_in);
165 }
166 
167 static void test_rrbe(struct kvm_vcpu *vcpu, int fault_in)
168 {
169 	unsigned char k;
170 	int i;
171 
172 	/* Set storage keys to a sequential pattern */
173 	fault_in_buffer(vcpu, fault_in, 1);
174 	set_pattern(expected);
175 	set_skeys(vcpu, expected);
176 
177 	/* Call the RRBE KEYOP ioctl on each page and verify the result */
178 	fault_in_buffer(vcpu, fault_in, 2);
179 	for (i = 0; i < BUF_PAGES; i++) {
180 		k = do_keyop(vcpu, KVM_S390_KEYOP_RRBE, i, 0xff);
181 		TEST_ASSERT((expected[i] & KEY_BITS_RC) == k,
182 			    "Old R or C value mismatch! expected: %#x, got %#x",
183 			    expected[i] & KEY_BITS_RC, k);
184 		if (i == BUF_PAGES / 2)
185 			fault_in_buffer(vcpu, fault_in, 3);
186 	}
187 
188 	for (i = 0; i < BUF_PAGES; i++)
189 		expected[i] &= ~KEY_BIT_R;
190 
191 	/* Verify that only the R bit has been cleared */
192 	fault_in_buffer(vcpu, fault_in, 4);
193 	get_skeys(vcpu, tmp);
194 	compare(tmp, expected, "New value mismatch", fault_in);
195 }
196 
197 static void test_iske(struct kvm_vcpu *vcpu, int fault_in)
198 {
199 	int i;
200 
201 	/* Set storage keys to a sequential pattern */
202 	fault_in_buffer(vcpu, fault_in, 1);
203 	set_pattern(expected);
204 	set_skeys(vcpu, expected);
205 
206 	/* Call the ISKE KEYOP ioctl on each page and verify the result */
207 	fault_in_buffer(vcpu, fault_in, 2);
208 	for (i = 0; i < BUF_PAGES; i++) {
209 		tmp[i] = do_keyop(vcpu, KVM_S390_KEYOP_ISKE, i, 0xff);
210 		if (i == BUF_PAGES / 2)
211 			fault_in_buffer(vcpu, fault_in, 3);
212 	}
213 	compare(tmp, expected, "Old value mismatch", fault_in);
214 
215 	/* Check storage keys have not changed */
216 	fault_in_buffer(vcpu, fault_in, 4);
217 	get_skeys(vcpu, tmp);
218 	compare(tmp, expected, "Storage keys values changed", fault_in);
219 }
220 
221 static void test_sske(struct kvm_vcpu *vcpu, int fault_in)
222 {
223 	int i;
224 
225 	/* Set storage keys to a sequential pattern */
226 	fault_in_buffer(vcpu, fault_in, 1);
227 	set_pattern(tmp);
228 	set_skeys(vcpu, tmp);
229 
230 	/* Call the SSKE KEYOP ioctl on each page and verify the result */
231 	fault_in_buffer(vcpu, fault_in, 2);
232 	for (i = 0; i < BUF_PAGES; i++) {
233 		expected[i] = ~tmp[i] & KEY_BITS_ALL;
234 		/* Set the new storage keys to be the bit-inversion of the previous ones */
235 		old[i] = do_keyop(vcpu, KVM_S390_KEYOP_SSKE, i, expected[i] | 1);
236 		if (i == BUF_PAGES / 2)
237 			fault_in_buffer(vcpu, fault_in, 3);
238 	}
239 	compare(old, tmp, "Old value mismatch", fault_in);
240 
241 	/* Verify that the storage keys have been set correctly */
242 	fault_in_buffer(vcpu, fault_in, 4);
243 	get_skeys(vcpu, tmp);
244 	compare(tmp, expected, "New value mismatch", fault_in);
245 }
246 
247 static struct testdef {
248 	const char *name;
249 	void (*test)(struct kvm_vcpu *vcpu, int fault_in_location);
250 	int n_fault_in_locations;
251 } testplan[] = {
252 	{ "Initialization", test_init, 5 },
253 	{ "RRBE", test_rrbe, 5 },
254 	{ "ISKE", test_iske, 5 },
255 	{ "SSKE", test_sske, 5 },
256 };
257 
258 static void run_test(void (*the_test)(struct kvm_vcpu *, int), int fault_in_location)
259 {
260 	struct kvm_vcpu *vcpu;
261 	struct kvm_vm *vm;
262 	int r;
263 
264 	vm = vm_create_barebones();
265 	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, GUEST_PAGES, 0);
266 	vcpu = __vm_vcpu_add(vm, 0);
267 
268 	r = _get_skeys(vcpu, tmp);
269 	TEST_ASSERT(r == KVM_S390_GET_SKEYS_NONE,
270 		    "Storage keys are not disabled initially, r=%d", r);
271 
272 	clear_all();
273 
274 	the_test(vcpu, fault_in_location);
275 
276 	kvm_vm_free(vm);
277 }
278 
279 int main(int argc, char *argv[])
280 {
281 	int i, f;
282 
283 	TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_KEYOP));
284 	TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_UCONTROL));
285 
286 	ksft_print_header();
287 	for (i = 0, f = 0; i < ARRAY_SIZE(testplan); i++)
288 		f += testplan[i].n_fault_in_locations;
289 	ksft_set_plan(f);
290 
291 	for (i = 0; i < ARRAY_SIZE(testplan); i++) {
292 		for (f = 0; f < testplan[i].n_fault_in_locations; f++) {
293 			run_test(testplan[i].test, f);
294 			ksft_test_result_pass("%s (fault-in location %d)\n", testplan[i].name, f);
295 		}
296 	}
297 
298 	ksft_finished();	/* Print results and exit() accordingly */
299 }
300