Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

arm64: armv8_deprecated: rework deprected instruction handling

Support for deprecated instructions can be enabled or disabled at
runtime. To handle this, the code in armv8_deprecated.c registers and
unregisters undef_hooks, and makes cross CPU calls to configure HW
support. This is rather complicated, and the synchronization required to
make this safe ends up serializing the handling of instructions which
have been trapped.

This patch simplifies the deprecated instruction handling by removing
the dynamic registration and unregistration, and changing the trap
handling code to determine whether a handler should be invoked. This
removes the need for dynamic list management, and simplifies the locking
requirements, making it possible to handle trapped instructions entirely
in parallel.

Where changing the emulation state requires a cross-call, this is
serialized by locally disabling interrupts, ensuring that the CPU is not
left in an inconsistent state.

To simplify sysctl management, each insn_emulation is given a separate
sysctl table, permitting these to be registered separately. The core
sysctl code will iterate over all of these when walking sysfs.

I've tested this with userspace programs which use each of the
deprecated instructions, and I've concurrently modified the support
level for each of the features back-and-forth between HW and emulated to
check that there are no spurious SIGILLs sent to userspace when the
support level is changed.

Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Joey Gouly <joey.gouly@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Will Deacon <will@kernel.org>
Link: https://lore.kernel.org/r/20221019144123.612388-10-mark.rutland@arm.com
Signed-off-by: Will Deacon <will@kernel.org>

authored by

Mark Rutland and committed by
Will Deacon
124c49b1 0c5f4162

