jump_label.c 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. // SPDX-License-Identifier: GPL-2.0
  2. // Copyright (C) 2018 Cadence Design Systems Inc.
  3. #include <linux/cpu.h>
  4. #include <linux/jump_label.h>
  5. #include <linux/kernel.h>
  6. #include <linux/memory.h>
  7. #include <linux/stop_machine.h>
  8. #include <linux/types.h>
  9. #include <asm/cacheflush.h>
  10. #define J_OFFSET_MASK 0x0003ffff
  11. #define J_SIGN_MASK (~(J_OFFSET_MASK >> 1))
  12. #if defined(__XTENSA_EL__)
  13. #define J_INSN 0x6
  14. #define NOP_INSN 0x0020f0
  15. #elif defined(__XTENSA_EB__)
  16. #define J_INSN 0x60000000
  17. #define NOP_INSN 0x0f020000
  18. #else
  19. #error Unsupported endianness.
  20. #endif
  21. struct patch {
  22. atomic_t cpu_count;
  23. unsigned long addr;
  24. size_t sz;
  25. const void *data;
  26. };
  27. static void local_patch_text(unsigned long addr, const void *data, size_t sz)
  28. {
  29. memcpy((void *)addr, data, sz);
  30. local_flush_icache_range(addr, addr + sz);
  31. }
  32. static int patch_text_stop_machine(void *data)
  33. {
  34. struct patch *patch = data;
  35. if (atomic_inc_return(&patch->cpu_count) == num_online_cpus()) {
  36. local_patch_text(patch->addr, patch->data, patch->sz);
  37. atomic_inc(&patch->cpu_count);
  38. } else {
  39. while (atomic_read(&patch->cpu_count) <= num_online_cpus())
  40. cpu_relax();
  41. __invalidate_icache_range(patch->addr, patch->sz);
  42. }
  43. return 0;
  44. }
  45. static void patch_text(unsigned long addr, const void *data, size_t sz)
  46. {
  47. if (IS_ENABLED(CONFIG_SMP)) {
  48. struct patch patch = {
  49. .cpu_count = ATOMIC_INIT(0),
  50. .addr = addr,
  51. .sz = sz,
  52. .data = data,
  53. };
  54. stop_machine_cpuslocked(patch_text_stop_machine,
  55. &patch, cpu_online_mask);
  56. } else {
  57. unsigned long flags;
  58. local_irq_save(flags);
  59. local_patch_text(addr, data, sz);
  60. local_irq_restore(flags);
  61. }
  62. }
  63. void arch_jump_label_transform(struct jump_entry *e,
  64. enum jump_label_type type)
  65. {
  66. u32 d = (jump_entry_target(e) - (jump_entry_code(e) + 4));
  67. u32 insn;
  68. /* Jump only works within 128K of the J instruction. */
  69. BUG_ON(!((d & J_SIGN_MASK) == 0 ||
  70. (d & J_SIGN_MASK) == J_SIGN_MASK));
  71. if (type == JUMP_LABEL_JMP) {
  72. #if defined(__XTENSA_EL__)
  73. insn = ((d & J_OFFSET_MASK) << 6) | J_INSN;
  74. #elif defined(__XTENSA_EB__)
  75. insn = ((d & J_OFFSET_MASK) << 8) | J_INSN;
  76. #endif
  77. } else {
  78. insn = NOP_INSN;
  79. }
  80. patch_text(jump_entry_code(e), &insn, JUMP_LABEL_NOP_SIZE);
  81. }