FILE
traps_misaligned.c
File preview with syntax highlighting.
linux-6.19.14/arch/riscv/kernel/traps_misaligned.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (C) 2020 Western Digital Corporation or its affiliates. 4 */ 5#include <linux/kernel.h> 6#include <linux/init.h> 7#include <linux/mm.h> 8#include <linux/module.h> 9#include <linux/perf_event.h> 10#include <linux/irq.h> 11#include <linux/stringify.h> 12 13#include <asm/processor.h> 14#include <asm/ptrace.h> 15#include <asm/csr.h> 16#include <asm/entry-common.h> 17#include <asm/hwprobe.h> 18#include <asm/cpufeature.h> 19#include <asm/sbi.h> 20#include <asm/vector.h> 21#include <asm/insn.h> 22 23#ifdef CONFIG_FPU 24 25#define FP_GET_RD(insn) (insn >> 7 & 0x1F) 26 27extern void put_f32_reg(unsigned long fp_reg, unsigned long value); 28 29static int set_f32_rd(unsigned long insn, struct pt_regs *regs, 30 unsigned long val) 31{ 32 unsigned long fp_reg = FP_GET_RD(insn); 33 34 put_f32_reg(fp_reg, val); 35 regs->status |= SR_FS_DIRTY; 36 37 return 0; 38} 39 40extern void put_f64_reg(unsigned long fp_reg, unsigned long value); 41 42static int set_f64_rd(unsigned long insn, struct pt_regs *regs, u64 val) 43{ 44 unsigned long fp_reg = FP_GET_RD(insn); 45 unsigned long value; 46 47#if __riscv_xlen == 32 48 value = (unsigned long) &val; 49#else 50 value = val; 51#endif 52 put_f64_reg(fp_reg, value); 53 regs->status |= SR_FS_DIRTY; 54 55 return 0; 56} 57 58#if __riscv_xlen == 32 59extern void get_f64_reg(unsigned long fp_reg, u64 *value); 60 61static u64 get_f64_rs(unsigned long insn, u8 fp_reg_offset, 62 struct pt_regs *regs) 63{ 64 unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F; 65 u64 val; 66 67 get_f64_reg(fp_reg, &val); 68 regs->status |= SR_FS_DIRTY; 69 70 return val; 71} 72#else 73 74extern unsigned long get_f64_reg(unsigned long fp_reg); 75 76static unsigned long get_f64_rs(unsigned long insn, u8 fp_reg_offset, 77 struct pt_regs *regs) 78{ 79 unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F; 80 unsigned long val; 81 82 val = get_f64_reg(fp_reg); 83 regs->status |= SR_FS_DIRTY; 84 85 return val; 86} 87 88#endif 89 90extern unsigned long get_f32_reg(unsigned long fp_reg); 91 92static unsigned long get_f32_rs(unsigned long insn, u8 fp_reg_offset, 93 struct pt_regs *regs) 94{ 95 unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F; 96 unsigned long val; 97 98 val = get_f32_reg(fp_reg); 99 regs->status |= SR_FS_DIRTY; 100 101 return val; 102} 103 104#else /* CONFIG_FPU */ 105static void set_f32_rd(unsigned long insn, struct pt_regs *regs, 106 unsigned long val) {} 107 108static void set_f64_rd(unsigned long insn, struct pt_regs *regs, u64 val) {} 109 110static unsigned long get_f64_rs(unsigned long insn, u8 fp_reg_offset, 111 struct pt_regs *regs) 112{ 113 return 0; 114} 115 116static unsigned long get_f32_rs(unsigned long insn, u8 fp_reg_offset, 117 struct pt_regs *regs) 118{ 119 return 0; 120} 121 122#endif 123 124#define GET_F64_RS2(insn, regs) (get_f64_rs(insn, 20, regs)) 125#define GET_F64_RS2C(insn, regs) (get_f64_rs(insn, 2, regs)) 126#define GET_F64_RS2S(insn, regs) (get_f64_rs(RVC_RS2S(insn), 0, regs)) 127 128#define GET_F32_RS2(insn, regs) (get_f32_rs(insn, 20, regs)) 129#define GET_F32_RS2C(insn, regs) (get_f32_rs(insn, 2, regs)) 130#define GET_F32_RS2S(insn, regs) (get_f32_rs(RVC_RS2S(insn), 0, regs)) 131 132#define __read_insn(regs, insn, insn_addr, type) \ 133({ \ 134 int __ret; \ 135 \ 136 if (user_mode(regs)) { \ 137 __ret = get_user(insn, (type __user *) insn_addr); \ 138 } else { \ 139 insn = *(type *)insn_addr; \ 140 __ret = 0; \ 141 } \ 142 \ 143 __ret; \ 144}) 145 146static inline int get_insn(struct pt_regs *regs, ulong epc, ulong *r_insn) 147{ 148 ulong insn = 0; 149 150 if (epc & 0x2) { 151 ulong tmp = 0; 152 153 if (__read_insn(regs, insn, epc, u16)) 154 return -EFAULT; 155 /* __get_user() uses regular "lw" which sign extend the loaded 156 * value make sure to clear higher order bits in case we "or" it 157 * below with the upper 16 bits half. 158 */ 159 insn &= GENMASK(15, 0); 160 if ((insn & __INSN_LENGTH_MASK) != __INSN_LENGTH_32) { 161 *r_insn = insn; 162 return 0; 163 } 164 epc += sizeof(u16); 165 if (__read_insn(regs, tmp, epc, u16)) 166 return -EFAULT; 167 *r_insn = (tmp << 16) | insn; 168 169 return 0; 170 } else { 171 if (__read_insn(regs, insn, epc, u32)) 172 return -EFAULT; 173 if ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_32) { 174 *r_insn = insn; 175 return 0; 176 } 177 insn &= GENMASK(15, 0); 178 *r_insn = insn; 179 180 return 0; 181 } 182} 183 184union reg_data { 185 u8 data_bytes[8]; 186 ulong data_ulong; 187 u64 data_u64; 188}; 189 190/* sysctl hooks */ 191int unaligned_enabled __read_mostly = 1; /* Enabled by default */ 192 193#ifdef CONFIG_RISCV_VECTOR_MISALIGNED 194static int handle_vector_misaligned_load(struct pt_regs *regs) 195{ 196 unsigned long epc = regs->epc; 197 unsigned long insn; 198 199 if (get_insn(regs, epc, &insn)) 200 return -1; 201 202 /* Only return 0 when in check_vector_unaligned_access_emulated */ 203 if (*this_cpu_ptr(&vector_misaligned_access) == RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN) { 204 *this_cpu_ptr(&vector_misaligned_access) = RISCV_HWPROBE_MISALIGNED_VECTOR_UNSUPPORTED; 205 regs->epc = epc + INSN_LEN(insn); 206 return 0; 207 } 208 209 /* If vector instruction we don't emulate it yet */ 210 regs->epc = epc; 211 return -1; 212} 213#else 214static int handle_vector_misaligned_load(struct pt_regs *regs) 215{ 216 return -1; 217} 218#endif 219 220static int handle_scalar_misaligned_load(struct pt_regs *regs) 221{ 222 union reg_data val; 223 unsigned long epc = regs->epc; 224 unsigned long insn; 225 unsigned long addr = regs->badaddr; 226 int fp = 0, shift = 0, len = 0; 227 228 perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, addr); 229 230 *this_cpu_ptr(&misaligned_access_speed) = RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED; 231 232 if (!unaligned_enabled) 233 return -1; 234 235 if (user_mode(regs) && (current->thread.align_ctl & PR_UNALIGN_SIGBUS)) 236 return -1; 237 238 if (get_insn(regs, epc, &insn)) 239 return -1; 240 241 regs->epc = 0; 242 243 if ((insn & INSN_MASK_LW) == INSN_MATCH_LW) { 244 len = 4; 245 shift = 8 * (sizeof(unsigned long) - len); 246#if defined(CONFIG_64BIT) 247 } else if ((insn & INSN_MASK_LD) == INSN_MATCH_LD) { 248 len = 8; 249 shift = 8 * (sizeof(unsigned long) - len); 250 } else if ((insn & INSN_MASK_LWU) == INSN_MATCH_LWU) { 251 len = 4; 252#endif 253 } else if ((insn & INSN_MASK_FLD) == INSN_MATCH_FLD) { 254 fp = 1; 255 len = 8; 256 } else if ((insn & INSN_MASK_FLW) == INSN_MATCH_FLW) { 257 fp = 1; 258 len = 4; 259 } else if ((insn & INSN_MASK_LH) == INSN_MATCH_LH) { 260 len = 2; 261 shift = 8 * (sizeof(unsigned long) - len); 262 } else if ((insn & INSN_MASK_LHU) == INSN_MATCH_LHU) { 263 len = 2; 264#if defined(CONFIG_64BIT) 265 } else if ((insn & INSN_MASK_C_LD) == INSN_MATCH_C_LD) { 266 len = 8; 267 shift = 8 * (sizeof(unsigned long) - len); 268 insn = RVC_RS2S(insn) << SH_RD; 269 } else if ((insn & INSN_MASK_C_LDSP) == INSN_MATCH_C_LDSP && 270 ((insn >> SH_RD) & 0x1f)) { 271 len = 8; 272 shift = 8 * (sizeof(unsigned long) - len); 273#endif 274 } else if ((insn & INSN_MASK_C_LW) == INSN_MATCH_C_LW) { 275 len = 4; 276 shift = 8 * (sizeof(unsigned long) - len); 277 insn = RVC_RS2S(insn) << SH_RD; 278 } else if ((insn & INSN_MASK_C_LWSP) == INSN_MATCH_C_LWSP && 279 ((insn >> SH_RD) & 0x1f)) { 280 len = 4; 281 shift = 8 * (sizeof(unsigned long) - len); 282 } else if ((insn & INSN_MASK_C_FLD) == INSN_MATCH_C_FLD) { 283 fp = 1; 284 len = 8; 285 insn = RVC_RS2S(insn) << SH_RD; 286 } else if ((insn & INSN_MASK_C_FLDSP) == INSN_MATCH_C_FLDSP) { 287 fp = 1; 288 len = 8; 289#if defined(CONFIG_32BIT) 290 } else if ((insn & INSN_MASK_C_FLW) == INSN_MATCH_C_FLW) { 291 fp = 1; 292 len = 4; 293 insn = RVC_RS2S(insn) << SH_RD; 294 } else if ((insn & INSN_MASK_C_FLWSP) == INSN_MATCH_C_FLWSP) { 295 fp = 1; 296 len = 4; 297#endif 298 } else if ((insn & INSN_MASK_C_LHU) == INSN_MATCH_C_LHU) { 299 len = 2; 300 insn = RVC_RS2S(insn) << SH_RD; 301 } else if ((insn & INSN_MASK_C_LH) == INSN_MATCH_C_LH) { 302 len = 2; 303 shift = 8 * (sizeof(ulong) - len); 304 insn = RVC_RS2S(insn) << SH_RD; 305 } else { 306 regs->epc = epc; 307 return -1; 308 } 309 310 if (!IS_ENABLED(CONFIG_FPU) && fp) 311 return -EOPNOTSUPP; 312 313 val.data_u64 = 0; 314 if (user_mode(regs)) { 315 if (copy_from_user(&val, (u8 __user *)addr, len)) 316 return -1; 317 } else { 318 memcpy(&val, (u8 *)addr, len); 319 } 320 321 if (!fp) 322 SET_RD(insn, regs, (long)(val.data_ulong << shift) >> shift); 323 else if (len == 8) 324 set_f64_rd(insn, regs, val.data_u64); 325 else 326 set_f32_rd(insn, regs, val.data_ulong); 327 328 regs->epc = epc + INSN_LEN(insn); 329 330 return 0; 331} 332 333static int handle_scalar_misaligned_store(struct pt_regs *regs) 334{ 335 union reg_data val; 336 unsigned long epc = regs->epc; 337 unsigned long insn; 338 unsigned long addr = regs->badaddr; 339 int len = 0, fp = 0; 340 341 perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, addr); 342 343 if (!unaligned_enabled) 344 return -1; 345 346 if (user_mode(regs) && (current->thread.align_ctl & PR_UNALIGN_SIGBUS)) 347 return -1; 348 349 if (get_insn(regs, epc, &insn)) 350 return -1; 351 352 regs->epc = 0; 353 354 val.data_ulong = GET_RS2(insn, regs); 355 356 if ((insn & INSN_MASK_SW) == INSN_MATCH_SW) { 357 len = 4; 358#if defined(CONFIG_64BIT) 359 } else if ((insn & INSN_MASK_SD) == INSN_MATCH_SD) { 360 len = 8; 361#endif 362 } else if ((insn & INSN_MASK_FSD) == INSN_MATCH_FSD) { 363 fp = 1; 364 len = 8; 365 val.data_u64 = GET_F64_RS2(insn, regs); 366 } else if ((insn & INSN_MASK_FSW) == INSN_MATCH_FSW) { 367 fp = 1; 368 len = 4; 369 val.data_ulong = GET_F32_RS2(insn, regs); 370 } else if ((insn & INSN_MASK_SH) == INSN_MATCH_SH) { 371 len = 2; 372#if defined(CONFIG_64BIT) 373 } else if ((insn & INSN_MASK_C_SD) == INSN_MATCH_C_SD) { 374 len = 8; 375 val.data_ulong = GET_RS2S(insn, regs); 376 } else if ((insn & INSN_MASK_C_SDSP) == INSN_MATCH_C_SDSP) { 377 len = 8; 378 val.data_ulong = GET_RS2C(insn, regs); 379#endif 380 } else if ((insn & INSN_MASK_C_SW) == INSN_MATCH_C_SW) { 381 len = 4; 382 val.data_ulong = GET_RS2S(insn, regs); 383 } else if ((insn & INSN_MASK_C_SWSP) == INSN_MATCH_C_SWSP) { 384 len = 4; 385 val.data_ulong = GET_RS2C(insn, regs); 386 } else if ((insn & INSN_MASK_C_FSD) == INSN_MATCH_C_FSD) { 387 fp = 1; 388 len = 8; 389 val.data_u64 = GET_F64_RS2S(insn, regs); 390 } else if ((insn & INSN_MASK_C_FSDSP) == INSN_MATCH_C_FSDSP) { 391 fp = 1; 392 len = 8; 393 val.data_u64 = GET_F64_RS2C(insn, regs); 394#if !defined(CONFIG_64BIT) 395 } else if ((insn & INSN_MASK_C_FSW) == INSN_MATCH_C_FSW) { 396 fp = 1; 397 len = 4; 398 val.data_ulong = GET_F32_RS2S(insn, regs); 399 } else if ((insn & INSN_MASK_C_FSWSP) == INSN_MATCH_C_FSWSP) { 400 fp = 1; 401 len = 4; 402 val.data_ulong = GET_F32_RS2C(insn, regs); 403#endif 404 } else if ((insn & INSN_MASK_C_SH) == INSN_MATCH_C_SH) { 405 len = 2; 406 val.data_ulong = GET_RS2S(insn, regs); 407 } else { 408 regs->epc = epc; 409 return -1; 410 } 411 412 if (!IS_ENABLED(CONFIG_FPU) && fp) 413 return -EOPNOTSUPP; 414 415 if (user_mode(regs)) { 416 if (copy_to_user((u8 __user *)addr, &val, len)) 417 return -1; 418 } else { 419 memcpy((u8 *)addr, &val, len); 420 } 421 422 regs->epc = epc + INSN_LEN(insn); 423 424 return 0; 425} 426 427int handle_misaligned_load(struct pt_regs *regs) 428{ 429 unsigned long epc = regs->epc; 430 unsigned long insn; 431 432 if (IS_ENABLED(CONFIG_RISCV_VECTOR_MISALIGNED)) { 433 if (get_insn(regs, epc, &insn)) 434 return -1; 435 436 if (insn_is_vector(insn)) 437 return handle_vector_misaligned_load(regs); 438 } 439 440 if (IS_ENABLED(CONFIG_RISCV_SCALAR_MISALIGNED)) 441 return handle_scalar_misaligned_load(regs); 442 443 return -1; 444} 445 446int handle_misaligned_store(struct pt_regs *regs) 447{ 448 if (IS_ENABLED(CONFIG_RISCV_SCALAR_MISALIGNED)) 449 return handle_scalar_misaligned_store(regs); 450 451 return -1; 452} 453 454#ifdef CONFIG_RISCV_VECTOR_MISALIGNED 455void check_vector_unaligned_access_emulated(struct work_struct *work __always_unused) 456{ 457 long *mas_ptr = this_cpu_ptr(&vector_misaligned_access); 458 unsigned long tmp_var; 459 460 *mas_ptr = RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN; 461 462 kernel_vector_begin(); 463 /* 464 * In pre-13.0.0 versions of GCC, vector registers cannot appear in 465 * the clobber list. This inline asm clobbers v0, but since we do not 466 * currently build the kernel with V enabled, the v0 clobber arg is not 467 * needed (as the compiler will not emit vector code itself). If the kernel 468 * is changed to build with V enabled, the clobber arg will need to be 469 * added here. 470 */ 471 __asm__ __volatile__ ( 472 ".balign 4\n\t" 473 ".option push\n\t" 474 ".option arch, +zve32x\n\t" 475 " vsetivli zero, 1, e16, m1, ta, ma\n\t" // Vectors of 16b 476 " vle16.v v0, (%[ptr])\n\t" // Load bytes 477 ".option pop\n\t" 478 : : [ptr] "r" ((u8 *)&tmp_var + 1)); 479 kernel_vector_end(); 480} 481 482bool __init check_vector_unaligned_access_emulated_all_cpus(void) 483{ 484 int cpu; 485 486 /* 487 * While being documented as very slow, schedule_on_each_cpu() is used since 488 * kernel_vector_begin() expects irqs to be enabled or it will panic() 489 */ 490 schedule_on_each_cpu(check_vector_unaligned_access_emulated); 491 492 for_each_online_cpu(cpu) 493 if (per_cpu(vector_misaligned_access, cpu) 494 == RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN) 495 return false; 496 497 return true; 498} 499#else 500bool __init check_vector_unaligned_access_emulated_all_cpus(void) 501{ 502 return false; 503} 504#endif 505 506static bool all_cpus_unaligned_scalar_access_emulated(void) 507{ 508 int cpu; 509 510 for_each_online_cpu(cpu) 511 if (per_cpu(misaligned_access_speed, cpu) != 512 RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED) 513 return false; 514 515 return true; 516} 517 518#ifdef CONFIG_RISCV_SCALAR_MISALIGNED 519 520static bool unaligned_ctl __read_mostly; 521 522static void check_unaligned_access_emulated(void *arg __always_unused) 523{ 524 int cpu = smp_processor_id(); 525 long *mas_ptr = per_cpu_ptr(&misaligned_access_speed, cpu); 526 unsigned long tmp_var, tmp_val; 527 528 *mas_ptr = RISCV_HWPROBE_MISALIGNED_SCALAR_UNKNOWN; 529 530 __asm__ __volatile__ ( 531 " "REG_L" %[tmp], 1(%[ptr])\n" 532 : [tmp] "=r" (tmp_val) : [ptr] "r" (&tmp_var) : "memory"); 533} 534 535static int cpu_online_check_unaligned_access_emulated(unsigned int cpu) 536{ 537 long *mas_ptr = per_cpu_ptr(&misaligned_access_speed, cpu); 538 539 check_unaligned_access_emulated(NULL); 540 541 /* 542 * If unaligned_ctl is already set, this means that we detected that all 543 * CPUS uses emulated misaligned access at boot time. If that changed 544 * when hotplugging the new cpu, this is something we don't handle. 545 */ 546 if (unlikely(unaligned_ctl && (*mas_ptr != RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED))) { 547 pr_crit("CPU misaligned accesses non homogeneous (expected all emulated)\n"); 548 return -EINVAL; 549 } 550 551 return 0; 552} 553 554bool __init check_unaligned_access_emulated_all_cpus(void) 555{ 556 /* 557 * We can only support PR_UNALIGN controls if all CPUs have misaligned 558 * accesses emulated since tasks requesting such control can run on any 559 * CPU. 560 */ 561 on_each_cpu(check_unaligned_access_emulated, NULL, 1); 562 563 if (!all_cpus_unaligned_scalar_access_emulated()) 564 return false; 565 566 unaligned_ctl = true; 567 return true; 568} 569 570bool unaligned_ctl_available(void) 571{ 572 return unaligned_ctl; 573} 574#else 575bool __init check_unaligned_access_emulated_all_cpus(void) 576{ 577 return false; 578} 579static int cpu_online_check_unaligned_access_emulated(unsigned int cpu) 580{ 581 return 0; 582} 583#endif 584 585static bool misaligned_traps_delegated; 586 587#ifdef CONFIG_RISCV_SBI 588 589static int cpu_online_sbi_unaligned_setup(unsigned int cpu) 590{ 591 if (sbi_fwft_set(SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0) && 592 misaligned_traps_delegated) { 593 pr_crit("Misaligned trap delegation non homogeneous (expected delegated)"); 594 return -EINVAL; 595 } 596 597 return 0; 598} 599 600void __init unaligned_access_init(void) 601{ 602 int ret; 603 604 ret = sbi_fwft_set_online_cpus(SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0); 605 if (ret) 606 return; 607 608 misaligned_traps_delegated = true; 609 pr_info("SBI misaligned access exception delegation ok\n"); 610 /* 611 * Note that we don't have to take any specific action here, if 612 * the delegation is successful, then 613 * check_unaligned_access_emulated() will verify that indeed the 614 * platform traps on misaligned accesses. 615 */ 616} 617#else 618void __init unaligned_access_init(void) {} 619 620static int cpu_online_sbi_unaligned_setup(unsigned int cpu __always_unused) 621{ 622 return 0; 623} 624 625#endif 626 627int cpu_online_unaligned_access_init(unsigned int cpu) 628{ 629 int ret; 630 631 ret = cpu_online_sbi_unaligned_setup(cpu); 632 if (ret) 633 return ret; 634 635 return cpu_online_check_unaligned_access_emulated(cpu); 636} 637 638bool misaligned_traps_can_delegate(void) 639{ 640 /* 641 * Either we successfully requested misaligned traps delegation for all 642 * CPUs, or the SBI does not implement the FWFT extension but delegated 643 * the exception by default. 644 */ 645 return misaligned_traps_delegated || 646 all_cpus_unaligned_scalar_access_emulated(); 647} 648EXPORT_SYMBOL_GPL(misaligned_traps_can_delegate);
~/
Read-only file preview.