1e039ee4eSAndre Przywara /* 2e039ee4eSAndre Przywara * alternative runtime patching 3e039ee4eSAndre Przywara * inspired by the x86 version 4e039ee4eSAndre Przywara * 5e039ee4eSAndre Przywara * Copyright (C) 2014 ARM Ltd. 6e039ee4eSAndre Przywara * 7e039ee4eSAndre Przywara * This program is free software; you can redistribute it and/or modify 8e039ee4eSAndre Przywara * it under the terms of the GNU General Public License version 2 as 9e039ee4eSAndre Przywara * published by the Free Software Foundation. 10e039ee4eSAndre Przywara * 11e039ee4eSAndre Przywara * This program is distributed in the hope that it will be useful, 12e039ee4eSAndre Przywara * but WITHOUT ANY WARRANTY; without even the implied warranty of 13e039ee4eSAndre Przywara * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14e039ee4eSAndre Przywara * GNU General Public License for more details. 15e039ee4eSAndre Przywara * 16e039ee4eSAndre Przywara * You should have received a copy of the GNU General Public License 17e039ee4eSAndre Przywara * along with this program. If not, see <http://www.gnu.org/licenses/>. 18e039ee4eSAndre Przywara */ 19e039ee4eSAndre Przywara 20e039ee4eSAndre Przywara #define pr_fmt(fmt) "alternatives: " fmt 21e039ee4eSAndre Przywara 22e039ee4eSAndre Przywara #include <linux/init.h> 23e039ee4eSAndre Przywara #include <linux/cpu.h> 24e039ee4eSAndre Przywara #include <asm/cacheflush.h> 25e039ee4eSAndre Przywara #include <asm/alternative.h> 26e039ee4eSAndre Przywara #include <asm/cpufeature.h> 277616fc8bSMarc Zyngier #include <asm/insn.h> 28e039ee4eSAndre Przywara #include <linux/stop_machine.h> 29e039ee4eSAndre Przywara 307616fc8bSMarc Zyngier #define __ALT_PTR(a,f) (u32 *)((void *)&(a)->f + (a)->f) 317616fc8bSMarc Zyngier #define ALT_ORIG_PTR(a) __ALT_PTR(a, orig_offset) 327616fc8bSMarc Zyngier #define ALT_REPL_PTR(a) __ALT_PTR(a, alt_offset) 337616fc8bSMarc Zyngier 34e039ee4eSAndre Przywara extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; 35e039ee4eSAndre Przywara 36932ded4bSAndre Przywara struct alt_region { 37932ded4bSAndre Przywara struct alt_instr *begin; 38932ded4bSAndre Przywara struct alt_instr *end; 39932ded4bSAndre Przywara }; 40932ded4bSAndre Przywara 417616fc8bSMarc Zyngier /* 427616fc8bSMarc Zyngier * Check if the target PC is within an alternative block. 437616fc8bSMarc Zyngier */ 447616fc8bSMarc Zyngier static bool branch_insn_requires_update(struct alt_instr *alt, unsigned long pc) 457616fc8bSMarc Zyngier { 467616fc8bSMarc Zyngier unsigned long replptr; 477616fc8bSMarc Zyngier 487616fc8bSMarc Zyngier if (kernel_text_address(pc)) 497616fc8bSMarc Zyngier return 1; 507616fc8bSMarc Zyngier 517616fc8bSMarc Zyngier replptr = (unsigned long)ALT_REPL_PTR(alt); 527616fc8bSMarc Zyngier if (pc >= replptr && pc <= (replptr + alt->alt_len)) 537616fc8bSMarc Zyngier return 0; 547616fc8bSMarc Zyngier 557616fc8bSMarc Zyngier /* 567616fc8bSMarc Zyngier * Branching into *another* alternate sequence is doomed, and 577616fc8bSMarc Zyngier * we're not even trying to fix it up. 587616fc8bSMarc Zyngier */ 597616fc8bSMarc Zyngier BUG(); 607616fc8bSMarc Zyngier } 617616fc8bSMarc Zyngier 627616fc8bSMarc Zyngier static u32 get_alt_insn(struct alt_instr *alt, u32 *insnptr, u32 *altinsnptr) 637616fc8bSMarc Zyngier { 647616fc8bSMarc Zyngier u32 insn; 657616fc8bSMarc Zyngier 667616fc8bSMarc Zyngier insn = le32_to_cpu(*altinsnptr); 677616fc8bSMarc Zyngier 687616fc8bSMarc Zyngier if (aarch64_insn_is_branch_imm(insn)) { 697616fc8bSMarc Zyngier s32 offset = aarch64_get_branch_offset(insn); 707616fc8bSMarc Zyngier unsigned long target; 717616fc8bSMarc Zyngier 727616fc8bSMarc Zyngier target = (unsigned long)altinsnptr + offset; 737616fc8bSMarc Zyngier 747616fc8bSMarc Zyngier /* 757616fc8bSMarc Zyngier * If we're branching inside the alternate sequence, 767616fc8bSMarc Zyngier * do not rewrite the instruction, as it is already 777616fc8bSMarc Zyngier * correct. Otherwise, generate the new instruction. 787616fc8bSMarc Zyngier */ 797616fc8bSMarc Zyngier if (branch_insn_requires_update(alt, target)) { 807616fc8bSMarc Zyngier offset = target - (unsigned long)insnptr; 817616fc8bSMarc Zyngier insn = aarch64_set_branch_offset(insn, offset); 827616fc8bSMarc Zyngier } 837616fc8bSMarc Zyngier } 847616fc8bSMarc Zyngier 857616fc8bSMarc Zyngier return insn; 867616fc8bSMarc Zyngier } 877616fc8bSMarc Zyngier 88*ef5e724bSWill Deacon static void __apply_alternatives(void *alt_region) 89e039ee4eSAndre Przywara { 90e039ee4eSAndre Przywara struct alt_instr *alt; 91932ded4bSAndre Przywara struct alt_region *region = alt_region; 927616fc8bSMarc Zyngier u32 *origptr, *replptr; 93e039ee4eSAndre Przywara 94932ded4bSAndre Przywara for (alt = region->begin; alt < region->end; alt++) { 957616fc8bSMarc Zyngier u32 insn; 967616fc8bSMarc Zyngier int i, nr_inst; 977616fc8bSMarc Zyngier 98e039ee4eSAndre Przywara if (!cpus_have_cap(alt->cpufeature)) 99e039ee4eSAndre Przywara continue; 100e039ee4eSAndre Przywara 101fef7f2b2SMarc Zyngier BUG_ON(alt->alt_len != alt->orig_len); 102e039ee4eSAndre Przywara 103e039ee4eSAndre Przywara pr_info_once("patching kernel code\n"); 104e039ee4eSAndre Przywara 1057616fc8bSMarc Zyngier origptr = ALT_ORIG_PTR(alt); 1067616fc8bSMarc Zyngier replptr = ALT_REPL_PTR(alt); 1077616fc8bSMarc Zyngier nr_inst = alt->alt_len / sizeof(insn); 1087616fc8bSMarc Zyngier 1097616fc8bSMarc Zyngier for (i = 0; i < nr_inst; i++) { 1107616fc8bSMarc Zyngier insn = get_alt_insn(alt, origptr + i, replptr + i); 1117616fc8bSMarc Zyngier *(origptr + i) = cpu_to_le32(insn); 1127616fc8bSMarc Zyngier } 1137616fc8bSMarc Zyngier 114e039ee4eSAndre Przywara flush_icache_range((uintptr_t)origptr, 1157616fc8bSMarc Zyngier (uintptr_t)(origptr + nr_inst)); 116e039ee4eSAndre Przywara } 117e039ee4eSAndre Przywara } 118e039ee4eSAndre Przywara 119*ef5e724bSWill Deacon /* 120*ef5e724bSWill Deacon * We might be patching the stop_machine state machine, so implement a 121*ef5e724bSWill Deacon * really simple polling protocol here. 122*ef5e724bSWill Deacon */ 123*ef5e724bSWill Deacon static int __apply_alternatives_multi_stop(void *unused) 124e039ee4eSAndre Przywara { 125*ef5e724bSWill Deacon static int patched = 0; 126932ded4bSAndre Przywara struct alt_region region = { 127932ded4bSAndre Przywara .begin = __alt_instructions, 128932ded4bSAndre Przywara .end = __alt_instructions_end, 129932ded4bSAndre Przywara }; 130932ded4bSAndre Przywara 131*ef5e724bSWill Deacon /* We always have a CPU 0 at this point (__init) */ 132*ef5e724bSWill Deacon if (smp_processor_id()) { 133*ef5e724bSWill Deacon while (!READ_ONCE(patched)) 134*ef5e724bSWill Deacon cpu_relax(); 135*ef5e724bSWill Deacon } else { 136*ef5e724bSWill Deacon BUG_ON(patched); 137*ef5e724bSWill Deacon __apply_alternatives(®ion); 138*ef5e724bSWill Deacon /* Barriers provided by the cache flushing */ 139*ef5e724bSWill Deacon WRITE_ONCE(patched, 1); 140*ef5e724bSWill Deacon } 141*ef5e724bSWill Deacon 142*ef5e724bSWill Deacon return 0; 143*ef5e724bSWill Deacon } 144*ef5e724bSWill Deacon 145*ef5e724bSWill Deacon void __init apply_alternatives_all(void) 146*ef5e724bSWill Deacon { 147e039ee4eSAndre Przywara /* better not try code patching on a live SMP system */ 148*ef5e724bSWill Deacon stop_machine(__apply_alternatives_multi_stop, NULL, cpu_online_mask); 149932ded4bSAndre Przywara } 150932ded4bSAndre Przywara 151932ded4bSAndre Przywara void apply_alternatives(void *start, size_t length) 152932ded4bSAndre Przywara { 153932ded4bSAndre Przywara struct alt_region region = { 154932ded4bSAndre Przywara .begin = start, 155932ded4bSAndre Przywara .end = start + length, 156932ded4bSAndre Przywara }; 157932ded4bSAndre Przywara 158932ded4bSAndre Przywara __apply_alternatives(®ion); 159e039ee4eSAndre Przywara } 160e039ee4eSAndre Przywara 161e039ee4eSAndre Przywara void free_alternatives_memory(void) 162e039ee4eSAndre Przywara { 163e039ee4eSAndre Przywara free_reserved_area(__alt_instructions, __alt_instructions_end, 164e039ee4eSAndre Przywara 0, "alternatives"); 165e039ee4eSAndre Przywara } 166