xref: /kvm-unit-tests/s390x/uv-host.c (revision c604fa931a1cb70c3649ac1b7223178fc79eab6a)
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3  * Guest Ultravisor Call tests
4  *
5  * Copyright (c) 2021 IBM Corp
6  *
7  * Authors:
8  *  Janosch Frank <frankja@linux.ibm.com>
9  */
10 
11 #include <libcflat.h>
12 #include <hardware.h>
13 #include <alloc.h>
14 #include <vmalloc.h>
15 #include <sclp.h>
16 #include <smp.h>
17 #include <uv.h>
18 #include <asm/page.h>
19 #include <asm/sigp.h>
20 #include <asm/pgtable.h>
21 #include <asm/asm-offsets.h>
22 #include <asm/interrupt.h>
23 #include <asm/facility.h>
24 #include <asm/uv.h>
25 #include <asm-generic/barrier.h>
26 
27 static struct uv_cb_qui uvcb_qui;
28 static struct uv_cb_init uvcb_init;
29 static struct uv_cb_cgc uvcb_cgc;
30 static struct uv_cb_csc uvcb_csc;
31 
32 extern int diag308_load_reset(u64 code);
33 
34 struct cmd_list{
35 	const char *name;
36 	uint16_t cmd;
37 	uint16_t len;
38 	int call_bit;
39 };
40 
41 static void cpu_loop(void)
42 {
43 	for (;;) {}
44 }
45 
46 static struct cmd_list cmds[] = {
47 	{ "init", UVC_CMD_INIT_UV, sizeof(struct uv_cb_init), BIT_UVC_CMD_INIT_UV },
48 	{ "create conf", UVC_CMD_CREATE_SEC_CONF, sizeof(struct uv_cb_cgc), BIT_UVC_CMD_CREATE_SEC_CONF },
49 	{ "destroy conf", UVC_CMD_DESTROY_SEC_CONF, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_DESTROY_SEC_CONF },
50 	{ "create cpu", UVC_CMD_CREATE_SEC_CPU, sizeof(struct uv_cb_csc), BIT_UVC_CMD_CREATE_SEC_CPU },
51 	{ "destroy cpu", UVC_CMD_DESTROY_SEC_CPU, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_DESTROY_SEC_CPU },
52 	{ "conv to", UVC_CMD_CONV_TO_SEC_STOR, sizeof(struct uv_cb_cts), BIT_UVC_CMD_CONV_TO_SEC_STOR },
53 	{ "conv from", UVC_CMD_CONV_FROM_SEC_STOR, sizeof(struct uv_cb_cfs), BIT_UVC_CMD_CONV_FROM_SEC_STOR },
54 	{ "set sec conf", UVC_CMD_SET_SEC_CONF_PARAMS, sizeof(struct uv_cb_ssc), BIT_UVC_CMD_SET_SEC_PARMS },
55 	{ "unpack", UVC_CMD_UNPACK_IMG, sizeof(struct uv_cb_unp), BIT_UVC_CMD_UNPACK_IMG },
56 	{ "verify", UVC_CMD_VERIFY_IMG, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_VERIFY_IMG },
57 	{ "cpu reset", UVC_CMD_CPU_RESET, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_RESET },
58 	{ "cpu initial reset", UVC_CMD_CPU_RESET_INITIAL, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_RESET_INITIAL },
59 	{ "conf clear reset", UVC_CMD_PREPARE_RESET, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_PREPARE_RESET },
60 	{ "cpu clear reset", UVC_CMD_CPU_RESET_CLEAR, sizeof(struct uv_cb_nodata), BIT_UVC_CMD_CPU_PERFORM_CLEAR_RESET },
61 	{ "cpu set state", UVC_CMD_CPU_SET_STATE, sizeof(struct uv_cb_cpu_set_state), BIT_UVC_CMD_CPU_SET_STATE },
62 	{ "pin shared", UVC_CMD_PIN_PAGE_SHARED, sizeof(struct uv_cb_cfs), BIT_UVC_CMD_PIN_PAGE_SHARED },
63 	{ "unpin shared", UVC_CMD_UNPIN_PAGE_SHARED, sizeof(struct uv_cb_cts), BIT_UVC_CMD_UNPIN_PAGE_SHARED },
64 	{ NULL, 0, 0 },
65 };
66 
67 static void test_priv(void)
68 {
69 	struct uv_cb_header uvcb = {};
70 	uint16_t pgm;
71 	int i;
72 
73 	report_prefix_push("privileged");
74 	for (i = 0; cmds[i].name; i++) {
75 		expect_pgm_int();
76 		uvcb.cmd = cmds[i].cmd;
77 		uvcb.len = cmds[i].len;
78 		enter_pstate();
79 		uv_call(0, (uint64_t)&uvcb);
80 		pgm = clear_pgm_int();
81 		report(pgm == PGM_INT_CODE_PRIVILEGED_OPERATION, "%s", cmds[i].name);
82 	}
83 	report_prefix_pop();
84 }
85 
86 static void test_config_destroy(void)
87 {
88 	int rc;
89 	struct uv_cb_nodata uvcb = {
90 		.header.cmd = UVC_CMD_DESTROY_SEC_CONF,
91 		.header.len = sizeof(uvcb),
92 		.handle = uvcb_cgc.guest_handle,
93 	};
94 
95 	report_prefix_push("dsc");
96 	uvcb.header.len -= 8;
97 	rc = uv_call(0, (uint64_t)&uvcb);
98 	report(rc == 1 && uvcb.header.rc == UVC_RC_INV_LEN,
99 	       "hdr invalid length");
100 	uvcb.header.len += 8;
101 
102 	uvcb.handle += 1;
103 	rc = uv_call(0, (uint64_t)&uvcb);
104 	report(rc == 1 && uvcb.header.rc == UVC_RC_INV_GHANDLE, "invalid handle");
105 	uvcb.handle -= 1;
106 
107 	rc = uv_call(0, (uint64_t)&uvcb);
108 	report(rc == 0 && uvcb.header.rc == UVC_RC_EXECUTED, "success");
109 	report_prefix_pop();
110 }
111 
112 static void test_cpu_destroy(void)
113 {
114 	int rc;
115 	struct uv_cb_nodata uvcb = {
116 		.header.len = sizeof(uvcb),
117 		.header.cmd = UVC_CMD_DESTROY_SEC_CPU,
118 		.handle = uvcb_csc.cpu_handle,
119 	};
120 
121 	report_prefix_push("dcpu");
122 
123 	uvcb.header.len -= 8;
124 	rc = uv_call(0, (uint64_t)&uvcb);
125 	report(rc == 1 && uvcb.header.rc == UVC_RC_INV_LEN,
126 	       "hdr invalid length");
127 	uvcb.header.len += 8;
128 
129 	if (!machine_is_z15()) {
130 		uvcb.handle += 1;
131 		rc = uv_call(0, (uint64_t)&uvcb);
132 		report(rc == 1 && uvcb.header.rc == UVC_RC_INV_CHANDLE, "invalid handle");
133 		uvcb.handle -= 1;
134 	}
135 
136 	rc = uv_call(0, (uint64_t)&uvcb);
137 	report(rc == 0 && uvcb.header.rc == UVC_RC_EXECUTED, "success");
138 
139 	report_prefix_pop();
140 }
141 
142 static void test_cpu_create(void)
143 {
144 	int rc;
145 	unsigned long tmp;
146 
147 	report_prefix_push("csc");
148 	uvcb_csc.header.len = sizeof(uvcb_csc);
149 	uvcb_csc.header.cmd = UVC_CMD_CREATE_SEC_CPU;
150 	uvcb_csc.guest_handle = uvcb_cgc.guest_handle;
151 	uvcb_csc.stor_origin = (unsigned long)memalign(PAGE_SIZE, uvcb_qui.cpu_stor_len);
152 	uvcb_csc.state_origin = (unsigned long)memalign(PAGE_SIZE, PAGE_SIZE);
153 
154 	uvcb_csc.header.len -= 8;
155 	rc = uv_call(0, (uint64_t)&uvcb_csc);
156 	report(uvcb_csc.header.rc == UVC_RC_INV_LEN && rc == 1 &&
157 	       !uvcb_csc.cpu_handle, "hdr invalid length");
158 	uvcb_csc.header.len += 8;
159 
160 	uvcb_csc.guest_handle += 1;
161 	rc = uv_call(0, (uint64_t)&uvcb_csc);
162 	report(uvcb_csc.header.rc == UVC_RC_INV_GHANDLE && rc == 1,
163 	       "invalid guest handle");
164 	uvcb_csc.guest_handle -= 1;
165 
166 	uvcb_csc.num = uvcb_qui.max_guest_cpus + 1;
167 	rc = uv_call(0, (uint64_t)&uvcb_csc);
168 	report(uvcb_csc.header.rc == 0x103 && rc == 1,
169 	       "invalid cpu #");
170 	uvcb_csc.num = 0;
171 
172 	tmp = uvcb_csc.stor_origin;
173 	uvcb_csc.stor_origin = get_max_ram_size() + PAGE_SIZE;
174 	rc = uv_call(0, (uint64_t)&uvcb_csc);
175 	report(uvcb_csc.header.rc == 0x105 && rc == 1,
176 	       "cpu stor inaccessible");
177 	uvcb_csc.stor_origin = tmp;
178 
179 	tmp = uvcb_csc.stor_origin;
180 	uvcb_csc.stor_origin = 0;
181 	rc = uv_call(0, (uint64_t)&uvcb_csc);
182 	report(uvcb_csc.header.rc == 0x106 && rc == 1,
183 	       "cpu stor in lowcore");
184 	uvcb_csc.stor_origin = tmp;
185 
186 	tmp = uvcb_csc.state_origin;
187 	uvcb_csc.state_origin = get_max_ram_size() + PAGE_SIZE;
188 	rc = uv_call(0, (uint64_t)&uvcb_csc);
189 	report(uvcb_csc.header.rc == 0x107 && rc == 1,
190 	       "SIE SD inaccessible");
191 	uvcb_csc.state_origin = tmp;
192 
193 	rc = uv_call(0, (uint64_t)&uvcb_csc);
194 	report(rc == 0 && uvcb_csc.header.rc == UVC_RC_EXECUTED &&
195 	       uvcb_csc.cpu_handle, "success");
196 
197 	tmp = uvcb_csc.stor_origin;
198 	uvcb_csc.stor_origin = (unsigned long)memalign(PAGE_SIZE, uvcb_qui.cpu_stor_len);
199 	rc = uv_call(0, (uint64_t)&uvcb_csc);
200 	report(rc == 1 && uvcb_csc.header.rc == 0x104, "already defined");
201 	uvcb_csc.stor_origin = tmp;
202 	report_prefix_pop();
203 }
204 
205 static void test_config_create(void)
206 {
207 	int rc;
208 	unsigned long vsize, tmp;
209 	static struct uv_cb_cgc uvcb;
210 
211 	uvcb_cgc.header.cmd = UVC_CMD_CREATE_SEC_CONF;
212 	uvcb_cgc.header.len = sizeof(uvcb_cgc);
213 	report_prefix_push("cgc");
214 
215 	uvcb_cgc.guest_stor_origin = 0;
216 	uvcb_cgc.guest_stor_len = 42 * (1UL << 20);
217 	vsize = uvcb_qui.conf_base_virt_stor_len +
218 		((uvcb_cgc.guest_stor_len / (1UL << 20)) * uvcb_qui.conf_virt_var_stor_len);
219 
220 	uvcb_cgc.conf_base_stor_origin = (uint64_t)memalign(PAGE_SIZE * 4, uvcb_qui.conf_base_phys_stor_len);
221 	uvcb_cgc.conf_var_stor_origin = (uint64_t)memalign(PAGE_SIZE, vsize);
222 	uvcb_cgc.guest_asce = (uint64_t)memalign(PAGE_SIZE, 4 * PAGE_SIZE) | ASCE_DT_SEGMENT | REGION_TABLE_LENGTH | ASCE_P;
223 	uvcb_cgc.guest_sca = (uint64_t)memalign(PAGE_SIZE * 4, PAGE_SIZE * 4);
224 
225 	uvcb_cgc.header.len -= 8;
226 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
227 	report(uvcb_cgc.header.rc == UVC_RC_INV_LEN && rc == 1 &&
228 	       !uvcb_cgc.guest_handle, "hdr invalid length");
229 	uvcb_cgc.header.len += 8;
230 
231 	uvcb_cgc.guest_stor_origin = uvcb_qui.max_guest_stor_addr + (1UL << 20) * 2 + 1;
232 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
233 	report(uvcb_cgc.header.rc == 0x101 && rc == 1,
234 	       "MSO > max guest addr");
235 	uvcb_cgc.guest_stor_origin = 0;
236 
237 	uvcb_cgc.guest_stor_origin = uvcb_qui.max_guest_stor_addr - (1UL << 20);
238 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
239 	report(uvcb_cgc.header.rc == 0x102 && rc == 1,
240 	       "MSO + MSL > max guest addr");
241 	uvcb_cgc.guest_stor_origin = 0;
242 
243 	uvcb_cgc.guest_asce &= ~ASCE_P;
244 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
245 	report(uvcb_cgc.header.rc == 0x105 && rc == 1,
246 	       "ASCE private bit missing");
247 	uvcb_cgc.guest_asce |= ASCE_P;
248 
249 	uvcb_cgc.guest_asce |= 0x20;
250 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
251 	report(uvcb_cgc.header.rc == 0x105 && rc == 1,
252 	       "ASCE bit 58 set");
253 	uvcb_cgc.guest_asce &= ~0x20;
254 
255 	tmp = uvcb_cgc.conf_base_stor_origin;
256 	uvcb_cgc.conf_base_stor_origin = get_max_ram_size() + 8;
257 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
258 	report(uvcb_cgc.header.rc == 0x108 && rc == 1,
259 	       "base storage origin > available memory");
260 	uvcb_cgc.conf_base_stor_origin = tmp;
261 
262 	tmp = uvcb_cgc.conf_base_stor_origin;
263 	uvcb_cgc.conf_base_stor_origin = 0x1000;
264 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
265 	report(uvcb_cgc.header.rc == 0x109 && rc == 1,
266 	       "base storage origin contains lowcore");
267 	uvcb_cgc.conf_base_stor_origin = tmp;
268 
269 	if (smp_query_num_cpus() == 1) {
270 		sigp_retry(1, SIGP_SET_PREFIX,
271 			   uvcb_cgc.conf_var_stor_origin + PAGE_SIZE, NULL);
272 		rc = uv_call(0, (uint64_t)&uvcb_cgc);
273 		report(uvcb_cgc.header.rc == 0x10e && rc == 1 &&
274 		       !uvcb_cgc.guest_handle, "variable storage area contains lowcore");
275 		sigp_retry(1, SIGP_SET_PREFIX, 0x0, NULL);
276 	}
277 
278 	tmp = uvcb_cgc.guest_sca;
279 	uvcb_cgc.guest_sca = 0;
280 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
281 	report(uvcb_cgc.header.rc == 0x10c && rc == 1,
282 	       "sca == 0");
283 	uvcb_cgc.guest_sca = tmp;
284 
285 	tmp = uvcb_cgc.guest_sca;
286 	uvcb_cgc.guest_sca = get_max_ram_size() + + PAGE_SIZE * 4;
287 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
288 	report(uvcb_cgc.header.rc == 0x10d && rc == 1,
289 	       "sca inaccessible");
290 	uvcb_cgc.guest_sca = tmp;
291 
292 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
293 	report(rc == 0 && uvcb_cgc.header.rc == UVC_RC_EXECUTED, "successful");
294 
295 	uvcb_cgc.header.rc = 0;
296 	uvcb_cgc.header.rrc = 0;
297 	tmp = uvcb_cgc.guest_handle;
298 	uvcb_cgc.guest_handle = 0;
299 	rc = uv_call(0, (uint64_t)&uvcb_cgc);
300 	report(uvcb_cgc.header.rc >= 0x100 && rc == 1, "reuse uvcb");
301 	uvcb_cgc.guest_handle = tmp;
302 
303 	/* Copy over most data from uvcb_cgc, so we have the ASCE that was used. */
304 	memcpy(&uvcb, &uvcb_cgc, sizeof(uvcb));
305 
306 	/* Reset the header and handle */
307 	uvcb.header.rc = 0;
308 	uvcb.header.rrc = 0;
309 	uvcb.guest_handle = 0;
310 
311 	/* Use new storage areas. */
312 	uvcb.conf_base_stor_origin = (uint64_t)memalign(PAGE_SIZE * 4, uvcb_qui.conf_base_phys_stor_len);
313 	uvcb.conf_var_stor_origin = (uint64_t)memalign(PAGE_SIZE, vsize);
314 
315 	rc = uv_call(0, (uint64_t)&uvcb);
316 	report(uvcb.header.rc >= 0x104 && rc == 1 && !uvcb.guest_handle,
317 	       "reuse ASCE");
318 	free((void *)uvcb.conf_base_stor_origin);
319 	free((void *)uvcb.conf_var_stor_origin);
320 
321 	/* Missing: 106, 10a, a0b */
322 	report_prefix_pop();
323 }
324 
325 static void test_init(void)
326 {
327 	int rc;
328 	uint64_t mem;
329 	struct psw psw;
330 
331 	/* Donated storage needs to be over 2GB */
332 	mem = (uint64_t)memalign(1UL << 31, uvcb_qui.uv_base_stor_len);
333 
334 	uvcb_init.header.len = sizeof(uvcb_init);
335 	uvcb_init.header.cmd = UVC_CMD_INIT_UV;
336 	uvcb_init.stor_origin = mem;
337 	uvcb_init.stor_len = uvcb_qui.uv_base_stor_len;
338 
339 	report_prefix_push("init");
340 	uvcb_init.header.len -= 8;
341 	rc = uv_call(0, (uint64_t)&uvcb_init);
342 	report(rc == 1 && uvcb_init.header.rc == UVC_RC_INV_LEN,
343 	       "hdr invalid length");
344 	uvcb_init.header.len += 8;
345 
346 	uvcb_init.stor_len -= 8;
347 	rc = uv_call(0, (uint64_t)&uvcb_init);
348 	report(rc == 1 && uvcb_init.header.rc == 0x103,
349 	       "storage invalid length");
350 	uvcb_init.stor_len += 8;
351 
352 	uvcb_init.stor_origin =  get_max_ram_size() + 8;
353 	rc = uv_call(0, (uint64_t)&uvcb_init);
354 	report(rc == 1 && uvcb_init.header.rc == 0x104,
355 	       "storage origin invalid");
356 	uvcb_init.stor_origin = mem;
357 
358 	uvcb_init.stor_origin = get_max_ram_size() - 8;
359 	rc = uv_call(0, (uint64_t)&uvcb_init);
360 	report(rc == 1 && uvcb_init.header.rc == 0x105,
361 	       "storage + length invalid");
362 	uvcb_init.stor_origin = mem;
363 
364 	uvcb_init.stor_origin = 1UL << 30;
365 	rc = uv_call(0, (uint64_t)&uvcb_init);
366 	report(rc == 1 && uvcb_init.header.rc == 0x108,
367 	       "storage below 2GB");
368 	uvcb_init.stor_origin = mem;
369 
370 	psw.mask = extract_psw_mask();
371 	psw.addr = (unsigned long)cpu_loop;
372 	smp_cpu_setup(1, psw);
373 	rc = uv_call(0, (uint64_t)&uvcb_init);
374 	report(rc == 1 && uvcb_init.header.rc == 0x102,
375 	       "too many running cpus");
376 	smp_cpu_stop(1);
377 
378 	rc = uv_call(0, (uint64_t)&uvcb_init);
379 	report(rc == 0 && uvcb_init.header.rc == UVC_RC_EXECUTED, "successful");
380 
381 	mem = (uint64_t)memalign(1UL << 31, uvcb_qui.uv_base_stor_len);
382 	rc = uv_call(0, (uint64_t)&uvcb_init);
383 	report(rc == 1 && uvcb_init.header.rc == 0x101, "double init");
384 	free((void *)mem);
385 
386 	report_prefix_pop();
387 }
388 
389 static void test_query(void)
390 {
391 	int i = 0, cc;
392 
393 	uvcb_qui.header.cmd = UVC_CMD_QUI;
394 	uvcb_qui.header.len = sizeof(uvcb_qui);
395 
396 	report_prefix_push("query");
397 	uvcb_qui.header.len = 0xa0;
398 	uv_call(0, (uint64_t)&uvcb_qui);
399 	report(uvcb_qui.header.rc == UVC_RC_INV_LEN, "length");
400 
401 	uvcb_qui.header.len = 0xa8;
402 	uv_call(0, (uint64_t)&uvcb_qui);
403 	report(uvcb_qui.header.rc == 0x100, "insf length");
404 
405 	uvcb_qui.header.len = sizeof(uvcb_qui);
406 	cc = uv_call(0, (uint64_t)&uvcb_qui);
407 	report((!cc && uvcb_qui.header.rc == UVC_RC_EXECUTED) ||
408 	       (cc == 1 && uvcb_qui.header.rc == 0x100),
409 		"successful query");
410 
411 	for (i = 0; cmds[i].name; i++)
412 		report(uv_query_test_call(cmds[i].call_bit), "%s", cmds[i].name);
413 
414 	report_prefix_pop();
415 }
416 
417 static struct cmd_list invalid_cmds[] = {
418 	{ "bogus", 0x4242, sizeof(struct uv_cb_header), -1},
419 	{ "share", UVC_CMD_SET_SHARED_ACCESS, sizeof(struct uv_cb_share), BIT_UVC_CMD_SET_SHARED_ACCESS },
420 	{ "unshare", UVC_CMD_REMOVE_SHARED_ACCESS, sizeof(struct uv_cb_share), BIT_UVC_CMD_REMOVE_SHARED_ACCESS },
421 	{ NULL, 0, 0 },
422 };
423 
424 static void test_invalid(void)
425 {
426 	struct uv_cb_header hdr = {};
427 	int i, cc;
428 
429 	report_prefix_push("invalid");
430 	for (i = 0; invalid_cmds[i].name; i++) {
431 		hdr.cmd = invalid_cmds[i].cmd;
432 		hdr.len = invalid_cmds[i].len;
433 		cc = uv_call(0, (uint64_t)&hdr);
434 		report(cc == 1 && hdr.rc == UVC_RC_INV_CMD &&
435 		       (invalid_cmds[i].call_bit == -1 || !uv_query_test_call(invalid_cmds[i].call_bit)),
436 		       "%s", invalid_cmds[i].name);
437 	}
438 	report_prefix_pop();
439 }
440 
441 static void test_clear(void)
442 {
443 	uint64_t *tmp = (void *)uvcb_init.stor_origin;
444 
445 	diag308_load_reset(1);
446 	sclp_console_setup();
447 	report(!*tmp, "memory cleared after reset 1");
448 }
449 
450 static void setup_vmem(void)
451 {
452 	uint64_t asce;
453 
454 	setup_mmu(get_max_ram_size(), NULL);
455 	/*
456 	 * setup_mmu() will enable DAT and set the primary address
457 	 * space but we need to have a valid home space since UV calls
458 	 * take home space virtual addresses.
459 	 *
460 	 * Hence we just copy the primary asce into the home space.
461 	 */
462 	asce = stctg(1);
463 	lctlg(13, asce);
464 }
465 
466 int main(void)
467 {
468 	bool has_uvc = test_facility(158);
469 
470 	report_prefix_push("uvc");
471 	if (!has_uvc) {
472 		report_skip("Ultravisor call facility is not available");
473 		goto done;
474 	}
475 
476 	test_priv();
477 	test_invalid();
478 	test_query();
479 	test_init();
480 
481 	setup_vmem();
482 	test_config_create();
483 	test_cpu_create();
484 	test_cpu_destroy();
485 	test_config_destroy();
486 	test_clear();
487 
488 done:
489 	return report_summary();
490 }
491