1 /* $NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $ */ 2 3 /* 4 * Copyright (c) 2026 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $"); 31 32 #include "sig_fpu.h" 33 34 #include <sys/types.h> 35 36 #include <x86/cpu_extended_state.h> 37 38 #include <cpuid.h> 39 #include <inttypes.h> 40 #include <sched.h> 41 #include <signal.h> 42 #include <stdbool.h> 43 #include <stdio.h> 44 45 #include "h_macros.h" 46 47 #ifdef __x86_64__ 48 # define NVECREGS 16 49 #else /* 32-bit */ 50 # define NVECREGS 8 51 #endif 52 53 #define X87_CW_RC __BITS(11,10) /* rounding control (mode) */ 54 #define X87_CW_PC __BITS(9,8) /* precision control */ 55 #define X87_CW_SW_EXC __BITS(5,0) /* exception bits */ 56 57 struct cpuid { 58 uint32_t edx; 59 uint32_t ecx; 60 uint32_t ebx; 61 uint32_t eax; 62 }; 63 64 static struct cpuid 65 cpuid(uint32_t eax_fn, uint32_t ecx_idx) 66 { 67 struct cpuid c; 68 69 if (!__get_cpuid_count(eax_fn, ecx_idx, 70 &c.eax, &c.ebx, &c.ecx, &c.edx)) 71 memset(&c, 0, sizeof(c)); 72 73 return c; 74 } 75 76 __printflike(4,5) 77 __noinline 78 static void 79 hexdump(FILE *fp, const void *buf, size_t len, const char *title, ...) 80 { 81 va_list va; 82 const uint8_t *p = buf; 83 size_t i; 84 85 fprintf(fp, "# "); 86 va_start(va, title); 87 vfprintf(fp, title, va); 88 va_end(va); 89 fprintf(fp, "\n"); 90 for (i = 0; i < len; i++) { 91 if ((i % 8) == 0) 92 fprintf(fp, " "); 93 fprintf(fp, " %02hhx", p[i]); 94 if ((i % 16) == 15) 95 fprintf(fp, "\n"); 96 } 97 if (i % 16) 98 fprintf(fp, "\n"); 99 } 100 101 bool 102 x87_supported(void) 103 { 104 #ifdef __x86_64__ 105 return true; 106 #else /* 32-bit */ 107 return cpuid(0x00000001, 0).edx & __BIT(0); 108 #endif 109 } 110 111 int 112 test_x87(volatile bool *ready, const volatile bool *done) 113 { 114 struct save87 s, s1; 115 uint32_t rcpc_exc; 116 int error = 0; 117 118 /* 119 * Gather the current FPU state so we have some reasonable 120 * content for the weird stuff when we restore from it. 121 */ 122 asm("wait\n" 123 " fnsave %0" 124 : /*out*/ "=m"(s)); 125 126 /* 127 * Randomize the floating-point stack entries ST(0),...,ST(7). 128 */ 129 arc4random_buf(s.s87_ac, sizeof(s.s87_ac)); 130 131 /* 132 * Randomize rounding control, precision control, and a set of 133 * exception bits. We don't want to raise any unmasked 134 * exceptions, so we'll raise _and_ mask them. 135 * 136 * XXX Consider testing the condition code, exception status, 137 * and top of stack bits in the status word too. 138 */ 139 rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC); 140 if (__SHIFTOUT(rcpc_exc, X87_CW_PC) == 1) /* reserved */ 141 rcpc_exc |= __SHIFTIN(3, X87_CW_PC); /* extended */ 142 s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC); 143 s.s87_cw |= rcpc_exc; 144 s.s87_sw &= ~X87_CW_SW_EXC; 145 s.s87_sw |= rcpc_exc & X87_CW_SW_EXC; 146 147 /* 148 * Load up the registers, report that we're ready, busy-wait 149 * with the registers still live -- so no subroutine calls -- 150 * until we're done, and then store the registers back to 151 * memory so we can check them. 152 */ 153 asm("\n" 154 " frstor %[s]\n" 155 " lock\n" 156 " orb $1,%[ready]\n" 157 "0: pause\n" 158 " cmpb $0,%[done]\n" 159 " lfence\n" 160 " je 0b\n" 161 " wait\n" 162 " fnsave %[s1]\n" 163 : /*out*/ [ready]"=m"(*ready), [s1]"=m"(s1) 164 : /*in*/ [done]"m"(*done), [s]"m"(s)); 165 166 /* 167 * If there are any mismatches between the before and after 168 * ST(i) registers, or the control word, or the status word, 169 * print them and fail. 170 */ 171 if (memcmp(s.s87_ac, s1.s87_ac, sizeof(s.s87_ac)) != 0) { 172 unsigned i; 173 174 for (i = 0; i < 8; i++) { 175 if (memcmp(&s.s87_ac[i], &s1.s87_ac[i], 176 sizeof(s.s87_ac[i])) == 0) 177 continue; 178 fprintf(stderr, "ST(%u)=0x%08x%016"PRIx64"," 179 " expected 0x%08x%016"PRIx64"\n", 180 i, 181 s1.s87_ac[i].f87_exp_sign, 182 s1.s87_ac[i].f87_mantissa, 183 s.s87_ac[i].f87_exp_sign, 184 s.s87_ac[i].f87_mantissa); 185 error |= 1 << i; 186 } 187 } 188 if ((s1.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC)) != 189 (s.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC))) { 190 fprintf(stderr, "cw=0x%04x, expected 0x%04x," 191 " diff 0x%04x\n", 192 s1.s87_cw, s.s87_cw, 193 (uint32_t)((s1.s87_cw ^ s.s87_cw) & 194 (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC))); 195 error |= 1 << 8; 196 } 197 if ((s1.s87_sw & X87_CW_SW_EXC) != 198 (s.s87_cw & X87_CW_SW_EXC)) { 199 fprintf(stderr, "sw=0x%04x, expected 0x%04x," 200 " diff 0x%04x\n", 201 s1.s87_sw, s.s87_sw, 202 (uint32_t)((s1.s87_sw ^ s.s87_sw) & 203 X87_CW_SW_EXC)); 204 error |= 1 << 9; 205 } 206 207 return error; 208 } 209 210 void 211 trash_x87(void) 212 { 213 struct save87 s; 214 uint32_t rcpc_exc; 215 216 /* 217 * Gather the current FPU state so we have some reasonable 218 * content for the weird stuff when we restore from it. 219 */ 220 asm("wait\n" 221 " fnsave %0" 222 : /*out*/ "=m"(s)); 223 224 /* 225 * Randomize the floating-point stack entries ST(0),...,ST(7). 226 */ 227 arc4random_buf(s.s87_ac, sizeof(s.s87_ac)); 228 229 /* 230 * Randomize rounding control, precision control, and a set of 231 * exception bits. We don't want to raise any unmasked 232 * exceptions, so we'll raise _and_ mask them. 233 * 234 * XXX Consider trashing the condition code, exception status, 235 * and top of stack bits in the status word too. 236 */ 237 rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC); 238 s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC); 239 s.s87_cw |= rcpc_exc; 240 s.s87_sw &= ~X87_CW_SW_EXC; 241 s.s87_sw |= rcpc_exc & X87_CW_SW_EXC; 242 243 /* 244 * Load the state into the FPU. 245 */ 246 asm volatile("frstor %0" :: /*in*/ "m"(s)); 247 } 248 249 #define MXCSR_MISALIGNEDEXC __BIT(17) /* misaligned exc fault */ 250 #define MXCSR_FTZ __BIT(15) /* flush to zero */ 251 #define MXCSR_RC __BITS(14,13) /* rounding control */ 252 #define MXCSR_EXCMASK __BITS(12,7) /* exceptions masked */ 253 #define MXCSR_DAZ __BIT(6) /* denormals are zero */ 254 #define MXCSR_EXCFLAG __BITS(5,0) /* exceptions raised */ 255 256 struct xmmregs { 257 struct { 258 uint8_t b[16]; 259 } __aligned(16) xmm[NVECREGS]; 260 uint32_t mxcsr; 261 }; 262 263 bool 264 xmm_supported(void) 265 { 266 #ifdef __x86_64__ 267 return true; 268 #else /* 32-bit */ 269 return cpuid(0x00000001, 0).edx & __BIT(25); 270 #endif 271 } 272 273 __attribute__((target("sse"))) 274 int 275 test_xmm(volatile bool *ready, const volatile bool *done) 276 { 277 struct xmmregs before, after; 278 uint32_t exc; 279 unsigned i; 280 int error = 0; 281 282 /* 283 * Randomize the xmm register content. Randomize plausible 284 * bits for the mxcsr. 285 */ 286 arc4random_buf(before.xmm, sizeof(before.xmm)); 287 before.mxcsr = arc4random() & (MXCSR_FTZ | MXCSR_RC | MXCSR_DAZ); 288 exc = arc4random() & 0x3f; 289 before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCMASK); 290 before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCFLAG); 291 292 /* 293 * Load up the registers, report that we're ready, busy-wait 294 * with the registers still live -- so no subroutine calls -- 295 * until we're done, and then store the registers back to 296 * memory so we can check them. 297 */ 298 asm("\n" 299 " ldmxcsr %[mxcsr_before]\n" 300 " movdqa 0*16(%[xmm_before]),%%xmm0\n" 301 " movdqa 1*16(%[xmm_before]),%%xmm1\n" 302 " movdqa 2*16(%[xmm_before]),%%xmm2\n" 303 " movdqa 3*16(%[xmm_before]),%%xmm3\n" 304 " movdqa 4*16(%[xmm_before]),%%xmm4\n" 305 " movdqa 5*16(%[xmm_before]),%%xmm5\n" 306 " movdqa 6*16(%[xmm_before]),%%xmm6\n" 307 " movdqa 7*16(%[xmm_before]),%%xmm7\n" 308 #ifdef __x86_64__ 309 " movdqa 8*16(%[xmm_before]),%%xmm8\n" 310 " movdqa 9*16(%[xmm_before]),%%xmm9\n" 311 " movdqa 10*16(%[xmm_before]),%%xmm10\n" 312 " movdqa 11*16(%[xmm_before]),%%xmm11\n" 313 " movdqa 12*16(%[xmm_before]),%%xmm12\n" 314 " movdqa 13*16(%[xmm_before]),%%xmm13\n" 315 " movdqa 14*16(%[xmm_before]),%%xmm14\n" 316 " movdqa 15*16(%[xmm_before]),%%xmm15\n" 317 #endif 318 " lock\n" 319 " orb $1,%[ready]\n" 320 "0: pause\n" 321 " cmpb $0,%[done]\n" 322 " lfence\n" 323 " je 0b\n" 324 " movdqa %%xmm0,0*16(%[xmm_after])\n" 325 " movdqa %%xmm1,1*16(%[xmm_after])\n" 326 " movdqa %%xmm2,2*16(%[xmm_after])\n" 327 " movdqa %%xmm3,3*16(%[xmm_after])\n" 328 " movdqa %%xmm4,4*16(%[xmm_after])\n" 329 " movdqa %%xmm5,5*16(%[xmm_after])\n" 330 " movdqa %%xmm6,6*16(%[xmm_after])\n" 331 " movdqa %%xmm7,7*16(%[xmm_after])\n" 332 #ifdef __x86_64__ 333 " movdqa %%xmm8,8*16(%[xmm_after])\n" 334 " movdqa %%xmm9,9*16(%[xmm_after])\n" 335 " movdqa %%xmm10,10*16(%[xmm_after])\n" 336 " movdqa %%xmm11,11*16(%[xmm_after])\n" 337 " movdqa %%xmm12,12*16(%[xmm_after])\n" 338 " movdqa %%xmm13,13*16(%[xmm_after])\n" 339 " movdqa %%xmm14,14*16(%[xmm_after])\n" 340 " movdqa %%xmm15,15*16(%[xmm_after])\n" 341 #endif 342 " stmxcsr %[mxcsr_after]\n" 343 : /*out*/ [ready]"=m"(*ready), "=m"(after.xmm), 344 [mxcsr_after]"=m"(after.mxcsr) 345 : /*in*/ [done]"m"(*done), "m"(before.xmm), 346 [mxcsr_before]"m"(before.mxcsr), 347 [xmm_before]"r"(&before.xmm), [xmm_after]"r"(&after.xmm) 348 : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", 349 "xmm6", "xmm7" 350 #ifdef __x86_64__ 351 , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", 352 "xmm13", "xmm14", "xmm15" 353 #endif 354 ); 355 356 /* 357 * If there are any mismatches between the before and after xmm 358 * registers, or the mxcsr, print them and fail. 359 */ 360 for (i = 0; i < __arraycount(before.xmm); i++) { 361 if (memcmp(&before.xmm[i], &after.xmm[i], 362 sizeof(before.xmm[i]))) { 363 fprintf(stderr, "xmm%u clobbered\n", i); 364 hexdump(stderr, &before.xmm[i], sizeof(before.xmm[i]), 365 "before"); 366 hexdump(stderr, &after.xmm[i], sizeof(after.xmm[i]), 367 "after"); 368 error |= 1 << i; 369 } 370 } 371 372 if (before.mxcsr != after.mxcsr) { 373 fprintf(stderr, "mxcsr clobbered:" 374 " before=0x%08"PRIx32", after=0x%08"PRIx32"\n", 375 before.mxcsr, after.mxcsr); 376 error |= 1 << 16; 377 } 378 379 return error; 380 } 381 382 __attribute__((target("sse"))) 383 void 384 trash_xmm(void) 385 { 386 struct xmmregs regs; 387 388 arc4random_buf(®s, sizeof(regs)); 389 390 asm("\n" 391 " movdqa 0*32(%[xmm]),%%xmm0\n" 392 " movdqa 1*32(%[xmm]),%%xmm1\n" 393 " movdqa 2*32(%[xmm]),%%xmm2\n" 394 " movdqa 3*32(%[xmm]),%%xmm3\n" 395 " movdqa 4*32(%[xmm]),%%xmm4\n" 396 " movdqa 5*32(%[xmm]),%%xmm5\n" 397 " movdqa 6*32(%[xmm]),%%xmm6\n" 398 " movdqa 7*32(%[xmm]),%%xmm7\n" 399 #ifdef __x86_64__ 400 " movdqa 8*32(%[xmm]),%%xmm8\n" 401 " movdqa 9*32(%[xmm]),%%xmm9\n" 402 " movdqa 10*32(%[xmm]),%%xmm10\n" 403 " movdqa 11*32(%[xmm]),%%xmm11\n" 404 " movdqa 12*32(%[xmm]),%%xmm12\n" 405 " movdqa 13*32(%[xmm]),%%xmm13\n" 406 " movdqa 14*32(%[xmm]),%%xmm14\n" 407 " movdqa 15*32(%[xmm]),%%xmm15\n" 408 #endif 409 : /*out*/ 410 : /*in*/ [xmm]"r"(regs.xmm), "m"(regs.xmm) 411 : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", 412 "xmm6", "xmm7" 413 #ifdef __x86_64__ 414 , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", 415 "xmm13", "xmm14", "xmm15" 416 #endif 417 ); 418 } 419 420 bool 421 ymm_supported(void) 422 { 423 return cpuid(0x00000001, 0).ecx & __BIT(28); 424 } 425 426 struct ymmregs { 427 struct { 428 uint8_t b[32]; 429 } __aligned(32) ymm[NVECREGS]; 430 }; 431 432 __attribute__((target("avx"))) 433 int 434 test_ymm(volatile bool *ready, const volatile bool *done) 435 { 436 struct ymmregs before, after; 437 unsigned i; 438 int error = 0; 439 440 /* 441 * Randomize the ymm register content. 442 */ 443 arc4random_buf(&before, sizeof(before)); 444 445 /* 446 * Load up the registers, report that we're ready, busy-wait 447 * with the registers still live -- so no subroutine calls -- 448 * until we're done, and then store the registers back to 449 * memory so we can check them. 450 */ 451 asm("\n" 452 " vmovdqa 0*32(%[before_ymm]),%%ymm0\n" 453 " vmovdqa 1*32(%[before_ymm]),%%ymm1\n" 454 " vmovdqa 2*32(%[before_ymm]),%%ymm2\n" 455 " vmovdqa 3*32(%[before_ymm]),%%ymm3\n" 456 " vmovdqa 4*32(%[before_ymm]),%%ymm4\n" 457 " vmovdqa 5*32(%[before_ymm]),%%ymm5\n" 458 " vmovdqa 6*32(%[before_ymm]),%%ymm6\n" 459 " vmovdqa 7*32(%[before_ymm]),%%ymm7\n" 460 #ifdef __x86_64__ 461 " vmovdqa 8*32(%[before_ymm]),%%ymm8\n" 462 " vmovdqa 9*32(%[before_ymm]),%%ymm9\n" 463 " vmovdqa 10*32(%[before_ymm]),%%ymm10\n" 464 " vmovdqa 11*32(%[before_ymm]),%%ymm11\n" 465 " vmovdqa 12*32(%[before_ymm]),%%ymm12\n" 466 " vmovdqa 13*32(%[before_ymm]),%%ymm13\n" 467 " vmovdqa 14*32(%[before_ymm]),%%ymm14\n" 468 " vmovdqa 15*32(%[before_ymm]),%%ymm15\n" 469 #endif 470 " lock\n" 471 " orb $1,%[ready]\n" 472 "0: pause\n" 473 " cmpb $0,%[done]\n" 474 " lfence\n" 475 " je 0b\n" 476 " vmovdqa %%ymm0,0*32(%[after_ymm])\n" 477 " vmovdqa %%ymm1,1*32(%[after_ymm])\n" 478 " vmovdqa %%ymm2,2*32(%[after_ymm])\n" 479 " vmovdqa %%ymm3,3*32(%[after_ymm])\n" 480 " vmovdqa %%ymm4,4*32(%[after_ymm])\n" 481 " vmovdqa %%ymm5,5*32(%[after_ymm])\n" 482 " vmovdqa %%ymm6,6*32(%[after_ymm])\n" 483 " vmovdqa %%ymm7,7*32(%[after_ymm])\n" 484 #ifdef __x86_64__ 485 " vmovdqa %%ymm8,8*32(%[after_ymm])\n" 486 " vmovdqa %%ymm9,9*32(%[after_ymm])\n" 487 " vmovdqa %%ymm10,10*32(%[after_ymm])\n" 488 " vmovdqa %%ymm11,11*32(%[after_ymm])\n" 489 " vmovdqa %%ymm12,12*32(%[after_ymm])\n" 490 " vmovdqa %%ymm13,13*32(%[after_ymm])\n" 491 " vmovdqa %%ymm14,14*32(%[after_ymm])\n" 492 " vmovdqa %%ymm15,15*32(%[after_ymm])\n" 493 #endif 494 : /*out*/ [ready]"=m"(*ready), "=m"(after.ymm) 495 : /*in*/ [done]"m"(*done), "m"(before.ymm), 496 [before_ymm]"r"(before.ymm), [after_ymm]"r"(after.ymm) 497 : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", 498 "ymm6", "ymm7" 499 #ifdef __x86_64__ 500 , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", 501 "ymm13", "ymm14", "ymm15" 502 #endif 503 ); 504 505 for (i = 0; i < __arraycount(before.ymm); i++) { 506 if (memcmp(&before.ymm[i], &after.ymm[i], 507 sizeof(before.ymm[i]))) { 508 fprintf(stderr, "ymm%u clobbered\n", i); 509 hexdump(stderr, &before.ymm[i], sizeof(before.ymm[i]), 510 "before"); 511 hexdump(stderr, &after.ymm[i], sizeof(after.ymm[i]), 512 "after"); 513 error |= 1 << i; 514 } 515 } 516 517 return error; 518 } 519 520 __attribute__((target("avx"))) 521 void 522 trash_ymm(void) 523 { 524 struct ymmregs regs; 525 526 arc4random_buf(®s, sizeof(regs)); 527 528 asm("\n" 529 " vmovdqa 0*32(%[ymm]),%%ymm0\n" 530 " vmovdqa 1*32(%[ymm]),%%ymm1\n" 531 " vmovdqa 2*32(%[ymm]),%%ymm2\n" 532 " vmovdqa 3*32(%[ymm]),%%ymm3\n" 533 " vmovdqa 4*32(%[ymm]),%%ymm4\n" 534 " vmovdqa 5*32(%[ymm]),%%ymm5\n" 535 " vmovdqa 6*32(%[ymm]),%%ymm6\n" 536 " vmovdqa 7*32(%[ymm]),%%ymm7\n" 537 #ifdef __x86_64__ 538 " vmovdqa 8*32(%[ymm]),%%ymm8\n" 539 " vmovdqa 9*32(%[ymm]),%%ymm9\n" 540 " vmovdqa 10*32(%[ymm]),%%ymm10\n" 541 " vmovdqa 11*32(%[ymm]),%%ymm11\n" 542 " vmovdqa 12*32(%[ymm]),%%ymm12\n" 543 " vmovdqa 13*32(%[ymm]),%%ymm13\n" 544 " vmovdqa 14*32(%[ymm]),%%ymm14\n" 545 " vmovdqa 15*32(%[ymm]),%%ymm15\n" 546 #endif 547 : /*out*/ 548 : /*in*/ "m"(regs.ymm), [ymm]"r"(regs.ymm) 549 : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", 550 "ymm6", "ymm7" 551 #ifdef __x86_64__ 552 , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", 553 "ymm13", "ymm14", "ymm15" 554 #endif 555 ); 556 } 557