Path: blob/master/arch/powerpc/lib/code-patching.c
10817 views
/*1* Copyright 2008 Michael Ellerman, IBM Corporation.2*3* This program is free software; you can redistribute it and/or4* modify it under the terms of the GNU General Public License5* as published by the Free Software Foundation; either version6* 2 of the License, or (at your option) any later version.7*/89#include <linux/kernel.h>10#include <linux/vmalloc.h>11#include <linux/init.h>12#include <linux/mm.h>13#include <asm/page.h>14#include <asm/code-patching.h>151617void patch_instruction(unsigned int *addr, unsigned int instr)18{19*addr = instr;20asm ("dcbst 0, %0; sync; icbi 0,%0; sync; isync" : : "r" (addr));21}2223void patch_branch(unsigned int *addr, unsigned long target, int flags)24{25patch_instruction(addr, create_branch(addr, target, flags));26}2728unsigned int create_branch(const unsigned int *addr,29unsigned long target, int flags)30{31unsigned int instruction;32long offset;3334offset = target;35if (! (flags & BRANCH_ABSOLUTE))36offset = offset - (unsigned long)addr;3738/* Check we can represent the target in the instruction format */39if (offset < -0x2000000 || offset > 0x1fffffc || offset & 0x3)40return 0;4142/* Mask out the flags and target, so they don't step on each other. */43instruction = 0x48000000 | (flags & 0x3) | (offset & 0x03FFFFFC);4445return instruction;46}4748unsigned int create_cond_branch(const unsigned int *addr,49unsigned long target, int flags)50{51unsigned int instruction;52long offset;5354offset = target;55if (! (flags & BRANCH_ABSOLUTE))56offset = offset - (unsigned long)addr;5758/* Check we can represent the target in the instruction format */59if (offset < -0x8000 || offset > 0x7FFF || offset & 0x3)60return 0;6162/* Mask out the flags and target, so they don't step on each other. */63instruction = 0x40000000 | (flags & 0x3FF0003) | (offset & 0xFFFC);6465return instruction;66}6768static unsigned int branch_opcode(unsigned int instr)69{70return (instr >> 26) & 0x3F;71}7273static int instr_is_branch_iform(unsigned int instr)74{75return branch_opcode(instr) == 18;76}7778static int instr_is_branch_bform(unsigned int instr)79{80return branch_opcode(instr) == 16;81}8283int instr_is_relative_branch(unsigned int instr)84{85if (instr & BRANCH_ABSOLUTE)86return 0;8788return instr_is_branch_iform(instr) || instr_is_branch_bform(instr);89}9091static unsigned long branch_iform_target(const unsigned int *instr)92{93signed long imm;9495imm = *instr & 0x3FFFFFC;9697/* If the top bit of the immediate value is set this is negative */98if (imm & 0x2000000)99imm -= 0x4000000;100101if ((*instr & BRANCH_ABSOLUTE) == 0)102imm += (unsigned long)instr;103104return (unsigned long)imm;105}106107static unsigned long branch_bform_target(const unsigned int *instr)108{109signed long imm;110111imm = *instr & 0xFFFC;112113/* If the top bit of the immediate value is set this is negative */114if (imm & 0x8000)115imm -= 0x10000;116117if ((*instr & BRANCH_ABSOLUTE) == 0)118imm += (unsigned long)instr;119120return (unsigned long)imm;121}122123unsigned long branch_target(const unsigned int *instr)124{125if (instr_is_branch_iform(*instr))126return branch_iform_target(instr);127else if (instr_is_branch_bform(*instr))128return branch_bform_target(instr);129130return 0;131}132133int instr_is_branch_to_addr(const unsigned int *instr, unsigned long addr)134{135if (instr_is_branch_iform(*instr) || instr_is_branch_bform(*instr))136return branch_target(instr) == addr;137138return 0;139}140141unsigned int translate_branch(const unsigned int *dest, const unsigned int *src)142{143unsigned long target;144145target = branch_target(src);146147if (instr_is_branch_iform(*src))148return create_branch(dest, target, *src);149else if (instr_is_branch_bform(*src))150return create_cond_branch(dest, target, *src);151152return 0;153}154155156#ifdef CONFIG_CODE_PATCHING_SELFTEST157158static void __init test_trampoline(void)159{160asm ("nop;\n");161}162163#define check(x) \164if (!(x)) printk("code-patching: test failed at line %d\n", __LINE__);165166static void __init test_branch_iform(void)167{168unsigned int instr;169unsigned long addr;170171addr = (unsigned long)&instr;172173/* The simplest case, branch to self, no flags */174check(instr_is_branch_iform(0x48000000));175/* All bits of target set, and flags */176check(instr_is_branch_iform(0x4bffffff));177/* High bit of opcode set, which is wrong */178check(!instr_is_branch_iform(0xcbffffff));179/* Middle bits of opcode set, which is wrong */180check(!instr_is_branch_iform(0x7bffffff));181182/* Simplest case, branch to self with link */183check(instr_is_branch_iform(0x48000001));184/* All bits of targets set */185check(instr_is_branch_iform(0x4bfffffd));186/* Some bits of targets set */187check(instr_is_branch_iform(0x4bff00fd));188/* Must be a valid branch to start with */189check(!instr_is_branch_iform(0x7bfffffd));190191/* Absolute branch to 0x100 */192instr = 0x48000103;193check(instr_is_branch_to_addr(&instr, 0x100));194/* Absolute branch to 0x420fc */195instr = 0x480420ff;196check(instr_is_branch_to_addr(&instr, 0x420fc));197/* Maximum positive relative branch, + 20MB - 4B */198instr = 0x49fffffc;199check(instr_is_branch_to_addr(&instr, addr + 0x1FFFFFC));200/* Smallest negative relative branch, - 4B */201instr = 0x4bfffffc;202check(instr_is_branch_to_addr(&instr, addr - 4));203/* Largest negative relative branch, - 32 MB */204instr = 0x4a000000;205check(instr_is_branch_to_addr(&instr, addr - 0x2000000));206207/* Branch to self, with link */208instr = create_branch(&instr, addr, BRANCH_SET_LINK);209check(instr_is_branch_to_addr(&instr, addr));210211/* Branch to self - 0x100, with link */212instr = create_branch(&instr, addr - 0x100, BRANCH_SET_LINK);213check(instr_is_branch_to_addr(&instr, addr - 0x100));214215/* Branch to self + 0x100, no link */216instr = create_branch(&instr, addr + 0x100, 0);217check(instr_is_branch_to_addr(&instr, addr + 0x100));218219/* Maximum relative negative offset, - 32 MB */220instr = create_branch(&instr, addr - 0x2000000, BRANCH_SET_LINK);221check(instr_is_branch_to_addr(&instr, addr - 0x2000000));222223/* Out of range relative negative offset, - 32 MB + 4*/224instr = create_branch(&instr, addr - 0x2000004, BRANCH_SET_LINK);225check(instr == 0);226227/* Out of range relative positive offset, + 32 MB */228instr = create_branch(&instr, addr + 0x2000000, BRANCH_SET_LINK);229check(instr == 0);230231/* Unaligned target */232instr = create_branch(&instr, addr + 3, BRANCH_SET_LINK);233check(instr == 0);234235/* Check flags are masked correctly */236instr = create_branch(&instr, addr, 0xFFFFFFFC);237check(instr_is_branch_to_addr(&instr, addr));238check(instr == 0x48000000);239}240241static void __init test_create_function_call(void)242{243unsigned int *iptr;244unsigned long dest;245246/* Check we can create a function call */247iptr = (unsigned int *)ppc_function_entry(test_trampoline);248dest = ppc_function_entry(test_create_function_call);249patch_instruction(iptr, create_branch(iptr, dest, BRANCH_SET_LINK));250check(instr_is_branch_to_addr(iptr, dest));251}252253static void __init test_branch_bform(void)254{255unsigned long addr;256unsigned int *iptr, instr, flags;257258iptr = &instr;259addr = (unsigned long)iptr;260261/* The simplest case, branch to self, no flags */262check(instr_is_branch_bform(0x40000000));263/* All bits of target set, and flags */264check(instr_is_branch_bform(0x43ffffff));265/* High bit of opcode set, which is wrong */266check(!instr_is_branch_bform(0xc3ffffff));267/* Middle bits of opcode set, which is wrong */268check(!instr_is_branch_bform(0x7bffffff));269270/* Absolute conditional branch to 0x100 */271instr = 0x43ff0103;272check(instr_is_branch_to_addr(&instr, 0x100));273/* Absolute conditional branch to 0x20fc */274instr = 0x43ff20ff;275check(instr_is_branch_to_addr(&instr, 0x20fc));276/* Maximum positive relative conditional branch, + 32 KB - 4B */277instr = 0x43ff7ffc;278check(instr_is_branch_to_addr(&instr, addr + 0x7FFC));279/* Smallest negative relative conditional branch, - 4B */280instr = 0x43fffffc;281check(instr_is_branch_to_addr(&instr, addr - 4));282/* Largest negative relative conditional branch, - 32 KB */283instr = 0x43ff8000;284check(instr_is_branch_to_addr(&instr, addr - 0x8000));285286/* All condition code bits set & link */287flags = 0x3ff000 | BRANCH_SET_LINK;288289/* Branch to self */290instr = create_cond_branch(iptr, addr, flags);291check(instr_is_branch_to_addr(&instr, addr));292293/* Branch to self - 0x100 */294instr = create_cond_branch(iptr, addr - 0x100, flags);295check(instr_is_branch_to_addr(&instr, addr - 0x100));296297/* Branch to self + 0x100 */298instr = create_cond_branch(iptr, addr + 0x100, flags);299check(instr_is_branch_to_addr(&instr, addr + 0x100));300301/* Maximum relative negative offset, - 32 KB */302instr = create_cond_branch(iptr, addr - 0x8000, flags);303check(instr_is_branch_to_addr(&instr, addr - 0x8000));304305/* Out of range relative negative offset, - 32 KB + 4*/306instr = create_cond_branch(iptr, addr - 0x8004, flags);307check(instr == 0);308309/* Out of range relative positive offset, + 32 KB */310instr = create_cond_branch(iptr, addr + 0x8000, flags);311check(instr == 0);312313/* Unaligned target */314instr = create_cond_branch(iptr, addr + 3, flags);315check(instr == 0);316317/* Check flags are masked correctly */318instr = create_cond_branch(iptr, addr, 0xFFFFFFFC);319check(instr_is_branch_to_addr(&instr, addr));320check(instr == 0x43FF0000);321}322323static void __init test_translate_branch(void)324{325unsigned long addr;326unsigned int *p, *q;327void *buf;328329buf = vmalloc(PAGE_ALIGN(0x2000000 + 1));330check(buf);331if (!buf)332return;333334/* Simple case, branch to self moved a little */335p = buf;336addr = (unsigned long)p;337patch_branch(p, addr, 0);338check(instr_is_branch_to_addr(p, addr));339q = p + 1;340patch_instruction(q, translate_branch(q, p));341check(instr_is_branch_to_addr(q, addr));342343/* Maximum negative case, move b . to addr + 32 MB */344p = buf;345addr = (unsigned long)p;346patch_branch(p, addr, 0);347q = buf + 0x2000000;348patch_instruction(q, translate_branch(q, p));349check(instr_is_branch_to_addr(p, addr));350check(instr_is_branch_to_addr(q, addr));351check(*q == 0x4a000000);352353/* Maximum positive case, move x to x - 32 MB + 4 */354p = buf + 0x2000000;355addr = (unsigned long)p;356patch_branch(p, addr, 0);357q = buf + 4;358patch_instruction(q, translate_branch(q, p));359check(instr_is_branch_to_addr(p, addr));360check(instr_is_branch_to_addr(q, addr));361check(*q == 0x49fffffc);362363/* Jump to x + 16 MB moved to x + 20 MB */364p = buf;365addr = 0x1000000 + (unsigned long)buf;366patch_branch(p, addr, BRANCH_SET_LINK);367q = buf + 0x1400000;368patch_instruction(q, translate_branch(q, p));369check(instr_is_branch_to_addr(p, addr));370check(instr_is_branch_to_addr(q, addr));371372/* Jump to x + 16 MB moved to x - 16 MB + 4 */373p = buf + 0x1000000;374addr = 0x2000000 + (unsigned long)buf;375patch_branch(p, addr, 0);376q = buf + 4;377patch_instruction(q, translate_branch(q, p));378check(instr_is_branch_to_addr(p, addr));379check(instr_is_branch_to_addr(q, addr));380381382/* Conditional branch tests */383384/* Simple case, branch to self moved a little */385p = buf;386addr = (unsigned long)p;387patch_instruction(p, create_cond_branch(p, addr, 0));388check(instr_is_branch_to_addr(p, addr));389q = p + 1;390patch_instruction(q, translate_branch(q, p));391check(instr_is_branch_to_addr(q, addr));392393/* Maximum negative case, move b . to addr + 32 KB */394p = buf;395addr = (unsigned long)p;396patch_instruction(p, create_cond_branch(p, addr, 0xFFFFFFFC));397q = buf + 0x8000;398patch_instruction(q, translate_branch(q, p));399check(instr_is_branch_to_addr(p, addr));400check(instr_is_branch_to_addr(q, addr));401check(*q == 0x43ff8000);402403/* Maximum positive case, move x to x - 32 KB + 4 */404p = buf + 0x8000;405addr = (unsigned long)p;406patch_instruction(p, create_cond_branch(p, addr, 0xFFFFFFFC));407q = buf + 4;408patch_instruction(q, translate_branch(q, p));409check(instr_is_branch_to_addr(p, addr));410check(instr_is_branch_to_addr(q, addr));411check(*q == 0x43ff7ffc);412413/* Jump to x + 12 KB moved to x + 20 KB */414p = buf;415addr = 0x3000 + (unsigned long)buf;416patch_instruction(p, create_cond_branch(p, addr, BRANCH_SET_LINK));417q = buf + 0x5000;418patch_instruction(q, translate_branch(q, p));419check(instr_is_branch_to_addr(p, addr));420check(instr_is_branch_to_addr(q, addr));421422/* Jump to x + 8 KB moved to x - 8 KB + 4 */423p = buf + 0x2000;424addr = 0x4000 + (unsigned long)buf;425patch_instruction(p, create_cond_branch(p, addr, 0));426q = buf + 4;427patch_instruction(q, translate_branch(q, p));428check(instr_is_branch_to_addr(p, addr));429check(instr_is_branch_to_addr(q, addr));430431/* Free the buffer we were using */432vfree(buf);433}434435static int __init test_code_patching(void)436{437printk(KERN_DEBUG "Running code patching self-tests ...\n");438439test_branch_iform();440test_branch_bform();441test_create_function_call();442test_translate_branch();443444return 0;445}446late_initcall(test_code_patching);447448#endif /* CONFIG_CODE_PATCHING_SELFTEST */449450451