mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
7.5 KiB
326 lines
7.5 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright 2008 Michael Ellerman, IBM Corporation. |
|
*/ |
|
|
|
#include <linux/kprobes.h> |
|
#include <linux/vmalloc.h> |
|
#include <linux/init.h> |
|
#include <linux/cpuhotplug.h> |
|
#include <linux/uaccess.h> |
|
#include <linux/jump_label.h> |
|
|
|
#include <asm/tlbflush.h> |
|
#include <asm/page.h> |
|
#include <asm/code-patching.h> |
|
#include <asm/inst.h> |
|
|
|
static int __patch_instruction(u32 *exec_addr, ppc_inst_t instr, u32 *patch_addr) |
|
{ |
|
if (!ppc_inst_prefixed(instr)) { |
|
u32 val = ppc_inst_val(instr); |
|
|
|
__put_kernel_nofault(patch_addr, &val, u32, failed); |
|
} else { |
|
u64 val = ppc_inst_as_ulong(instr); |
|
|
|
__put_kernel_nofault(patch_addr, &val, u64, failed); |
|
} |
|
|
|
asm ("dcbst 0, %0; sync; icbi 0,%1; sync; isync" :: "r" (patch_addr), |
|
"r" (exec_addr)); |
|
|
|
return 0; |
|
|
|
failed: |
|
return -EPERM; |
|
} |
|
|
|
int raw_patch_instruction(u32 *addr, ppc_inst_t instr) |
|
{ |
|
return __patch_instruction(addr, instr, addr); |
|
} |
|
|
|
#ifdef CONFIG_STRICT_KERNEL_RWX |
|
static DEFINE_PER_CPU(struct vm_struct *, text_poke_area); |
|
|
|
static int map_patch_area(void *addr, unsigned long text_poke_addr); |
|
static void unmap_patch_area(unsigned long addr); |
|
|
|
static int text_area_cpu_up(unsigned int cpu) |
|
{ |
|
struct vm_struct *area; |
|
unsigned long addr; |
|
int err; |
|
|
|
area = get_vm_area(PAGE_SIZE, VM_ALLOC); |
|
if (!area) { |
|
WARN_ONCE(1, "Failed to create text area for cpu %d\n", |
|
cpu); |
|
return -1; |
|
} |
|
|
|
// Map/unmap the area to ensure all page tables are pre-allocated |
|
addr = (unsigned long)area->addr; |
|
err = map_patch_area(empty_zero_page, addr); |
|
if (err) |
|
return err; |
|
|
|
unmap_patch_area(addr); |
|
|
|
this_cpu_write(text_poke_area, area); |
|
|
|
return 0; |
|
} |
|
|
|
static int text_area_cpu_down(unsigned int cpu) |
|
{ |
|
free_vm_area(this_cpu_read(text_poke_area)); |
|
return 0; |
|
} |
|
|
|
static __ro_after_init DEFINE_STATIC_KEY_FALSE(poking_init_done); |
|
|
|
/* |
|
* Although BUG_ON() is rude, in this case it should only happen if ENOMEM, and |
|
* we judge it as being preferable to a kernel that will crash later when |
|
* someone tries to use patch_instruction(). |
|
*/ |
|
void __init poking_init(void) |
|
{ |
|
BUG_ON(!cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, |
|
"powerpc/text_poke:online", text_area_cpu_up, |
|
text_area_cpu_down)); |
|
static_branch_enable(&poking_init_done); |
|
} |
|
|
|
/* |
|
* This can be called for kernel text or a module. |
|
*/ |
|
static int map_patch_area(void *addr, unsigned long text_poke_addr) |
|
{ |
|
unsigned long pfn; |
|
|
|
if (IS_ENABLED(CONFIG_MODULES) && is_vmalloc_or_module_addr(addr)) |
|
pfn = vmalloc_to_pfn(addr); |
|
else |
|
pfn = __pa_symbol(addr) >> PAGE_SHIFT; |
|
|
|
return map_kernel_page(text_poke_addr, (pfn << PAGE_SHIFT), PAGE_KERNEL); |
|
} |
|
|
|
static void unmap_patch_area(unsigned long addr) |
|
{ |
|
pte_t *ptep; |
|
pmd_t *pmdp; |
|
pud_t *pudp; |
|
p4d_t *p4dp; |
|
pgd_t *pgdp; |
|
|
|
pgdp = pgd_offset_k(addr); |
|
if (WARN_ON(pgd_none(*pgdp))) |
|
return; |
|
|
|
p4dp = p4d_offset(pgdp, addr); |
|
if (WARN_ON(p4d_none(*p4dp))) |
|
return; |
|
|
|
pudp = pud_offset(p4dp, addr); |
|
if (WARN_ON(pud_none(*pudp))) |
|
return; |
|
|
|
pmdp = pmd_offset(pudp, addr); |
|
if (WARN_ON(pmd_none(*pmdp))) |
|
return; |
|
|
|
ptep = pte_offset_kernel(pmdp, addr); |
|
if (WARN_ON(pte_none(*ptep))) |
|
return; |
|
|
|
/* |
|
* In hash, pte_clear flushes the tlb, in radix, we have to |
|
*/ |
|
pte_clear(&init_mm, addr, ptep); |
|
flush_tlb_kernel_range(addr, addr + PAGE_SIZE); |
|
} |
|
|
|
static int __do_patch_instruction(u32 *addr, ppc_inst_t instr) |
|
{ |
|
int err; |
|
u32 *patch_addr; |
|
unsigned long text_poke_addr; |
|
|
|
text_poke_addr = (unsigned long)__this_cpu_read(text_poke_area)->addr; |
|
patch_addr = (u32 *)(text_poke_addr + offset_in_page(addr)); |
|
|
|
err = map_patch_area(addr, text_poke_addr); |
|
if (err) |
|
return err; |
|
|
|
err = __patch_instruction(addr, instr, patch_addr); |
|
|
|
unmap_patch_area(text_poke_addr); |
|
|
|
return err; |
|
} |
|
|
|
static int do_patch_instruction(u32 *addr, ppc_inst_t instr) |
|
{ |
|
int err; |
|
unsigned long flags; |
|
|
|
/* |
|
* During early early boot patch_instruction is called |
|
* when text_poke_area is not ready, but we still need |
|
* to allow patching. We just do the plain old patching |
|
*/ |
|
if (!static_branch_likely(&poking_init_done)) |
|
return raw_patch_instruction(addr, instr); |
|
|
|
local_irq_save(flags); |
|
err = __do_patch_instruction(addr, instr); |
|
local_irq_restore(flags); |
|
|
|
return err; |
|
} |
|
#else /* !CONFIG_STRICT_KERNEL_RWX */ |
|
|
|
static int do_patch_instruction(u32 *addr, ppc_inst_t instr) |
|
{ |
|
return raw_patch_instruction(addr, instr); |
|
} |
|
|
|
#endif /* CONFIG_STRICT_KERNEL_RWX */ |
|
|
|
__ro_after_init DEFINE_STATIC_KEY_FALSE(init_mem_is_free); |
|
|
|
int patch_instruction(u32 *addr, ppc_inst_t instr) |
|
{ |
|
/* Make sure we aren't patching a freed init section */ |
|
if (static_branch_likely(&init_mem_is_free) && init_section_contains(addr, 4)) |
|
return 0; |
|
|
|
return do_patch_instruction(addr, instr); |
|
} |
|
NOKPROBE_SYMBOL(patch_instruction); |
|
|
|
int patch_branch(u32 *addr, unsigned long target, int flags) |
|
{ |
|
ppc_inst_t instr; |
|
|
|
if (create_branch(&instr, addr, target, flags)) |
|
return -ERANGE; |
|
|
|
return patch_instruction(addr, instr); |
|
} |
|
|
|
/* |
|
* Helper to check if a given instruction is a conditional branch |
|
* Derived from the conditional checks in analyse_instr() |
|
*/ |
|
bool is_conditional_branch(ppc_inst_t instr) |
|
{ |
|
unsigned int opcode = ppc_inst_primary_opcode(instr); |
|
|
|
if (opcode == 16) /* bc, bca, bcl, bcla */ |
|
return true; |
|
if (opcode == 19) { |
|
switch ((ppc_inst_val(instr) >> 1) & 0x3ff) { |
|
case 16: /* bclr, bclrl */ |
|
case 528: /* bcctr, bcctrl */ |
|
case 560: /* bctar, bctarl */ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
NOKPROBE_SYMBOL(is_conditional_branch); |
|
|
|
int create_cond_branch(ppc_inst_t *instr, const u32 *addr, |
|
unsigned long target, int flags) |
|
{ |
|
long offset; |
|
|
|
offset = target; |
|
if (! (flags & BRANCH_ABSOLUTE)) |
|
offset = offset - (unsigned long)addr; |
|
|
|
/* Check we can represent the target in the instruction format */ |
|
if (!is_offset_in_cond_branch_range(offset)) |
|
return 1; |
|
|
|
/* Mask out the flags and target, so they don't step on each other. */ |
|
*instr = ppc_inst(0x40000000 | (flags & 0x3FF0003) | (offset & 0xFFFC)); |
|
|
|
return 0; |
|
} |
|
|
|
int instr_is_relative_branch(ppc_inst_t instr) |
|
{ |
|
if (ppc_inst_val(instr) & BRANCH_ABSOLUTE) |
|
return 0; |
|
|
|
return instr_is_branch_iform(instr) || instr_is_branch_bform(instr); |
|
} |
|
|
|
int instr_is_relative_link_branch(ppc_inst_t instr) |
|
{ |
|
return instr_is_relative_branch(instr) && (ppc_inst_val(instr) & BRANCH_SET_LINK); |
|
} |
|
|
|
static unsigned long branch_iform_target(const u32 *instr) |
|
{ |
|
signed long imm; |
|
|
|
imm = ppc_inst_val(ppc_inst_read(instr)) & 0x3FFFFFC; |
|
|
|
/* If the top bit of the immediate value is set this is negative */ |
|
if (imm & 0x2000000) |
|
imm -= 0x4000000; |
|
|
|
if ((ppc_inst_val(ppc_inst_read(instr)) & BRANCH_ABSOLUTE) == 0) |
|
imm += (unsigned long)instr; |
|
|
|
return (unsigned long)imm; |
|
} |
|
|
|
static unsigned long branch_bform_target(const u32 *instr) |
|
{ |
|
signed long imm; |
|
|
|
imm = ppc_inst_val(ppc_inst_read(instr)) & 0xFFFC; |
|
|
|
/* If the top bit of the immediate value is set this is negative */ |
|
if (imm & 0x8000) |
|
imm -= 0x10000; |
|
|
|
if ((ppc_inst_val(ppc_inst_read(instr)) & BRANCH_ABSOLUTE) == 0) |
|
imm += (unsigned long)instr; |
|
|
|
return (unsigned long)imm; |
|
} |
|
|
|
unsigned long branch_target(const u32 *instr) |
|
{ |
|
if (instr_is_branch_iform(ppc_inst_read(instr))) |
|
return branch_iform_target(instr); |
|
else if (instr_is_branch_bform(ppc_inst_read(instr))) |
|
return branch_bform_target(instr); |
|
|
|
return 0; |
|
} |
|
|
|
int translate_branch(ppc_inst_t *instr, const u32 *dest, const u32 *src) |
|
{ |
|
unsigned long target; |
|
target = branch_target(src); |
|
|
|
if (instr_is_branch_iform(ppc_inst_read(src))) |
|
return create_branch(instr, dest, target, |
|
ppc_inst_val(ppc_inst_read(src))); |
|
else if (instr_is_branch_bform(ppc_inst_read(src))) |
|
return create_cond_branch(instr, dest, target, |
|
ppc_inst_val(ppc_inst_read(src))); |
|
|
|
return 1; |
|
}
|
|
|