1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2022 ARM Limited. 4 */ 5 6 #include <errno.h> 7 #include <signal.h> 8 #include <stdbool.h> 9 #include <stddef.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <unistd.h> 14 #include <sys/auxv.h> 15 #include <sys/prctl.h> 16 #include <asm/hwcap.h> 17 #include <asm/sigcontext.h> 18 #include <asm/unistd.h> 19 20 #include "../../kselftest.h" 21 22 #define TESTS_PER_HWCAP 3 23 24 /* 25 * Function expected to generate exception when the feature is not 26 * supported and return when it is supported. If the specific exception 27 * is generated then the handler must be able to skip over the 28 * instruction safely. 29 * 30 * Note that it is expected that for many architecture extensions 31 * there are no specific traps due to no architecture state being 32 * added so we may not fault if running on a kernel which doesn't know 33 * to add the hwcap. 34 */ 35 typedef void (*sig_fn)(void); 36 37 static void aes_sigill(void) 38 { 39 /* AESE V0.16B, V0.16B */ 40 asm volatile(".inst 0x4e284800" : : : ); 41 } 42 43 static void atomics_sigill(void) 44 { 45 /* STADD W0, [SP] */ 46 asm volatile(".inst 0xb82003ff" : : : ); 47 } 48 49 static void crc32_sigill(void) 50 { 51 /* CRC32W W0, W0, W1 */ 52 asm volatile(".inst 0x1ac14800" : : : ); 53 } 54 55 static void cssc_sigill(void) 56 { 57 /* CNT x0, x0 */ 58 asm volatile(".inst 0xdac01c00" : : : "x0"); 59 } 60 61 static void fp_sigill(void) 62 { 63 asm volatile("fmov s0, #1"); 64 } 65 66 static void ilrcpc_sigill(void) 67 { 68 /* LDAPUR W0, [SP, #8] */ 69 asm volatile(".inst 0x994083e0" : : : ); 70 } 71 72 static void jscvt_sigill(void) 73 { 74 /* FJCVTZS W0, D0 */ 75 asm volatile(".inst 0x1e7e0000" : : : ); 76 } 77 78 static void lrcpc_sigill(void) 79 { 80 /* LDAPR W0, [SP, #0] */ 81 asm volatile(".inst 0xb8bfc3e0" : : : ); 82 } 83 84 static void mops_sigill(void) 85 { 86 char dst[1], src[1]; 87 register char *dstp asm ("x0") = dst; 88 register char *srcp asm ("x1") = src; 89 register long size asm ("x2") = 1; 90 91 /* CPYP [x0]!, [x1]!, x2! */ 92 asm volatile(".inst 0x1d010440" 93 : "+r" (dstp), "+r" (srcp), "+r" (size) 94 : 95 : "cc", "memory"); 96 } 97 98 static void pmull_sigill(void) 99 { 100 /* PMULL V0.1Q, V0.1D, V0.1D */ 101 asm volatile(".inst 0x0ee0e000" : : : ); 102 } 103 104 static void rng_sigill(void) 105 { 106 asm volatile("mrs x0, S3_3_C2_C4_0" : : : "x0"); 107 } 108 109 static void sha1_sigill(void) 110 { 111 /* SHA1H S0, S0 */ 112 asm volatile(".inst 0x5e280800" : : : ); 113 } 114 115 static void sha2_sigill(void) 116 { 117 /* SHA256H Q0, Q0, V0.4S */ 118 asm volatile(".inst 0x5e004000" : : : ); 119 } 120 121 static void sha512_sigill(void) 122 { 123 /* SHA512H Q0, Q0, V0.2D */ 124 asm volatile(".inst 0xce608000" : : : ); 125 } 126 127 static void sme_sigill(void) 128 { 129 /* RDSVL x0, #0 */ 130 asm volatile(".inst 0x04bf5800" : : : "x0"); 131 } 132 133 static void sme2_sigill(void) 134 { 135 /* SMSTART ZA */ 136 asm volatile("msr S0_3_C4_C5_3, xzr" : : : ); 137 138 /* ZERO ZT0 */ 139 asm volatile(".inst 0xc0480001" : : : ); 140 141 /* SMSTOP */ 142 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 143 } 144 145 static void sme2p1_sigill(void) 146 { 147 /* SMSTART SM */ 148 asm volatile("msr S0_3_C4_C3_3, xzr" : : : ); 149 150 /* BFCLAMP { Z0.H - Z1.H }, Z0.H, Z0.H */ 151 asm volatile(".inst 0xc120C000" : : : ); 152 153 /* SMSTOP */ 154 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 155 } 156 157 static void smei16i32_sigill(void) 158 { 159 /* SMSTART */ 160 asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); 161 162 /* SMOPA ZA0.S, P0/M, P0/M, Z0.B, Z0.B */ 163 asm volatile(".inst 0xa0800000" : : : ); 164 165 /* SMSTOP */ 166 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 167 } 168 169 static void smebi32i32_sigill(void) 170 { 171 /* SMSTART */ 172 asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); 173 174 /* BMOPA ZA0.S, P0/M, P0/M, Z0.B, Z0.B */ 175 asm volatile(".inst 0x80800008" : : : ); 176 177 /* SMSTOP */ 178 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 179 } 180 181 static void smeb16b16_sigill(void) 182 { 183 /* SMSTART */ 184 asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); 185 186 /* BFADD ZA.H[W0, 0], {Z0.H-Z1.H} */ 187 asm volatile(".inst 0xC1E41C00" : : : ); 188 189 /* SMSTOP */ 190 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 191 } 192 193 static void smef16f16_sigill(void) 194 { 195 /* SMSTART */ 196 asm volatile("msr S0_3_C4_C7_3, xzr" : : : ); 197 198 /* FADD ZA.H[W0, 0], { Z0.H-Z1.H } */ 199 asm volatile(".inst 0xc1a41C00" : : : ); 200 201 /* SMSTOP */ 202 asm volatile("msr S0_3_C4_C6_3, xzr" : : : ); 203 } 204 205 static void sve_sigill(void) 206 { 207 /* RDVL x0, #0 */ 208 asm volatile(".inst 0x04bf5000" : : : "x0"); 209 } 210 211 static void sve2_sigill(void) 212 { 213 /* SQABS Z0.b, P0/M, Z0.B */ 214 asm volatile(".inst 0x4408A000" : : : "z0"); 215 } 216 217 static void sve2p1_sigill(void) 218 { 219 /* BFADD Z0.H, Z0.H, Z0.H */ 220 asm volatile(".inst 0x65000000" : : : "z0"); 221 } 222 223 static void sveaes_sigill(void) 224 { 225 /* AESD z0.b, z0.b, z0.b */ 226 asm volatile(".inst 0x4522e400" : : : "z0"); 227 } 228 229 static void svepmull_sigill(void) 230 { 231 /* PMULLB Z0.Q, Z0.D, Z0.D */ 232 asm volatile(".inst 0x45006800" : : : "z0"); 233 } 234 235 static void svebitperm_sigill(void) 236 { 237 /* BDEP Z0.B, Z0.B, Z0.B */ 238 asm volatile(".inst 0x4500b400" : : : "z0"); 239 } 240 241 static void svesha3_sigill(void) 242 { 243 /* EOR3 Z0.D, Z0.D, Z0.D, Z0.D */ 244 asm volatile(".inst 0x4203800" : : : "z0"); 245 } 246 247 static void svesm4_sigill(void) 248 { 249 /* SM4E Z0.S, Z0.S, Z0.S */ 250 asm volatile(".inst 0x4523e000" : : : "z0"); 251 } 252 253 static void svei8mm_sigill(void) 254 { 255 /* USDOT Z0.S, Z0.B, Z0.B[0] */ 256 asm volatile(".inst 0x44a01800" : : : "z0"); 257 } 258 259 static void svef32mm_sigill(void) 260 { 261 /* FMMLA Z0.S, Z0.S, Z0.S */ 262 asm volatile(".inst 0x64a0e400" : : : "z0"); 263 } 264 265 static void svef64mm_sigill(void) 266 { 267 /* FMMLA Z0.D, Z0.D, Z0.D */ 268 asm volatile(".inst 0x64e0e400" : : : "z0"); 269 } 270 271 static void svebf16_sigill(void) 272 { 273 /* BFCVT Z0.H, P0/M, Z0.S */ 274 asm volatile(".inst 0x658aa000" : : : "z0"); 275 } 276 277 static void hbc_sigill(void) 278 { 279 /* BC.EQ +4 */ 280 asm volatile("cmp xzr, xzr\n" 281 ".inst 0x54000030" : : : "cc"); 282 } 283 284 static void uscat_sigbus(void) 285 { 286 /* unaligned atomic access */ 287 asm volatile("ADD x1, sp, #2" : : : ); 288 /* STADD W0, [X1] */ 289 asm volatile(".inst 0xb820003f" : : : ); 290 } 291 292 static const struct hwcap_data { 293 const char *name; 294 unsigned long at_hwcap; 295 unsigned long hwcap_bit; 296 const char *cpuinfo; 297 sig_fn sigill_fn; 298 bool sigill_reliable; 299 sig_fn sigbus_fn; 300 bool sigbus_reliable; 301 } hwcaps[] = { 302 { 303 .name = "AES", 304 .at_hwcap = AT_HWCAP, 305 .hwcap_bit = HWCAP_AES, 306 .cpuinfo = "aes", 307 .sigill_fn = aes_sigill, 308 }, 309 { 310 .name = "CRC32", 311 .at_hwcap = AT_HWCAP, 312 .hwcap_bit = HWCAP_CRC32, 313 .cpuinfo = "crc32", 314 .sigill_fn = crc32_sigill, 315 }, 316 { 317 .name = "CSSC", 318 .at_hwcap = AT_HWCAP2, 319 .hwcap_bit = HWCAP2_CSSC, 320 .cpuinfo = "cssc", 321 .sigill_fn = cssc_sigill, 322 }, 323 { 324 .name = "FP", 325 .at_hwcap = AT_HWCAP, 326 .hwcap_bit = HWCAP_FP, 327 .cpuinfo = "fp", 328 .sigill_fn = fp_sigill, 329 }, 330 { 331 .name = "JSCVT", 332 .at_hwcap = AT_HWCAP, 333 .hwcap_bit = HWCAP_JSCVT, 334 .cpuinfo = "jscvt", 335 .sigill_fn = jscvt_sigill, 336 }, 337 { 338 .name = "LRCPC", 339 .at_hwcap = AT_HWCAP, 340 .hwcap_bit = HWCAP_LRCPC, 341 .cpuinfo = "lrcpc", 342 .sigill_fn = lrcpc_sigill, 343 }, 344 { 345 .name = "LRCPC2", 346 .at_hwcap = AT_HWCAP, 347 .hwcap_bit = HWCAP_ILRCPC, 348 .cpuinfo = "ilrcpc", 349 .sigill_fn = ilrcpc_sigill, 350 }, 351 { 352 .name = "LSE", 353 .at_hwcap = AT_HWCAP, 354 .hwcap_bit = HWCAP_ATOMICS, 355 .cpuinfo = "atomics", 356 .sigill_fn = atomics_sigill, 357 }, 358 { 359 .name = "LSE2", 360 .at_hwcap = AT_HWCAP, 361 .hwcap_bit = HWCAP_USCAT, 362 .cpuinfo = "uscat", 363 .sigill_fn = atomics_sigill, 364 .sigbus_fn = uscat_sigbus, 365 .sigbus_reliable = true, 366 }, 367 { 368 .name = "MOPS", 369 .at_hwcap = AT_HWCAP2, 370 .hwcap_bit = HWCAP2_MOPS, 371 .cpuinfo = "mops", 372 .sigill_fn = mops_sigill, 373 .sigill_reliable = true, 374 }, 375 { 376 .name = "PMULL", 377 .at_hwcap = AT_HWCAP, 378 .hwcap_bit = HWCAP_PMULL, 379 .cpuinfo = "pmull", 380 .sigill_fn = pmull_sigill, 381 }, 382 { 383 .name = "RNG", 384 .at_hwcap = AT_HWCAP2, 385 .hwcap_bit = HWCAP2_RNG, 386 .cpuinfo = "rng", 387 .sigill_fn = rng_sigill, 388 }, 389 { 390 .name = "RPRFM", 391 .at_hwcap = AT_HWCAP2, 392 .hwcap_bit = HWCAP2_RPRFM, 393 .cpuinfo = "rprfm", 394 }, 395 { 396 .name = "SHA1", 397 .at_hwcap = AT_HWCAP, 398 .hwcap_bit = HWCAP_SHA1, 399 .cpuinfo = "sha1", 400 .sigill_fn = sha1_sigill, 401 }, 402 { 403 .name = "SHA2", 404 .at_hwcap = AT_HWCAP, 405 .hwcap_bit = HWCAP_SHA2, 406 .cpuinfo = "sha2", 407 .sigill_fn = sha2_sigill, 408 }, 409 { 410 .name = "SHA512", 411 .at_hwcap = AT_HWCAP, 412 .hwcap_bit = HWCAP_SHA512, 413 .cpuinfo = "sha512", 414 .sigill_fn = sha512_sigill, 415 }, 416 { 417 .name = "SME", 418 .at_hwcap = AT_HWCAP2, 419 .hwcap_bit = HWCAP2_SME, 420 .cpuinfo = "sme", 421 .sigill_fn = sme_sigill, 422 .sigill_reliable = true, 423 }, 424 { 425 .name = "SME2", 426 .at_hwcap = AT_HWCAP2, 427 .hwcap_bit = HWCAP2_SME2, 428 .cpuinfo = "sme2", 429 .sigill_fn = sme2_sigill, 430 .sigill_reliable = true, 431 }, 432 { 433 .name = "SME 2.1", 434 .at_hwcap = AT_HWCAP2, 435 .hwcap_bit = HWCAP2_SME2P1, 436 .cpuinfo = "sme2p1", 437 .sigill_fn = sme2p1_sigill, 438 }, 439 { 440 .name = "SME I16I32", 441 .at_hwcap = AT_HWCAP2, 442 .hwcap_bit = HWCAP2_SME_I16I32, 443 .cpuinfo = "smei16i32", 444 .sigill_fn = smei16i32_sigill, 445 }, 446 { 447 .name = "SME BI32I32", 448 .at_hwcap = AT_HWCAP2, 449 .hwcap_bit = HWCAP2_SME_BI32I32, 450 .cpuinfo = "smebi32i32", 451 .sigill_fn = smebi32i32_sigill, 452 }, 453 { 454 .name = "SME B16B16", 455 .at_hwcap = AT_HWCAP2, 456 .hwcap_bit = HWCAP2_SME_B16B16, 457 .cpuinfo = "smeb16b16", 458 .sigill_fn = smeb16b16_sigill, 459 }, 460 { 461 .name = "SME F16F16", 462 .at_hwcap = AT_HWCAP2, 463 .hwcap_bit = HWCAP2_SME_F16F16, 464 .cpuinfo = "smef16f16", 465 .sigill_fn = smef16f16_sigill, 466 }, 467 { 468 .name = "SVE", 469 .at_hwcap = AT_HWCAP, 470 .hwcap_bit = HWCAP_SVE, 471 .cpuinfo = "sve", 472 .sigill_fn = sve_sigill, 473 .sigill_reliable = true, 474 }, 475 { 476 .name = "SVE 2", 477 .at_hwcap = AT_HWCAP2, 478 .hwcap_bit = HWCAP2_SVE2, 479 .cpuinfo = "sve2", 480 .sigill_fn = sve2_sigill, 481 }, 482 { 483 .name = "SVE 2.1", 484 .at_hwcap = AT_HWCAP2, 485 .hwcap_bit = HWCAP2_SVE2P1, 486 .cpuinfo = "sve2p1", 487 .sigill_fn = sve2p1_sigill, 488 }, 489 { 490 .name = "SVE AES", 491 .at_hwcap = AT_HWCAP2, 492 .hwcap_bit = HWCAP2_SVEAES, 493 .cpuinfo = "sveaes", 494 .sigill_fn = sveaes_sigill, 495 }, 496 { 497 .name = "SVE2 PMULL", 498 .at_hwcap = AT_HWCAP2, 499 .hwcap_bit = HWCAP2_SVEPMULL, 500 .cpuinfo = "svepmull", 501 .sigill_fn = svepmull_sigill, 502 }, 503 { 504 .name = "SVE2 BITPERM", 505 .at_hwcap = AT_HWCAP2, 506 .hwcap_bit = HWCAP2_SVEBITPERM, 507 .cpuinfo = "svebitperm", 508 .sigill_fn = svebitperm_sigill, 509 }, 510 { 511 .name = "SVE2 SHA3", 512 .at_hwcap = AT_HWCAP2, 513 .hwcap_bit = HWCAP2_SVESHA3, 514 .cpuinfo = "svesha3", 515 .sigill_fn = svesha3_sigill, 516 }, 517 { 518 .name = "SVE2 SM4", 519 .at_hwcap = AT_HWCAP2, 520 .hwcap_bit = HWCAP2_SVESM4, 521 .cpuinfo = "svesm4", 522 .sigill_fn = svesm4_sigill, 523 }, 524 { 525 .name = "SVE2 I8MM", 526 .at_hwcap = AT_HWCAP2, 527 .hwcap_bit = HWCAP2_SVEI8MM, 528 .cpuinfo = "svei8mm", 529 .sigill_fn = svei8mm_sigill, 530 }, 531 { 532 .name = "SVE2 F32MM", 533 .at_hwcap = AT_HWCAP2, 534 .hwcap_bit = HWCAP2_SVEF32MM, 535 .cpuinfo = "svef32mm", 536 .sigill_fn = svef32mm_sigill, 537 }, 538 { 539 .name = "SVE2 F64MM", 540 .at_hwcap = AT_HWCAP2, 541 .hwcap_bit = HWCAP2_SVEF64MM, 542 .cpuinfo = "svef64mm", 543 .sigill_fn = svef64mm_sigill, 544 }, 545 { 546 .name = "SVE2 BF16", 547 .at_hwcap = AT_HWCAP2, 548 .hwcap_bit = HWCAP2_SVEBF16, 549 .cpuinfo = "svebf16", 550 .sigill_fn = svebf16_sigill, 551 }, 552 { 553 .name = "SVE2 EBF16", 554 .at_hwcap = AT_HWCAP2, 555 .hwcap_bit = HWCAP2_SVE_EBF16, 556 .cpuinfo = "sveebf16", 557 }, 558 { 559 .name = "HBC", 560 .at_hwcap = AT_HWCAP2, 561 .hwcap_bit = HWCAP2_HBC, 562 .cpuinfo = "hbc", 563 .sigill_fn = hbc_sigill, 564 .sigill_reliable = true, 565 }, 566 }; 567 568 typedef void (*sighandler_fn)(int, siginfo_t *, void *); 569 570 #define DEF_SIGHANDLER_FUNC(SIG, NUM) \ 571 static bool seen_##SIG; \ 572 static void handle_##SIG(int sig, siginfo_t *info, void *context) \ 573 { \ 574 ucontext_t *uc = context; \ 575 \ 576 seen_##SIG = true; \ 577 /* Skip over the offending instruction */ \ 578 uc->uc_mcontext.pc += 4; \ 579 } 580 581 DEF_SIGHANDLER_FUNC(sigill, SIGILL); 582 DEF_SIGHANDLER_FUNC(sigbus, SIGBUS); 583 584 bool cpuinfo_present(const char *name) 585 { 586 FILE *f; 587 char buf[2048], name_space[30], name_newline[30]; 588 char *s; 589 590 /* 591 * The feature should appear with a leading space and either a 592 * trailing space or a newline. 593 */ 594 snprintf(name_space, sizeof(name_space), " %s ", name); 595 snprintf(name_newline, sizeof(name_newline), " %s\n", name); 596 597 f = fopen("/proc/cpuinfo", "r"); 598 if (!f) { 599 ksft_print_msg("Failed to open /proc/cpuinfo\n"); 600 return false; 601 } 602 603 while (fgets(buf, sizeof(buf), f)) { 604 /* Features: line? */ 605 if (strncmp(buf, "Features\t:", strlen("Features\t:")) != 0) 606 continue; 607 608 /* All CPUs should be symmetric, don't read any more */ 609 fclose(f); 610 611 s = strstr(buf, name_space); 612 if (s) 613 return true; 614 s = strstr(buf, name_newline); 615 if (s) 616 return true; 617 618 return false; 619 } 620 621 ksft_print_msg("Failed to find Features in /proc/cpuinfo\n"); 622 fclose(f); 623 return false; 624 } 625 626 static int install_sigaction(int signum, sighandler_fn handler) 627 { 628 int ret; 629 struct sigaction sa; 630 631 memset(&sa, 0, sizeof(sa)); 632 sa.sa_sigaction = handler; 633 sa.sa_flags = SA_RESTART | SA_SIGINFO; 634 sigemptyset(&sa.sa_mask); 635 ret = sigaction(signum, &sa, NULL); 636 if (ret < 0) 637 ksft_exit_fail_msg("Failed to install SIGNAL handler: %s (%d)\n", 638 strerror(errno), errno); 639 640 return ret; 641 } 642 643 static void uninstall_sigaction(int signum) 644 { 645 if (sigaction(signum, NULL, NULL) < 0) 646 ksft_exit_fail_msg("Failed to uninstall SIGNAL handler: %s (%d)\n", 647 strerror(errno), errno); 648 } 649 650 #define DEF_INST_RAISE_SIG(SIG, NUM) \ 651 static bool inst_raise_##SIG(const struct hwcap_data *hwcap, \ 652 bool have_hwcap) \ 653 { \ 654 if (!hwcap->SIG##_fn) { \ 655 ksft_test_result_skip(#SIG"_%s\n", hwcap->name); \ 656 /* assume that it would raise exception in default */ \ 657 return true; \ 658 } \ 659 \ 660 install_sigaction(NUM, handle_##SIG); \ 661 \ 662 seen_##SIG = false; \ 663 hwcap->SIG##_fn(); \ 664 \ 665 if (have_hwcap) { \ 666 /* Should be able to use the extension */ \ 667 ksft_test_result(!seen_##SIG, \ 668 #SIG"_%s\n", hwcap->name); \ 669 } else if (hwcap->SIG##_reliable) { \ 670 /* Guaranteed a SIGNAL */ \ 671 ksft_test_result(seen_##SIG, \ 672 #SIG"_%s\n", hwcap->name); \ 673 } else { \ 674 /* Missing SIGNAL might be fine */ \ 675 ksft_print_msg(#SIG"_%sreported for %s\n", \ 676 seen_##SIG ? "" : "not ", \ 677 hwcap->name); \ 678 ksft_test_result_skip(#SIG"_%s\n", \ 679 hwcap->name); \ 680 } \ 681 \ 682 uninstall_sigaction(NUM); \ 683 return seen_##SIG; \ 684 } 685 686 DEF_INST_RAISE_SIG(sigill, SIGILL); 687 DEF_INST_RAISE_SIG(sigbus, SIGBUS); 688 689 int main(void) 690 { 691 int i; 692 const struct hwcap_data *hwcap; 693 bool have_cpuinfo, have_hwcap, raise_sigill; 694 695 ksft_print_header(); 696 ksft_set_plan(ARRAY_SIZE(hwcaps) * TESTS_PER_HWCAP); 697 698 for (i = 0; i < ARRAY_SIZE(hwcaps); i++) { 699 hwcap = &hwcaps[i]; 700 701 have_hwcap = getauxval(hwcap->at_hwcap) & hwcap->hwcap_bit; 702 have_cpuinfo = cpuinfo_present(hwcap->cpuinfo); 703 704 if (have_hwcap) 705 ksft_print_msg("%s present\n", hwcap->name); 706 707 ksft_test_result(have_hwcap == have_cpuinfo, 708 "cpuinfo_match_%s\n", hwcap->name); 709 710 /* 711 * Testing for SIGBUS only makes sense after make sure 712 * that the instruction does not cause a SIGILL signal. 713 */ 714 raise_sigill = inst_raise_sigill(hwcap, have_hwcap); 715 if (!raise_sigill) 716 inst_raise_sigbus(hwcap, have_hwcap); 717 else 718 ksft_test_result_skip("sigbus_%s\n", hwcap->name); 719 } 720 721 ksft_print_cnts(); 722 723 return 0; 724 } 725