Path: blob/master/arch/powerpc/lib/test-code-patching.c
26489 views
// SPDX-License-Identifier: GPL-2.0-or-later1/*2* Copyright 2008 Michael Ellerman, IBM Corporation.3*/45#include <linux/vmalloc.h>6#include <linux/init.h>78#include <asm/text-patching.h>910static int __init instr_is_branch_to_addr(const u32 *instr, unsigned long addr)11{12if (instr_is_branch_iform(ppc_inst_read(instr)) ||13instr_is_branch_bform(ppc_inst_read(instr)))14return branch_target(instr) == addr;1516return 0;17}1819static void __init test_trampoline(void)20{21asm ("nop;nop;\n");22}2324#define check(x) do { \25if (!(x)) \26pr_err("code-patching: test failed at line %d\n", __LINE__); \27} while (0)2829static void __init test_branch_iform(void)30{31int err;32ppc_inst_t instr;33u32 tmp[2];34u32 *iptr = tmp;35unsigned long addr = (unsigned long)tmp;3637/* The simplest case, branch to self, no flags */38check(instr_is_branch_iform(ppc_inst(0x48000000)));39/* All bits of target set, and flags */40check(instr_is_branch_iform(ppc_inst(0x4bffffff)));41/* High bit of opcode set, which is wrong */42check(!instr_is_branch_iform(ppc_inst(0xcbffffff)));43/* Middle bits of opcode set, which is wrong */44check(!instr_is_branch_iform(ppc_inst(0x7bffffff)));4546/* Simplest case, branch to self with link */47check(instr_is_branch_iform(ppc_inst(0x48000001)));48/* All bits of targets set */49check(instr_is_branch_iform(ppc_inst(0x4bfffffd)));50/* Some bits of targets set */51check(instr_is_branch_iform(ppc_inst(0x4bff00fd)));52/* Must be a valid branch to start with */53check(!instr_is_branch_iform(ppc_inst(0x7bfffffd)));5455/* Absolute branch to 0x100 */56ppc_inst_write(iptr, ppc_inst(0x48000103));57check(instr_is_branch_to_addr(iptr, 0x100));58/* Absolute branch to 0x420fc */59ppc_inst_write(iptr, ppc_inst(0x480420ff));60check(instr_is_branch_to_addr(iptr, 0x420fc));61/* Maximum positive relative branch, + 20MB - 4B */62ppc_inst_write(iptr, ppc_inst(0x49fffffc));63check(instr_is_branch_to_addr(iptr, addr + 0x1FFFFFC));64/* Smallest negative relative branch, - 4B */65ppc_inst_write(iptr, ppc_inst(0x4bfffffc));66check(instr_is_branch_to_addr(iptr, addr - 4));67/* Largest negative relative branch, - 32 MB */68ppc_inst_write(iptr, ppc_inst(0x4a000000));69check(instr_is_branch_to_addr(iptr, addr - 0x2000000));7071/* Branch to self, with link */72err = create_branch(&instr, iptr, addr, BRANCH_SET_LINK);73ppc_inst_write(iptr, instr);74check(instr_is_branch_to_addr(iptr, addr));7576/* Branch to self - 0x100, with link */77err = create_branch(&instr, iptr, addr - 0x100, BRANCH_SET_LINK);78ppc_inst_write(iptr, instr);79check(instr_is_branch_to_addr(iptr, addr - 0x100));8081/* Branch to self + 0x100, no link */82err = create_branch(&instr, iptr, addr + 0x100, 0);83ppc_inst_write(iptr, instr);84check(instr_is_branch_to_addr(iptr, addr + 0x100));8586/* Maximum relative negative offset, - 32 MB */87err = create_branch(&instr, iptr, addr - 0x2000000, BRANCH_SET_LINK);88ppc_inst_write(iptr, instr);89check(instr_is_branch_to_addr(iptr, addr - 0x2000000));9091/* Out of range relative negative offset, - 32 MB + 4*/92err = create_branch(&instr, iptr, addr - 0x2000004, BRANCH_SET_LINK);93check(err);9495/* Out of range relative positive offset, + 32 MB */96err = create_branch(&instr, iptr, addr + 0x2000000, BRANCH_SET_LINK);97check(err);9899/* Unaligned target */100err = create_branch(&instr, iptr, addr + 3, BRANCH_SET_LINK);101check(err);102103/* Check flags are masked correctly */104err = create_branch(&instr, iptr, addr, 0xFFFFFFFC);105ppc_inst_write(iptr, instr);106check(instr_is_branch_to_addr(iptr, addr));107check(ppc_inst_equal(instr, ppc_inst(0x48000000)));108}109110static void __init test_create_function_call(void)111{112u32 *iptr;113unsigned long dest;114ppc_inst_t instr;115116/* Check we can create a function call */117iptr = (u32 *)ppc_function_entry(test_trampoline);118dest = ppc_function_entry(test_create_function_call);119create_branch(&instr, iptr, dest, BRANCH_SET_LINK);120patch_instruction(iptr, instr);121check(instr_is_branch_to_addr(iptr, dest));122}123124static void __init test_branch_bform(void)125{126int err;127unsigned long addr;128ppc_inst_t instr;129u32 tmp[2];130u32 *iptr = tmp;131unsigned int flags;132133addr = (unsigned long)iptr;134135/* The simplest case, branch to self, no flags */136check(instr_is_branch_bform(ppc_inst(0x40000000)));137/* All bits of target set, and flags */138check(instr_is_branch_bform(ppc_inst(0x43ffffff)));139/* High bit of opcode set, which is wrong */140check(!instr_is_branch_bform(ppc_inst(0xc3ffffff)));141/* Middle bits of opcode set, which is wrong */142check(!instr_is_branch_bform(ppc_inst(0x7bffffff)));143144/* Absolute conditional branch to 0x100 */145ppc_inst_write(iptr, ppc_inst(0x43ff0103));146check(instr_is_branch_to_addr(iptr, 0x100));147/* Absolute conditional branch to 0x20fc */148ppc_inst_write(iptr, ppc_inst(0x43ff20ff));149check(instr_is_branch_to_addr(iptr, 0x20fc));150/* Maximum positive relative conditional branch, + 32 KB - 4B */151ppc_inst_write(iptr, ppc_inst(0x43ff7ffc));152check(instr_is_branch_to_addr(iptr, addr + 0x7FFC));153/* Smallest negative relative conditional branch, - 4B */154ppc_inst_write(iptr, ppc_inst(0x43fffffc));155check(instr_is_branch_to_addr(iptr, addr - 4));156/* Largest negative relative conditional branch, - 32 KB */157ppc_inst_write(iptr, ppc_inst(0x43ff8000));158check(instr_is_branch_to_addr(iptr, addr - 0x8000));159160/* All condition code bits set & link */161flags = 0x3ff000 | BRANCH_SET_LINK;162163/* Branch to self */164err = create_cond_branch(&instr, iptr, addr, flags);165ppc_inst_write(iptr, instr);166check(instr_is_branch_to_addr(iptr, addr));167168/* Branch to self - 0x100 */169err = create_cond_branch(&instr, iptr, addr - 0x100, flags);170ppc_inst_write(iptr, instr);171check(instr_is_branch_to_addr(iptr, addr - 0x100));172173/* Branch to self + 0x100 */174err = create_cond_branch(&instr, iptr, addr + 0x100, flags);175ppc_inst_write(iptr, instr);176check(instr_is_branch_to_addr(iptr, addr + 0x100));177178/* Maximum relative negative offset, - 32 KB */179err = create_cond_branch(&instr, iptr, addr - 0x8000, flags);180ppc_inst_write(iptr, instr);181check(instr_is_branch_to_addr(iptr, addr - 0x8000));182183/* Out of range relative negative offset, - 32 KB + 4*/184err = create_cond_branch(&instr, iptr, addr - 0x8004, flags);185check(err);186187/* Out of range relative positive offset, + 32 KB */188err = create_cond_branch(&instr, iptr, addr + 0x8000, flags);189check(err);190191/* Unaligned target */192err = create_cond_branch(&instr, iptr, addr + 3, flags);193check(err);194195/* Check flags are masked correctly */196err = create_cond_branch(&instr, iptr, addr, 0xFFFFFFFC);197ppc_inst_write(iptr, instr);198check(instr_is_branch_to_addr(iptr, addr));199check(ppc_inst_equal(instr, ppc_inst(0x43FF0000)));200}201202static void __init test_translate_branch(void)203{204unsigned long addr;205void *p, *q;206ppc_inst_t instr;207void *buf;208209buf = vmalloc(PAGE_ALIGN(0x2000000 + 1));210check(buf);211if (!buf)212return;213214/* Simple case, branch to self moved a little */215p = buf;216addr = (unsigned long)p;217create_branch(&instr, p, addr, 0);218ppc_inst_write(p, instr);219check(instr_is_branch_to_addr(p, addr));220q = p + 4;221translate_branch(&instr, q, p);222ppc_inst_write(q, instr);223check(instr_is_branch_to_addr(q, addr));224225/* Maximum negative case, move b . to addr + 32 MB */226p = buf;227addr = (unsigned long)p;228create_branch(&instr, p, addr, 0);229ppc_inst_write(p, instr);230q = buf + 0x2000000;231translate_branch(&instr, q, p);232ppc_inst_write(q, instr);233check(instr_is_branch_to_addr(p, addr));234check(instr_is_branch_to_addr(q, addr));235check(ppc_inst_equal(ppc_inst_read(q), ppc_inst(0x4a000000)));236237/* Maximum positive case, move x to x - 32 MB + 4 */238p = buf + 0x2000000;239addr = (unsigned long)p;240create_branch(&instr, p, addr, 0);241ppc_inst_write(p, instr);242q = buf + 4;243translate_branch(&instr, q, p);244ppc_inst_write(q, instr);245check(instr_is_branch_to_addr(p, addr));246check(instr_is_branch_to_addr(q, addr));247check(ppc_inst_equal(ppc_inst_read(q), ppc_inst(0x49fffffc)));248249/* Jump to x + 16 MB moved to x + 20 MB */250p = buf;251addr = 0x1000000 + (unsigned long)buf;252create_branch(&instr, p, addr, BRANCH_SET_LINK);253ppc_inst_write(p, instr);254q = buf + 0x1400000;255translate_branch(&instr, q, p);256ppc_inst_write(q, instr);257check(instr_is_branch_to_addr(p, addr));258check(instr_is_branch_to_addr(q, addr));259260/* Jump to x + 16 MB moved to x - 16 MB + 4 */261p = buf + 0x1000000;262addr = 0x2000000 + (unsigned long)buf;263create_branch(&instr, p, addr, 0);264ppc_inst_write(p, instr);265q = buf + 4;266translate_branch(&instr, q, p);267ppc_inst_write(q, instr);268check(instr_is_branch_to_addr(p, addr));269check(instr_is_branch_to_addr(q, addr));270271272/* Conditional branch tests */273274/* Simple case, branch to self moved a little */275p = buf;276addr = (unsigned long)p;277create_cond_branch(&instr, p, addr, 0);278ppc_inst_write(p, instr);279check(instr_is_branch_to_addr(p, addr));280q = buf + 4;281translate_branch(&instr, q, p);282ppc_inst_write(q, instr);283check(instr_is_branch_to_addr(q, addr));284285/* Maximum negative case, move b . to addr + 32 KB */286p = buf;287addr = (unsigned long)p;288create_cond_branch(&instr, p, addr, 0xFFFFFFFC);289ppc_inst_write(p, instr);290q = buf + 0x8000;291translate_branch(&instr, q, p);292ppc_inst_write(q, instr);293check(instr_is_branch_to_addr(p, addr));294check(instr_is_branch_to_addr(q, addr));295check(ppc_inst_equal(ppc_inst_read(q), ppc_inst(0x43ff8000)));296297/* Maximum positive case, move x to x - 32 KB + 4 */298p = buf + 0x8000;299addr = (unsigned long)p;300create_cond_branch(&instr, p, addr, 0xFFFFFFFC);301ppc_inst_write(p, instr);302q = buf + 4;303translate_branch(&instr, q, p);304ppc_inst_write(q, instr);305check(instr_is_branch_to_addr(p, addr));306check(instr_is_branch_to_addr(q, addr));307check(ppc_inst_equal(ppc_inst_read(q), ppc_inst(0x43ff7ffc)));308309/* Jump to x + 12 KB moved to x + 20 KB */310p = buf;311addr = 0x3000 + (unsigned long)buf;312create_cond_branch(&instr, p, addr, BRANCH_SET_LINK);313ppc_inst_write(p, instr);314q = buf + 0x5000;315translate_branch(&instr, q, p);316ppc_inst_write(q, instr);317check(instr_is_branch_to_addr(p, addr));318check(instr_is_branch_to_addr(q, addr));319320/* Jump to x + 8 KB moved to x - 8 KB + 4 */321p = buf + 0x2000;322addr = 0x4000 + (unsigned long)buf;323create_cond_branch(&instr, p, addr, 0);324ppc_inst_write(p, instr);325q = buf + 4;326translate_branch(&instr, q, p);327ppc_inst_write(q, instr);328check(instr_is_branch_to_addr(p, addr));329check(instr_is_branch_to_addr(q, addr));330331/* Free the buffer we were using */332vfree(buf);333}334335static void __init test_prefixed_patching(void)336{337u32 *iptr = (u32 *)ppc_function_entry(test_trampoline);338u32 expected[2] = {OP_PREFIX << 26, 0};339ppc_inst_t inst = ppc_inst_prefix(OP_PREFIX << 26, 0);340341if (!IS_ENABLED(CONFIG_PPC64))342return;343344patch_instruction(iptr, inst);345346check(!memcmp(iptr, expected, sizeof(expected)));347}348349static void __init test_multi_instruction_patching(void)350{351u32 code[32];352void *buf;353u32 *addr32;354u64 *addr64;355ppc_inst_t inst64 = ppc_inst_prefix(OP_PREFIX << 26 | 3UL << 24, PPC_RAW_TRAP());356u32 inst32 = PPC_RAW_NOP();357358buf = vzalloc(PAGE_SIZE * 8);359check(buf);360if (!buf)361return;362363/* Test single page 32-bit repeated instruction */364addr32 = buf + PAGE_SIZE;365check(!patch_instructions(addr32 + 1, &inst32, 12, true));366367check(addr32[0] == 0);368check(addr32[1] == inst32);369check(addr32[2] == inst32);370check(addr32[3] == inst32);371check(addr32[4] == 0);372373/* Test single page 64-bit repeated instruction */374if (IS_ENABLED(CONFIG_PPC64)) {375check(ppc_inst_prefixed(inst64));376377addr64 = buf + PAGE_SIZE * 2;378ppc_inst_write(code, inst64);379check(!patch_instructions((u32 *)(addr64 + 1), code, 24, true));380381check(addr64[0] == 0);382check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[1]), inst64));383check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[2]), inst64));384check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[3]), inst64));385check(addr64[4] == 0);386}387388/* Test single page memcpy */389addr32 = buf + PAGE_SIZE * 3;390391for (int i = 0; i < ARRAY_SIZE(code); i++)392code[i] = i + 1;393394check(!patch_instructions(addr32 + 1, code, sizeof(code), false));395396check(addr32[0] == 0);397check(!memcmp(&addr32[1], code, sizeof(code)));398check(addr32[ARRAY_SIZE(code) + 1] == 0);399400/* Test multipage 32-bit repeated instruction */401addr32 = buf + PAGE_SIZE * 4 - 8;402check(!patch_instructions(addr32 + 1, &inst32, 12, true));403404check(addr32[0] == 0);405check(addr32[1] == inst32);406check(addr32[2] == inst32);407check(addr32[3] == inst32);408check(addr32[4] == 0);409410/* Test multipage 64-bit repeated instruction */411if (IS_ENABLED(CONFIG_PPC64)) {412check(ppc_inst_prefixed(inst64));413414addr64 = buf + PAGE_SIZE * 5 - 8;415ppc_inst_write(code, inst64);416check(!patch_instructions((u32 *)(addr64 + 1), code, 24, true));417418check(addr64[0] == 0);419check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[1]), inst64));420check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[2]), inst64));421check(ppc_inst_equal(ppc_inst_read((u32 *)&addr64[3]), inst64));422check(addr64[4] == 0);423}424425/* Test multipage memcpy */426addr32 = buf + PAGE_SIZE * 6 - 12;427428for (int i = 0; i < ARRAY_SIZE(code); i++)429code[i] = i + 1;430431check(!patch_instructions(addr32 + 1, code, sizeof(code), false));432433check(addr32[0] == 0);434check(!memcmp(&addr32[1], code, sizeof(code)));435check(addr32[ARRAY_SIZE(code) + 1] == 0);436437vfree(buf);438}439440static void __init test_data_patching(void)441{442void *buf;443u32 *addr32;444445buf = vzalloc(PAGE_SIZE);446check(buf);447if (!buf)448return;449450addr32 = buf + 128;451452addr32[1] = 0xA0A1A2A3;453addr32[2] = 0xB0B1B2B3;454455check(!patch_uint(&addr32[1], 0xC0C1C2C3));456457check(addr32[0] == 0);458check(addr32[1] == 0xC0C1C2C3);459check(addr32[2] == 0xB0B1B2B3);460check(addr32[3] == 0);461462/* Unaligned patch_ulong() should fail */463if (IS_ENABLED(CONFIG_PPC64))464check(patch_ulong(&addr32[1], 0xD0D1D2D3) == -EINVAL);465466check(!patch_ulong(&addr32[2], 0xD0D1D2D3));467468check(addr32[0] == 0);469check(addr32[1] == 0xC0C1C2C3);470check(*(unsigned long *)(&addr32[2]) == 0xD0D1D2D3);471472if (!IS_ENABLED(CONFIG_PPC64))473check(addr32[3] == 0);474475check(addr32[4] == 0);476477vfree(buf);478}479480static int __init test_code_patching(void)481{482pr_info("Running code patching self-tests ...\n");483484test_branch_iform();485test_branch_bform();486test_create_function_call();487test_translate_branch();488test_prefixed_patching();489test_multi_instruction_patching();490test_data_patching();491492return 0;493}494late_initcall(test_code_patching);495496497