/* * arch/arm64/kernel/patch_text.c * * Copyright (C) 2013 Linaro Limited. * Based on arch/arm/kernel/patch.c * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include #include #include #include #include #include #include "patch_text.h" #define opcode_to_mem_le(x) (x) #define opcode_to_mem_be(x) swab32(x) /* * get cpu endian mode from SCTLR_EL1.EE * 0x1=Big Endian, 0x0=Litle Endian. */ static inline unsigned int is_el1_big_endian(void) { u32 sctlr; asm volatile ("mrs %0, sctlr_el1" : "=r" (sctlr)); return (sctlr >> 25) & 0x1; } struct patch { void *addr; unsigned int insn; }; /* Patch text - Supported for kernel in AArch64 mode only */ void __kprobes __patch_text(void *addr, unsigned int insn) { int size = sizeof(u32); /* address 32-bit alignment check */ if ((unsigned long)addr % size) { pr_warn("text patching failed @unaligned addr %p\n", addr); return; } if (is_el1_big_endian()) *(u32 *) addr = opcode_to_mem_be(insn); else *(u32 *) addr = opcode_to_mem_le(insn); /* sync Icache, ISB is issued as part of this */ flush_icache_range((uintptr_t) (addr), (uintptr_t) (addr) + size); } static int __kprobes patch_text_stop_machine(void *data) { struct patch *patch = data; __patch_text(patch->addr, patch->insn); return 0; } void __kprobes patch_text(void *addr, unsigned int insn) { struct patch patch = { .addr = addr, .insn = insn, }; stop_machine(patch_text_stop_machine, &patch, cpu_online_mask); }