162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2008 Michael Ellerman, IBM Corporation. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/kprobes.h> 762306a36Sopenharmony_ci#include <linux/mmu_context.h> 862306a36Sopenharmony_ci#include <linux/random.h> 962306a36Sopenharmony_ci#include <linux/vmalloc.h> 1062306a36Sopenharmony_ci#include <linux/init.h> 1162306a36Sopenharmony_ci#include <linux/cpuhotplug.h> 1262306a36Sopenharmony_ci#include <linux/uaccess.h> 1362306a36Sopenharmony_ci#include <linux/jump_label.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <asm/debug.h> 1662306a36Sopenharmony_ci#include <asm/pgalloc.h> 1762306a36Sopenharmony_ci#include <asm/tlb.h> 1862306a36Sopenharmony_ci#include <asm/tlbflush.h> 1962306a36Sopenharmony_ci#include <asm/page.h> 2062306a36Sopenharmony_ci#include <asm/code-patching.h> 2162306a36Sopenharmony_ci#include <asm/inst.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic int __patch_instruction(u32 *exec_addr, ppc_inst_t instr, u32 *patch_addr) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci if (!ppc_inst_prefixed(instr)) { 2662306a36Sopenharmony_ci u32 val = ppc_inst_val(instr); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci __put_kernel_nofault(patch_addr, &val, u32, failed); 2962306a36Sopenharmony_ci } else { 3062306a36Sopenharmony_ci u64 val = ppc_inst_as_ulong(instr); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci __put_kernel_nofault(patch_addr, &val, u64, failed); 3362306a36Sopenharmony_ci } 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci asm ("dcbst 0, %0; sync; icbi 0,%1; sync; isync" :: "r" (patch_addr), 3662306a36Sopenharmony_ci "r" (exec_addr)); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci return 0; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cifailed: 4162306a36Sopenharmony_ci return -EPERM; 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ciint raw_patch_instruction(u32 *addr, ppc_inst_t instr) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci return __patch_instruction(addr, instr, addr); 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistruct patch_context { 5062306a36Sopenharmony_ci union { 5162306a36Sopenharmony_ci struct vm_struct *area; 5262306a36Sopenharmony_ci struct mm_struct *mm; 5362306a36Sopenharmony_ci }; 5462306a36Sopenharmony_ci unsigned long addr; 5562306a36Sopenharmony_ci pte_t *pte; 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct patch_context, cpu_patching_context); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int map_patch_area(void *addr, unsigned long text_poke_addr); 6162306a36Sopenharmony_cistatic void unmap_patch_area(unsigned long addr); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic bool mm_patch_enabled(void) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci return IS_ENABLED(CONFIG_SMP) && radix_enabled(); 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* 6962306a36Sopenharmony_ci * The following applies for Radix MMU. Hash MMU has different requirements, 7062306a36Sopenharmony_ci * and so is not supported. 7162306a36Sopenharmony_ci * 7262306a36Sopenharmony_ci * Changing mm requires context synchronising instructions on both sides of 7362306a36Sopenharmony_ci * the context switch, as well as a hwsync between the last instruction for 7462306a36Sopenharmony_ci * which the address of an associated storage access was translated using 7562306a36Sopenharmony_ci * the current context. 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * switch_mm_irqs_off() performs an isync after the context switch. It is 7862306a36Sopenharmony_ci * the responsibility of the caller to perform the CSI and hwsync before 7962306a36Sopenharmony_ci * starting/stopping the temp mm. 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_cistatic struct mm_struct *start_using_temp_mm(struct mm_struct *temp_mm) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct mm_struct *orig_mm = current->active_mm; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci lockdep_assert_irqs_disabled(); 8662306a36Sopenharmony_ci switch_mm_irqs_off(orig_mm, temp_mm, current); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci WARN_ON(!mm_is_thread_local(temp_mm)); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci suspend_breakpoints(); 9162306a36Sopenharmony_ci return orig_mm; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic void stop_using_temp_mm(struct mm_struct *temp_mm, 9562306a36Sopenharmony_ci struct mm_struct *orig_mm) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci lockdep_assert_irqs_disabled(); 9862306a36Sopenharmony_ci switch_mm_irqs_off(temp_mm, orig_mm, current); 9962306a36Sopenharmony_ci restore_breakpoints(); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int text_area_cpu_up(unsigned int cpu) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct vm_struct *area; 10562306a36Sopenharmony_ci unsigned long addr; 10662306a36Sopenharmony_ci int err; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci area = get_vm_area(PAGE_SIZE, VM_ALLOC); 10962306a36Sopenharmony_ci if (!area) { 11062306a36Sopenharmony_ci WARN_ONCE(1, "Failed to create text area for cpu %d\n", 11162306a36Sopenharmony_ci cpu); 11262306a36Sopenharmony_ci return -1; 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci // Map/unmap the area to ensure all page tables are pre-allocated 11662306a36Sopenharmony_ci addr = (unsigned long)area->addr; 11762306a36Sopenharmony_ci err = map_patch_area(empty_zero_page, addr); 11862306a36Sopenharmony_ci if (err) 11962306a36Sopenharmony_ci return err; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci unmap_patch_area(addr); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.area, area); 12462306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.addr, addr); 12562306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.pte, virt_to_kpte(addr)); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci return 0; 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic int text_area_cpu_down(unsigned int cpu) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci free_vm_area(this_cpu_read(cpu_patching_context.area)); 13362306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.area, NULL); 13462306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.addr, 0); 13562306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.pte, NULL); 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic void put_patching_mm(struct mm_struct *mm, unsigned long patching_addr) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci struct mmu_gather tlb; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci tlb_gather_mmu(&tlb, mm); 14462306a36Sopenharmony_ci free_pgd_range(&tlb, patching_addr, patching_addr + PAGE_SIZE, 0, 0); 14562306a36Sopenharmony_ci mmput(mm); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_cistatic int text_area_cpu_up_mm(unsigned int cpu) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct mm_struct *mm; 15162306a36Sopenharmony_ci unsigned long addr; 15262306a36Sopenharmony_ci pte_t *pte; 15362306a36Sopenharmony_ci spinlock_t *ptl; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci mm = mm_alloc(); 15662306a36Sopenharmony_ci if (WARN_ON(!mm)) 15762306a36Sopenharmony_ci goto fail_no_mm; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* 16062306a36Sopenharmony_ci * Choose a random page-aligned address from the interval 16162306a36Sopenharmony_ci * [PAGE_SIZE .. DEFAULT_MAP_WINDOW - PAGE_SIZE]. 16262306a36Sopenharmony_ci * The lower address bound is PAGE_SIZE to avoid the zero-page. 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_ci addr = (1 + (get_random_long() % (DEFAULT_MAP_WINDOW / PAGE_SIZE - 2))) << PAGE_SHIFT; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* 16762306a36Sopenharmony_ci * PTE allocation uses GFP_KERNEL which means we need to 16862306a36Sopenharmony_ci * pre-allocate the PTE here because we cannot do the 16962306a36Sopenharmony_ci * allocation during patching when IRQs are disabled. 17062306a36Sopenharmony_ci * 17162306a36Sopenharmony_ci * Using get_locked_pte() to avoid open coding, the lock 17262306a36Sopenharmony_ci * is unnecessary. 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_ci pte = get_locked_pte(mm, addr, &ptl); 17562306a36Sopenharmony_ci if (!pte) 17662306a36Sopenharmony_ci goto fail_no_pte; 17762306a36Sopenharmony_ci pte_unmap_unlock(pte, ptl); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.mm, mm); 18062306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.addr, addr); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return 0; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cifail_no_pte: 18562306a36Sopenharmony_ci put_patching_mm(mm, addr); 18662306a36Sopenharmony_cifail_no_mm: 18762306a36Sopenharmony_ci return -ENOMEM; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic int text_area_cpu_down_mm(unsigned int cpu) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci put_patching_mm(this_cpu_read(cpu_patching_context.mm), 19362306a36Sopenharmony_ci this_cpu_read(cpu_patching_context.addr)); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.mm, NULL); 19662306a36Sopenharmony_ci this_cpu_write(cpu_patching_context.addr, 0); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return 0; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic __ro_after_init DEFINE_STATIC_KEY_FALSE(poking_init_done); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_civoid __init poking_init(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci int ret; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) 20862306a36Sopenharmony_ci return; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (mm_patch_enabled()) 21162306a36Sopenharmony_ci ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, 21262306a36Sopenharmony_ci "powerpc/text_poke_mm:online", 21362306a36Sopenharmony_ci text_area_cpu_up_mm, 21462306a36Sopenharmony_ci text_area_cpu_down_mm); 21562306a36Sopenharmony_ci else 21662306a36Sopenharmony_ci ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, 21762306a36Sopenharmony_ci "powerpc/text_poke:online", 21862306a36Sopenharmony_ci text_area_cpu_up, 21962306a36Sopenharmony_ci text_area_cpu_down); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* cpuhp_setup_state returns >= 0 on success */ 22262306a36Sopenharmony_ci if (WARN_ON(ret < 0)) 22362306a36Sopenharmony_ci return; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci static_branch_enable(&poking_init_done); 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic unsigned long get_patch_pfn(void *addr) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_MODULES) && is_vmalloc_or_module_addr(addr)) 23162306a36Sopenharmony_ci return vmalloc_to_pfn(addr); 23262306a36Sopenharmony_ci else 23362306a36Sopenharmony_ci return __pa_symbol(addr) >> PAGE_SHIFT; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/* 23762306a36Sopenharmony_ci * This can be called for kernel text or a module. 23862306a36Sopenharmony_ci */ 23962306a36Sopenharmony_cistatic int map_patch_area(void *addr, unsigned long text_poke_addr) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci unsigned long pfn = get_patch_pfn(addr); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return map_kernel_page(text_poke_addr, (pfn << PAGE_SHIFT), PAGE_KERNEL); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic void unmap_patch_area(unsigned long addr) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci pte_t *ptep; 24962306a36Sopenharmony_ci pmd_t *pmdp; 25062306a36Sopenharmony_ci pud_t *pudp; 25162306a36Sopenharmony_ci p4d_t *p4dp; 25262306a36Sopenharmony_ci pgd_t *pgdp; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci pgdp = pgd_offset_k(addr); 25562306a36Sopenharmony_ci if (WARN_ON(pgd_none(*pgdp))) 25662306a36Sopenharmony_ci return; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci p4dp = p4d_offset(pgdp, addr); 25962306a36Sopenharmony_ci if (WARN_ON(p4d_none(*p4dp))) 26062306a36Sopenharmony_ci return; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci pudp = pud_offset(p4dp, addr); 26362306a36Sopenharmony_ci if (WARN_ON(pud_none(*pudp))) 26462306a36Sopenharmony_ci return; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci pmdp = pmd_offset(pudp, addr); 26762306a36Sopenharmony_ci if (WARN_ON(pmd_none(*pmdp))) 26862306a36Sopenharmony_ci return; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ptep = pte_offset_kernel(pmdp, addr); 27162306a36Sopenharmony_ci if (WARN_ON(pte_none(*ptep))) 27262306a36Sopenharmony_ci return; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci /* 27562306a36Sopenharmony_ci * In hash, pte_clear flushes the tlb, in radix, we have to 27662306a36Sopenharmony_ci */ 27762306a36Sopenharmony_ci pte_clear(&init_mm, addr, ptep); 27862306a36Sopenharmony_ci flush_tlb_kernel_range(addr, addr + PAGE_SIZE); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_cistatic int __do_patch_instruction_mm(u32 *addr, ppc_inst_t instr) 28262306a36Sopenharmony_ci{ 28362306a36Sopenharmony_ci int err; 28462306a36Sopenharmony_ci u32 *patch_addr; 28562306a36Sopenharmony_ci unsigned long text_poke_addr; 28662306a36Sopenharmony_ci pte_t *pte; 28762306a36Sopenharmony_ci unsigned long pfn = get_patch_pfn(addr); 28862306a36Sopenharmony_ci struct mm_struct *patching_mm; 28962306a36Sopenharmony_ci struct mm_struct *orig_mm; 29062306a36Sopenharmony_ci spinlock_t *ptl; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci patching_mm = __this_cpu_read(cpu_patching_context.mm); 29362306a36Sopenharmony_ci text_poke_addr = __this_cpu_read(cpu_patching_context.addr); 29462306a36Sopenharmony_ci patch_addr = (u32 *)(text_poke_addr + offset_in_page(addr)); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci pte = get_locked_pte(patching_mm, text_poke_addr, &ptl); 29762306a36Sopenharmony_ci if (!pte) 29862306a36Sopenharmony_ci return -ENOMEM; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci __set_pte_at(patching_mm, text_poke_addr, pte, pfn_pte(pfn, PAGE_KERNEL), 0); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci /* order PTE update before use, also serves as the hwsync */ 30362306a36Sopenharmony_ci asm volatile("ptesync": : :"memory"); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* order context switch after arbitrary prior code */ 30662306a36Sopenharmony_ci isync(); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci orig_mm = start_using_temp_mm(patching_mm); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci err = __patch_instruction(addr, instr, patch_addr); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* hwsync performed by __patch_instruction (sync) if successful */ 31362306a36Sopenharmony_ci if (err) 31462306a36Sopenharmony_ci mb(); /* sync */ 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* context synchronisation performed by __patch_instruction (isync or exception) */ 31762306a36Sopenharmony_ci stop_using_temp_mm(patching_mm, orig_mm); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci pte_clear(patching_mm, text_poke_addr, pte); 32062306a36Sopenharmony_ci /* 32162306a36Sopenharmony_ci * ptesync to order PTE update before TLB invalidation done 32262306a36Sopenharmony_ci * by radix__local_flush_tlb_page_psize (in _tlbiel_va) 32362306a36Sopenharmony_ci */ 32462306a36Sopenharmony_ci local_flush_tlb_page_psize(patching_mm, text_poke_addr, mmu_virtual_psize); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci pte_unmap_unlock(pte, ptl); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci return err; 32962306a36Sopenharmony_ci} 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cistatic int __do_patch_instruction(u32 *addr, ppc_inst_t instr) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci int err; 33462306a36Sopenharmony_ci u32 *patch_addr; 33562306a36Sopenharmony_ci unsigned long text_poke_addr; 33662306a36Sopenharmony_ci pte_t *pte; 33762306a36Sopenharmony_ci unsigned long pfn = get_patch_pfn(addr); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci text_poke_addr = (unsigned long)__this_cpu_read(cpu_patching_context.addr) & PAGE_MASK; 34062306a36Sopenharmony_ci patch_addr = (u32 *)(text_poke_addr + offset_in_page(addr)); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci pte = __this_cpu_read(cpu_patching_context.pte); 34362306a36Sopenharmony_ci __set_pte_at(&init_mm, text_poke_addr, pte, pfn_pte(pfn, PAGE_KERNEL), 0); 34462306a36Sopenharmony_ci /* See ptesync comment in radix__set_pte_at() */ 34562306a36Sopenharmony_ci if (radix_enabled()) 34662306a36Sopenharmony_ci asm volatile("ptesync": : :"memory"); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci err = __patch_instruction(addr, instr, patch_addr); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci pte_clear(&init_mm, text_poke_addr, pte); 35162306a36Sopenharmony_ci flush_tlb_kernel_range(text_poke_addr, text_poke_addr + PAGE_SIZE); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci return err; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ciint patch_instruction(u32 *addr, ppc_inst_t instr) 35762306a36Sopenharmony_ci{ 35862306a36Sopenharmony_ci int err; 35962306a36Sopenharmony_ci unsigned long flags; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* 36262306a36Sopenharmony_ci * During early early boot patch_instruction is called 36362306a36Sopenharmony_ci * when text_poke_area is not ready, but we still need 36462306a36Sopenharmony_ci * to allow patching. We just do the plain old patching 36562306a36Sopenharmony_ci */ 36662306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_STRICT_KERNEL_RWX) || 36762306a36Sopenharmony_ci !static_branch_likely(&poking_init_done)) 36862306a36Sopenharmony_ci return raw_patch_instruction(addr, instr); 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci local_irq_save(flags); 37162306a36Sopenharmony_ci if (mm_patch_enabled()) 37262306a36Sopenharmony_ci err = __do_patch_instruction_mm(addr, instr); 37362306a36Sopenharmony_ci else 37462306a36Sopenharmony_ci err = __do_patch_instruction(addr, instr); 37562306a36Sopenharmony_ci local_irq_restore(flags); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return err; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ciNOKPROBE_SYMBOL(patch_instruction); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ciint patch_branch(u32 *addr, unsigned long target, int flags) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci ppc_inst_t instr; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (create_branch(&instr, addr, target, flags)) 38662306a36Sopenharmony_ci return -ERANGE; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci return patch_instruction(addr, instr); 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci/* 39262306a36Sopenharmony_ci * Helper to check if a given instruction is a conditional branch 39362306a36Sopenharmony_ci * Derived from the conditional checks in analyse_instr() 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_cibool is_conditional_branch(ppc_inst_t instr) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci unsigned int opcode = ppc_inst_primary_opcode(instr); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci if (opcode == 16) /* bc, bca, bcl, bcla */ 40062306a36Sopenharmony_ci return true; 40162306a36Sopenharmony_ci if (opcode == 19) { 40262306a36Sopenharmony_ci switch ((ppc_inst_val(instr) >> 1) & 0x3ff) { 40362306a36Sopenharmony_ci case 16: /* bclr, bclrl */ 40462306a36Sopenharmony_ci case 528: /* bcctr, bcctrl */ 40562306a36Sopenharmony_ci case 560: /* bctar, bctarl */ 40662306a36Sopenharmony_ci return true; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci return false; 41062306a36Sopenharmony_ci} 41162306a36Sopenharmony_ciNOKPROBE_SYMBOL(is_conditional_branch); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ciint create_cond_branch(ppc_inst_t *instr, const u32 *addr, 41462306a36Sopenharmony_ci unsigned long target, int flags) 41562306a36Sopenharmony_ci{ 41662306a36Sopenharmony_ci long offset; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci offset = target; 41962306a36Sopenharmony_ci if (! (flags & BRANCH_ABSOLUTE)) 42062306a36Sopenharmony_ci offset = offset - (unsigned long)addr; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* Check we can represent the target in the instruction format */ 42362306a36Sopenharmony_ci if (!is_offset_in_cond_branch_range(offset)) 42462306a36Sopenharmony_ci return 1; 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci /* Mask out the flags and target, so they don't step on each other. */ 42762306a36Sopenharmony_ci *instr = ppc_inst(0x40000000 | (flags & 0x3FF0003) | (offset & 0xFFFC)); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci return 0; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ciint instr_is_relative_branch(ppc_inst_t instr) 43362306a36Sopenharmony_ci{ 43462306a36Sopenharmony_ci if (ppc_inst_val(instr) & BRANCH_ABSOLUTE) 43562306a36Sopenharmony_ci return 0; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci return instr_is_branch_iform(instr) || instr_is_branch_bform(instr); 43862306a36Sopenharmony_ci} 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ciint instr_is_relative_link_branch(ppc_inst_t instr) 44162306a36Sopenharmony_ci{ 44262306a36Sopenharmony_ci return instr_is_relative_branch(instr) && (ppc_inst_val(instr) & BRANCH_SET_LINK); 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_cistatic unsigned long branch_iform_target(const u32 *instr) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci signed long imm; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci imm = ppc_inst_val(ppc_inst_read(instr)) & 0x3FFFFFC; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci /* If the top bit of the immediate value is set this is negative */ 45262306a36Sopenharmony_ci if (imm & 0x2000000) 45362306a36Sopenharmony_ci imm -= 0x4000000; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci if ((ppc_inst_val(ppc_inst_read(instr)) & BRANCH_ABSOLUTE) == 0) 45662306a36Sopenharmony_ci imm += (unsigned long)instr; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci return (unsigned long)imm; 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic unsigned long branch_bform_target(const u32 *instr) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci signed long imm; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci imm = ppc_inst_val(ppc_inst_read(instr)) & 0xFFFC; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci /* If the top bit of the immediate value is set this is negative */ 46862306a36Sopenharmony_ci if (imm & 0x8000) 46962306a36Sopenharmony_ci imm -= 0x10000; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci if ((ppc_inst_val(ppc_inst_read(instr)) & BRANCH_ABSOLUTE) == 0) 47262306a36Sopenharmony_ci imm += (unsigned long)instr; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci return (unsigned long)imm; 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ciunsigned long branch_target(const u32 *instr) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci if (instr_is_branch_iform(ppc_inst_read(instr))) 48062306a36Sopenharmony_ci return branch_iform_target(instr); 48162306a36Sopenharmony_ci else if (instr_is_branch_bform(ppc_inst_read(instr))) 48262306a36Sopenharmony_ci return branch_bform_target(instr); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci return 0; 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ciint translate_branch(ppc_inst_t *instr, const u32 *dest, const u32 *src) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci unsigned long target; 49062306a36Sopenharmony_ci target = branch_target(src); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (instr_is_branch_iform(ppc_inst_read(src))) 49362306a36Sopenharmony_ci return create_branch(instr, dest, target, 49462306a36Sopenharmony_ci ppc_inst_val(ppc_inst_read(src))); 49562306a36Sopenharmony_ci else if (instr_is_branch_bform(ppc_inst_read(src))) 49662306a36Sopenharmony_ci return create_cond_branch(instr, dest, target, 49762306a36Sopenharmony_ci ppc_inst_val(ppc_inst_read(src))); 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci return 1; 50062306a36Sopenharmony_ci} 501