+150 -193
+9 -10
arch/arm64/include/asm/traps.h
··· 13 13 14 14 struct pt_regs; 15 15 16 - struct undef_hook { 17 - struct list_head node; 18 - u32 instr_mask; 19 - u32 instr_val; 20 - u64 pstate_mask; 21 - u64 pstate_val; 22 - int (*fn)(struct pt_regs *regs, u32 instr); 23 - }; 16 + #ifdef CONFIG_ARMV8_DEPRECATED 17 + bool try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn); 18 + #else 19 + static inline bool 20 + try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn) 21 + { 22 + return false; 23 + } 24 + #endif /* CONFIG_ARMV8_DEPRECATED */ 24 25 25 - void register_undef_hook(struct undef_hook *hook); 26 - void unregister_undef_hook(struct undef_hook *hook); 27 26 void force_signal_inject(int signal, int code, unsigned long address, unsigned long err); 28 27 void arm64_notify_segfault(unsigned long addr); 29 28 void arm64_force_sig_fault(int signo, int code, unsigned long far, const char *str);
+140 -144
arch/arm64/kernel/armv8_deprecated.c
··· 38 38 enum legacy_insn_status { 39 39 INSN_DEPRECATED, 40 40 INSN_OBSOLETE, 41 + INSN_UNAVAILABLE, 41 42 }; 42 43 43 44 struct insn_emulation { 44 45 const char *name; 45 - struct list_head node; 46 46 enum legacy_insn_status status; 47 - struct undef_hook *hooks; 47 + bool (*try_emulate)(struct pt_regs *regs, 48 + u32 insn); 48 49 int (*set_hw_mode)(bool enable); 50 + 49 51 int current_mode; 50 52 int min; 51 53 int max; 54 + 55 + /* 56 + * sysctl for this emulation + a sentinal entry. 57 + */ 58 + struct ctl_table sysctl[2]; 52 59 }; 53 60 54 61 #define ARM_OPCODE_CONDTEST_FAIL 0 ··· 77 70 return ARM_OPCODE_CONDTEST_UNCOND; 78 71 } 79 72 73 + #ifdef CONFIG_SWP_EMULATION 80 74 /* 81 75 * Implement emulation of the SWP/SWPB instructions using load-exclusive and 82 76 * store-exclusive. ··· 230 222 return 0; 231 223 } 232 224 233 - /* 234 - * Only emulate SWP/SWPB executed in ARM state/User mode. 235 - * The kernel must be SWP free and SWP{B} does not exist in Thumb. 236 - */ 237 - static struct undef_hook swp_hooks[] = { 238 - { 239 - .instr_mask = 0x0fb00ff0, 240 - .instr_val = 0x01000090, 241 - .pstate_mask = PSR_AA32_MODE_MASK, 242 - .pstate_val = PSR_AA32_MODE_USR, 243 - .fn = swp_handler 244 - }, 245 - { } 246 - }; 225 + static bool try_emulate_swp(struct pt_regs *regs, u32 insn) 226 + { 227 + /* SWP{B} only exists in ARM state and does not exist in Thumb */ 228 + if (!compat_user_mode(regs) || compat_thumb_mode(regs)) 229 + return false; 230 + 231 + if ((insn & 0x0fb00ff0) != 0x01000090) 232 + return false; 233 + 234 + return swp_handler(regs, insn) == 0; 235 + } 247 236 248 237 static struct insn_emulation insn_swp = { 249 238 .name = "swp", 250 239 .status = INSN_OBSOLETE, 251 - .hooks = swp_hooks, 240 + .try_emulate = try_emulate_swp, 252 241 .set_hw_mode = NULL, 253 242 }; 243 + #endif /* CONFIG_SWP_EMULATION */ 254 244 245 + #ifdef CONFIG_CP15_BARRIER_EMULATION 255 246 static int cp15barrier_handler(struct pt_regs *regs, u32 instr) 256 247 { 257 248 perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc); ··· 313 306 return 0; 314 307 } 315 308 316 - static struct undef_hook cp15_barrier_hooks[] = { 317 - { 318 - .instr_mask = 0x0fff0fdf, 319 - .instr_val = 0x0e070f9a, 320 - .pstate_mask = PSR_AA32_MODE_MASK, 321 - .pstate_val = PSR_AA32_MODE_USR, 322 - .fn = cp15barrier_handler, 323 - }, 324 - { 325 - .instr_mask = 0x0fff0fff, 326 - .instr_val = 0x0e070f95, 327 - .pstate_mask = PSR_AA32_MODE_MASK, 328 - .pstate_val = PSR_AA32_MODE_USR, 329 - .fn = cp15barrier_handler, 330 - }, 331 - { } 332 - }; 309 + static bool try_emulate_cp15_barrier(struct pt_regs *regs, u32 insn) 310 + { 311 + if (!compat_user_mode(regs) || compat_thumb_mode(regs)) 312 + return false; 313 + 314 + if ((insn & 0x0fff0fdf) == 0x0e070f9a) 315 + return cp15barrier_handler(regs, insn) == 0; 316 + 317 + if ((insn & 0x0fff0fff) == 0x0e070f95) 318 + return cp15barrier_handler(regs, insn) == 0; 319 + 320 + return false; 321 + } 333 322 334 323 static struct insn_emulation insn_cp15_barrier = { 335 324 .name = "cp15_barrier", 336 325 .status = INSN_DEPRECATED, 337 - .hooks = cp15_barrier_hooks, 326 + .try_emulate = try_emulate_cp15_barrier, 338 327 .set_hw_mode = cp15_barrier_set_hw_mode, 339 328 }; 329 + #endif /* CONFIG_CP15_BARRIER_EMULATION */ 340 330 331 + #ifdef CONFIG_SETEND_EMULATION 341 332 static int setend_set_hw_mode(bool enable) 342 333 { 343 334 if (!cpu_supports_mixed_endian_el0()) ··· 383 378 return rc; 384 379 } 385 380 386 - static struct undef_hook setend_hooks[] = { 387 - { 388 - .instr_mask = 0xfffffdff, 389 - .instr_val = 0xf1010000, 390 - .pstate_mask = PSR_AA32_MODE_MASK, 391 - .pstate_val = PSR_AA32_MODE_USR, 392 - .fn = a32_setend_handler, 393 - }, 394 - { 395 - /* Thumb mode */ 396 - .instr_mask = 0xfffffff7, 397 - .instr_val = 0x0000b650, 398 - .pstate_mask = (PSR_AA32_T_BIT | PSR_AA32_MODE_MASK), 399 - .pstate_val = (PSR_AA32_T_BIT | PSR_AA32_MODE_USR), 400 - .fn = t16_setend_handler, 401 - }, 402 - {} 403 - }; 381 + static bool try_emulate_setend(struct pt_regs *regs, u32 insn) 382 + { 383 + if (compat_thumb_mode(regs) && 384 + (insn & 0xfffffff7) == 0x0000b650) 385 + return t16_setend_handler(regs, insn) == 0; 386 + 387 + if (compat_user_mode(regs) && 388 + (insn & 0xfffffdff) == 0xf1010000) 389 + return a32_setend_handler(regs, insn) == 0; 390 + 391 + return false; 392 + } 404 393 405 394 static struct insn_emulation insn_setend = { 406 395 .name = "setend", 407 396 .status = INSN_DEPRECATED, 408 - .hooks = setend_hooks, 397 + .try_emulate = try_emulate_setend, 409 398 .set_hw_mode = setend_set_hw_mode, 410 399 }; 400 + #endif /* CONFIG_SETEND_EMULATION */ 411 401 412 - static LIST_HEAD(insn_emulation); 413 - static int nr_insn_emulated __initdata; 414 - static DEFINE_RAW_SPINLOCK(insn_emulation_lock); 402 + static struct insn_emulation *insn_emulations[] = { 403 + #ifdef CONFIG_SWP_EMULATION 404 + &insn_swp, 405 + #endif 406 + #ifdef CONFIG_CP15_BARRIER_EMULATION 407 + &insn_cp15_barrier, 408 + #endif 409 + #ifdef CONFIG_SETEND_EMULATION 410 + &insn_setend, 411 + #endif 412 + }; 413 + 415 414 static DEFINE_MUTEX(insn_emulation_mutex); 416 - 417 - static void register_emulation_hooks(struct insn_emulation *insn) 418 - { 419 - struct undef_hook *hook; 420 - 421 - BUG_ON(!insn->hooks); 422 - 423 - for (hook = insn->hooks; hook->instr_mask; hook++) 424 - register_undef_hook(hook); 425 - 426 - pr_notice("Registered %s emulation handler\n", insn->name); 427 - } 428 - 429 - static void remove_emulation_hooks(struct insn_emulation *insn) 430 - { 431 - struct undef_hook *hook; 432 - 433 - BUG_ON(!insn->hooks); 434 - 435 - for (hook = insn->hooks; hook->instr_mask; hook++) 436 - unregister_undef_hook(hook); 437 - 438 - pr_notice("Removed %s emulation handler\n", insn->name); 439 - } 440 415 441 416 static void enable_insn_hw_mode(void *data) 442 417 { ··· 454 469 { 455 470 int rc = 0; 456 471 unsigned long flags; 457 - struct insn_emulation *insn; 458 472 459 - raw_spin_lock_irqsave(&insn_emulation_lock, flags); 460 - list_for_each_entry(insn, &insn_emulation, node) { 461 - bool enable = (insn->current_mode == INSN_HW); 473 + /* 474 + * Disable IRQs to serialize against an IPI from 475 + * run_all_cpu_set_hw_mode(), ensuring the HW is programmed to the most 476 + * recent enablement state if the two race with one another. 477 + */ 478 + local_irq_save(flags); 479 + for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { 480 + struct insn_emulation *insn = insn_emulations[i]; 481 + bool enable = READ_ONCE(insn->current_mode) == INSN_HW; 462 482 if (insn->set_hw_mode && insn->set_hw_mode(enable)) { 463 483 pr_warn("CPU[%u] cannot support the emulation of %s", 464 484 cpu, insn->name); 465 485 rc = -EINVAL; 466 486 } 467 487 } 468 - raw_spin_unlock_irqrestore(&insn_emulation_lock, flags); 488 + local_irq_restore(flags); 489 + 469 490 return rc; 470 491 } 471 492 ··· 484 493 case INSN_UNDEF: /* Nothing to be done */ 485 494 break; 486 495 case INSN_EMULATE: 487 - remove_emulation_hooks(insn); 488 496 break; 489 497 case INSN_HW: 490 498 if (!run_all_cpu_set_hw_mode(insn, false)) ··· 495 505 case INSN_UNDEF: 496 506 break; 497 507 case INSN_EMULATE: 498 - register_emulation_hooks(insn); 499 508 break; 500 509 case INSN_HW: 501 510 ret = run_all_cpu_set_hw_mode(insn, true); ··· 504 515 } 505 516 506 517 return ret; 507 - } 508 - 509 - static void __init register_insn_emulation(struct insn_emulation *insn) 510 - { 511 - unsigned long flags; 512 - 513 - insn->min = INSN_UNDEF; 514 - 515 - switch (insn->status) { 516 - case INSN_DEPRECATED: 517 - insn->current_mode = INSN_EMULATE; 518 - /* Disable the HW mode if it was turned on at early boot time */ 519 - run_all_cpu_set_hw_mode(insn, false); 520 - insn->max = INSN_HW; 521 - break; 522 - case INSN_OBSOLETE: 523 - insn->current_mode = INSN_UNDEF; 524 - insn->max = INSN_EMULATE; 525 - break; 526 - } 527 - 528 - raw_spin_lock_irqsave(&insn_emulation_lock, flags); 529 - list_add(&insn->node, &insn_emulation); 530 - nr_insn_emulated++; 531 - raw_spin_unlock_irqrestore(&insn_emulation_lock, flags); 532 - 533 - /* Register any handlers if required */ 534 - update_insn_emulation_mode(insn, INSN_UNDEF); 535 518 } 536 519 537 520 static int emulation_proc_handler(struct ctl_table *table, int write, ··· 523 562 ret = update_insn_emulation_mode(insn, prev_mode); 524 563 if (ret) { 525 564 /* Mode change failed, revert to previous mode. */ 526 - insn->current_mode = prev_mode; 565 + WRITE_ONCE(insn->current_mode, prev_mode); 527 566 update_insn_emulation_mode(insn, INSN_UNDEF); 528 567 } 529 568 ret: ··· 531 570 return ret; 532 571 } 533 572 534 - static void __init register_insn_emulation_sysctl(void) 573 + static void __init register_insn_emulation(struct insn_emulation *insn) 535 574 { 536 - unsigned long flags; 537 - int i = 0; 538 - struct insn_emulation *insn; 539 - struct ctl_table *insns_sysctl, *sysctl; 575 + struct ctl_table *sysctl; 540 576 541 - insns_sysctl = kcalloc(nr_insn_emulated + 1, sizeof(*sysctl), 542 - GFP_KERNEL); 543 - if (!insns_sysctl) 544 - return; 577 + insn->min = INSN_UNDEF; 545 578 546 - raw_spin_lock_irqsave(&insn_emulation_lock, flags); 547 - list_for_each_entry(insn, &insn_emulation, node) { 548 - sysctl = &insns_sysctl[i]; 579 + switch (insn->status) { 580 + case INSN_DEPRECATED: 581 + insn->current_mode = INSN_EMULATE; 582 + /* Disable the HW mode if it was turned on at early boot time */ 583 + run_all_cpu_set_hw_mode(insn, false); 584 + insn->max = INSN_HW; 585 + break; 586 + case INSN_OBSOLETE: 587 + insn->current_mode = INSN_UNDEF; 588 + insn->max = INSN_EMULATE; 589 + break; 590 + case INSN_UNAVAILABLE: 591 + insn->current_mode = INSN_UNDEF; 592 + insn->max = INSN_UNDEF; 593 + break; 594 + } 595 + 596 + /* Program the HW if required */ 597 + update_insn_emulation_mode(insn, INSN_UNDEF); 598 + 599 + if (insn->status != INSN_UNAVAILABLE) { 600 + sysctl = &insn->sysctl[0]; 549 601 550 602 sysctl->mode = 0644; 551 603 sysctl->maxlen = sizeof(int); ··· 568 594 sysctl->extra1 = &insn->min; 569 595 sysctl->extra2 = &insn->max; 570 596 sysctl->proc_handler = emulation_proc_handler; 571 - i++; 572 - } 573 - raw_spin_unlock_irqrestore(&insn_emulation_lock, flags); 574 597 575 - register_sysctl("abi", insns_sysctl); 598 + register_sysctl("abi", sysctl); 599 + } 600 + } 601 + 602 + bool try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn) 603 + { 604 + for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { 605 + struct insn_emulation *ie = insn_emulations[i]; 606 + 607 + if (ie->status == INSN_UNAVAILABLE) 608 + continue; 609 + 610 + /* 611 + * A trap may race with the mode being changed 612 + * INSN_EMULATE<->INSN_HW. Try to emulate the instruction to 613 + * avoid a spurious UNDEF. 614 + */ 615 + if (READ_ONCE(ie->current_mode) == INSN_UNDEF) 616 + continue; 617 + 618 + if (ie->try_emulate(regs, insn)) 619 + return true; 620 + } 621 + 622 + return false; 576 623 } 577 624 578 625 /* ··· 602 607 */ 603 608 static int __init armv8_deprecated_init(void) 604 609 { 605 - if (IS_ENABLED(CONFIG_SWP_EMULATION)) 606 - register_insn_emulation(&insn_swp); 610 + #ifdef CONFIG_SETEND_EMULATION 611 + if (!system_supports_mixed_endian_el0()) { 612 + insn_setend.status = INSN_UNAVAILABLE; 613 + pr_info("setend instruction emulation is not supported on this system\n"); 614 + } 607 615 608 - if (IS_ENABLED(CONFIG_CP15_BARRIER_EMULATION)) 609 - register_insn_emulation(&insn_cp15_barrier); 616 + #endif 617 + for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { 618 + struct insn_emulation *ie = insn_emulations[i]; 610 619 611 - if (IS_ENABLED(CONFIG_SETEND_EMULATION)) { 612 - if (system_supports_mixed_endian_el0()) 613 - register_insn_emulation(&insn_setend); 614 - else 615 - pr_info("setend instruction emulation is not supported on this system\n"); 620 + if (ie->status == INSN_UNAVAILABLE) 621 + continue; 622 + 623 + register_insn_emulation(ie); 616 624 } 617 625 618 626 cpuhp_setup_state_nocalls(CPUHP_AP_ARM64_ISNDEP_STARTING, 619 627 "arm64/isndep:starting", 620 628 run_all_insn_set_hw_mode, NULL); 621 - register_insn_emulation_sysctl(); 622 - 623 629 return 0; 624 630 } 625 631
+1 -39
arch/arm64/kernel/traps.c
··· 373 373 regs->pstate &= ~PSR_BTYPE_MASK; 374 374 } 375 375 376 - static LIST_HEAD(undef_hook); 377 - static DEFINE_RAW_SPINLOCK(undef_lock); 378 - 379 - void register_undef_hook(struct undef_hook *hook) 380 - { 381 - unsigned long flags; 382 - 383 - raw_spin_lock_irqsave(&undef_lock, flags); 384 - list_add(&hook->node, &undef_hook); 385 - raw_spin_unlock_irqrestore(&undef_lock, flags); 386 - } 387 - 388 - void unregister_undef_hook(struct undef_hook *hook) 389 - { 390 - unsigned long flags; 391 - 392 - raw_spin_lock_irqsave(&undef_lock, flags); 393 - list_del(&hook->node); 394 - raw_spin_unlock_irqrestore(&undef_lock, flags); 395 - } 396 - 397 376 static int user_insn_read(struct pt_regs *regs, u32 *insnp) 398 377 { 399 378 u32 instr; ··· 402 423 403 424 *insnp = instr; 404 425 return 0; 405 - } 406 - 407 - static int call_undef_hook(struct pt_regs *regs, u32 instr) 408 - { 409 - struct undef_hook *hook; 410 - unsigned long flags; 411 - int (*fn)(struct pt_regs *regs, u32 instr) = NULL; 412 - 413 - raw_spin_lock_irqsave(&undef_lock, flags); 414 - list_for_each_entry(hook, &undef_hook, node) 415 - if ((instr & hook->instr_mask) == hook->instr_val && 416 - (regs->pstate & hook->pstate_mask) == hook->pstate_val) 417 - fn = hook->fn; 418 - 419 - raw_spin_unlock_irqrestore(&undef_lock, flags); 420 - 421 - return fn ? fn(regs, instr) : 1; 422 426 } 423 427 424 428 void force_signal_inject(int signal, int code, unsigned long address, unsigned long err) ··· 464 502 if (try_emulate_mrs(regs, insn)) 465 503 return; 466 504 467 - if (call_undef_hook(regs, insn) == 0) 505 + if (try_emulate_armv8_deprecated(regs, insn)) 468 506 return; 469 507 470 508 out_err: