/****************************************************************************** * linux/arch/ia64/xen/paravirt_patch.c * * Copyright (c) 2008 Isaku Yamahata <yamahata at valinux co jp> * VA Linux Systems Japan K.K. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include <linux/init.h> #include <asm/intrinsics.h> #include <asm/kprobes.h> #include <asm/paravirt.h> #include <asm/paravirt_patch.h> typedef union ia64_inst { struct { unsigned long long qp : 6; unsigned long long : 31; unsigned long long opcode : 4; unsigned long long reserved : 23; } generic; unsigned long long l; } ia64_inst_t; /* * flush_icache_range() can't be used here. * we are here before cpu_init() which initializes * ia64_i_cache_stride_shift. flush_icache_range() uses it. */ void __init_or_module paravirt_flush_i_cache_range(const void *instr, unsigned long size) { extern void paravirt_fc_i(const void *addr); unsigned long i; for (i = 0; i < size; i += sizeof(bundle_t)) paravirt_fc_i(instr + i); } bundle_t* __init_or_module paravirt_get_bundle(unsigned long tag) { return (bundle_t *)(tag & ~3UL); } unsigned long __init_or_module paravirt_get_slot(unsigned long tag) { return tag & 3UL; } unsigned long __init_or_module paravirt_get_num_inst(unsigned long stag, unsigned long etag) { bundle_t *sbundle = paravirt_get_bundle(stag); unsigned long sslot = paravirt_get_slot(stag); bundle_t *ebundle = paravirt_get_bundle(etag); unsigned long eslot = paravirt_get_slot(etag); return (ebundle - sbundle) * 3 + eslot - sslot + 1; } unsigned long __init_or_module paravirt_get_next_tag(unsigned long tag) { unsigned long slot = paravirt_get_slot(tag); switch (slot) { case 0: case 1: return tag + 1; case 2: { bundle_t *bundle = paravirt_get_bundle(tag); return (unsigned long)(bundle + 1); } default: BUG(); } /* NOTREACHED */ } ia64_inst_t __init_or_module paravirt_read_slot0(const bundle_t *bundle) { ia64_inst_t inst; inst.l = bundle->quad0.slot0; return inst; } ia64_inst_t __init_or_module paravirt_read_slot1(const bundle_t *bundle) { ia64_inst_t inst; inst.l = bundle->quad0.slot1_p0 | ((unsigned long long)bundle->quad1.slot1_p1 << 18UL); return inst; } ia64_inst_t __init_or_module paravirt_read_slot2(const bundle_t *bundle) { ia64_inst_t inst; inst.l = bundle->quad1.slot2; return inst; } ia64_inst_t __init_or_module paravirt_read_inst(unsigned long tag) { bundle_t *bundle = paravirt_get_bundle(tag); unsigned long slot = paravirt_get_slot(tag); switch (slot) { case 0: return paravirt_read_slot0(bundle); case 1: return paravirt_read_slot1(bundle); case 2: return paravirt_read_slot2(bundle); default: BUG(); } /* NOTREACHED */ } void __init_or_module paravirt_write_slot0(bundle_t *bundle, ia64_inst_t inst) { bundle->quad0.slot0 = inst.l; } void __init_or_module paravirt_write_slot1(bundle_t *bundle, ia64_inst_t inst) { bundle->quad0.slot1_p0 = inst.l; bundle->quad1.slot1_p1 = inst.l >> 18UL; } void __init_or_module paravirt_write_slot2(bundle_t *bundle, ia64_inst_t inst) { bundle->quad1.slot2 = inst.l; } void __init_or_module paravirt_write_inst(unsigned long tag, ia64_inst_t inst) { bundle_t *bundle = paravirt_get_bundle(tag); unsigned long slot = paravirt_get_slot(tag); switch (slot) { case 0: paravirt_write_slot0(bundle, inst); break; case 1: paravirt_write_slot1(bundle, inst); break; case 2: paravirt_write_slot2(bundle, inst); break; default: BUG(); break; } paravirt_flush_i_cache_range(bundle, sizeof(*bundle)); } /* for debug */ void paravirt_print_bundle(const bundle_t *bundle) { const unsigned long *quad = (const unsigned long *)bundle; ia64_inst_t slot0 = paravirt_read_slot0(bundle); ia64_inst_t slot1 = paravirt_read_slot1(bundle); ia64_inst_t slot2 = paravirt_read_slot2(bundle); printk(KERN_DEBUG "bundle 0x%p 0x%016lx 0x%016lx\n", bundle, quad[0], quad[1]); printk(KERN_DEBUG "bundle template 0x%x\n", bundle->quad0.template); printk(KERN_DEBUG "slot0 0x%lx slot1_p0 0x%lx slot1_p1 0x%lx slot2 0x%lx\n", (unsigned long)bundle->quad0.slot0, (unsigned long)bundle->quad0.slot1_p0, (unsigned long)bundle->quad1.slot1_p1, (unsigned long)bundle->quad1.slot2); printk(KERN_DEBUG "slot0 0x%016llx slot1 0x%016llx slot2 0x%016llx\n", slot0.l, slot1.l, slot2.l); } static int noreplace_paravirt __init_or_module = 0; static int __init setup_noreplace_paravirt(char *str) { noreplace_paravirt = 1; return 1; } __setup("noreplace-paravirt", setup_noreplace_paravirt); #ifdef ASM_SUPPORTED static void __init_or_module fill_nop_bundle(void *sbundle, void *ebundle) { extern const char paravirt_nop_bundle[]; extern const unsigned long paravirt_nop_bundle_size; void *bundle = sbundle; BUG_ON((((unsigned long)sbundle) % sizeof(bundle_t)) != 0); BUG_ON((((unsigned long)ebundle) % sizeof(bundle_t)) != 0); while (bundle < ebundle) { memcpy(bundle, paravirt_nop_bundle, paravirt_nop_bundle_size); bundle += paravirt_nop_bundle_size; } } /* helper function */ unsigned long __init_or_module __paravirt_patch_apply_bundle(void *sbundle, void *ebundle, unsigned long type, const struct paravirt_patch_bundle_elem *elems, unsigned long nelems, const struct paravirt_patch_bundle_elem **found) { unsigned long used = 0; unsigned long i; BUG_ON((((unsigned long)sbundle) % sizeof(bundle_t)) != 0); BUG_ON((((unsigned long)ebundle) % sizeof(bundle_t)) != 0); found = NULL; for (i = 0; i < nelems; i++) { const struct paravirt_patch_bundle_elem *p = &elems[i]; if (p->type == type) { unsigned long need = p->ebundle - p->sbundle; unsigned long room = ebundle - sbundle; if (found != NULL) *found = p; if (room < need) { /* no room to replace. skip it */ printk(KERN_DEBUG "the space is too small to put " "bundles. type %ld need %ld room %ld\n", type, need, room); break; } used = need; memcpy(sbundle, p->sbundle, used); break; } } return used; } void __init_or_module paravirt_patch_apply_bundle(const struct paravirt_patch_site_bundle *start, const struct paravirt_patch_site_bundle *end) { const struct paravirt_patch_site_bundle *p; if (noreplace_paravirt) return; if (pv_init_ops.patch_bundle == NULL) return; for (p = start; p < end; p++) { unsigned long used; used = (*pv_init_ops.patch_bundle)(p->sbundle, p->ebundle, p->type); if (used == 0) continue; fill_nop_bundle(p->sbundle + used, p->ebundle); paravirt_flush_i_cache_range(p->sbundle, p->ebundle - p->sbundle); } ia64_sync_i(); ia64_srlz_i(); } /* * nop.i, nop.m, nop.f instruction are same format. * but nop.b has differennt format. * This doesn't support nop.b for now. */ static void __init_or_module fill_nop_inst(unsigned long stag, unsigned long etag) { extern const bundle_t paravirt_nop_mfi_inst_bundle[]; unsigned long tag; const ia64_inst_t nop_inst = paravirt_read_slot0(paravirt_nop_mfi_inst_bundle); for (tag = stag; tag < etag; tag = paravirt_get_next_tag(tag)) paravirt_write_inst(tag, nop_inst); } void __init_or_module paravirt_patch_apply_inst(const struct paravirt_patch_site_inst *start, const struct paravirt_patch_site_inst *end) { const struct paravirt_patch_site_inst *p; if (noreplace_paravirt) return; if (pv_init_ops.patch_inst == NULL) return; for (p = start; p < end; p++) { unsigned long tag; bundle_t *sbundle; bundle_t *ebundle; tag = (*pv_init_ops.patch_inst)(p->stag, p->etag, p->type); if (tag == p->stag) continue; fill_nop_inst(tag, p->etag); sbundle = paravirt_get_bundle(p->stag); ebundle = paravirt_get_bundle(p->etag) + 1; paravirt_flush_i_cache_range(sbundle, (ebundle - sbundle) * sizeof(bundle_t)); } ia64_sync_i(); ia64_srlz_i(); } #endif /* ASM_SUPPOTED */ /* brl.cond.sptk.many <target64> X3 */ typedef union inst_x3_op { ia64_inst_t inst; struct { unsigned long qp: 6; unsigned long btyp: 3; unsigned long unused: 3; unsigned long p: 1; unsigned long imm20b: 20; unsigned long wh: 2; unsigned long d: 1; unsigned long i: 1; unsigned long opcode: 4; }; unsigned long l; } inst_x3_op_t; typedef union inst_x3_imm { ia64_inst_t inst; struct { unsigned long unused: 2; unsigned long imm39: 39; }; unsigned long l; } inst_x3_imm_t; void __init_or_module paravirt_patch_reloc_brl(unsigned long tag, const void *target) { unsigned long tag_op = paravirt_get_next_tag(tag); unsigned long tag_imm = tag; bundle_t *bundle = paravirt_get_bundle(tag); ia64_inst_t inst_op = paravirt_read_inst(tag_op); ia64_inst_t inst_imm = paravirt_read_inst(tag_imm); inst_x3_op_t inst_x3_op = { .l = inst_op.l }; inst_x3_imm_t inst_x3_imm = { .l = inst_imm.l }; unsigned long imm60 = ((unsigned long)target - (unsigned long)bundle) >> 4; BUG_ON(paravirt_get_slot(tag) != 1); /* MLX */ BUG_ON(((unsigned long)target & (sizeof(bundle_t) - 1)) != 0); /* imm60[59] 1bit */ inst_x3_op.i = (imm60 >> 59) & 1; /* imm60[19:0] 20bit */ inst_x3_op.imm20b = imm60 & ((1UL << 20) - 1); /* imm60[58:20] 39bit */ inst_x3_imm.imm39 = (imm60 >> 20) & ((1UL << 39) - 1); inst_op.l = inst_x3_op.l; inst_imm.l = inst_x3_imm.l; paravirt_write_inst(tag_op, inst_op); paravirt_write_inst(tag_imm, inst_imm); } /* br.cond.sptk.many <target25> B1 */ typedef union inst_b1 { ia64_inst_t inst; struct { unsigned long qp: 6; unsigned long btype: 3; unsigned long unused: 3; unsigned long p: 1; unsigned long imm20b: 20; unsigned long wh: 2; unsigned long d: 1; unsigned long s: 1; unsigned long opcode: 4; }; unsigned long l; } inst_b1_t; void __init paravirt_patch_reloc_br(unsigned long tag, const void *target) { bundle_t *bundle = paravirt_get_bundle(tag); ia64_inst_t inst = paravirt_read_inst(tag); unsigned long target25 = (unsigned long)target - (unsigned long)bundle; inst_b1_t inst_b1; BUG_ON(((unsigned long)target & (sizeof(bundle_t) - 1)) != 0); inst_b1.l = inst.l; if (target25 & (1UL << 63)) inst_b1.s = 1; else inst_b1.s = 0; inst_b1.imm20b = target25 >> 4; inst.l = inst_b1.l; paravirt_write_inst(tag, inst); } void __init __paravirt_patch_apply_branch( unsigned long tag, unsigned long type, const struct paravirt_patch_branch_target *entries, unsigned int nr_entries) { unsigned int i; for (i = 0; i < nr_entries; i++) { if (entries[i].type == type) { paravirt_patch_reloc_br(tag, entries[i].entry); break; } } } static void __init paravirt_patch_apply_branch(const struct paravirt_patch_site_branch *start, const struct paravirt_patch_site_branch *end) { const struct paravirt_patch_site_branch *p; if (noreplace_paravirt) return; if (pv_init_ops.patch_branch == NULL) return; for (p = start; p < end; p++) (*pv_init_ops.patch_branch)(p->tag, p->type); ia64_sync_i(); ia64_srlz_i(); } void __init paravirt_patch_apply(void) { extern const char __start_paravirt_bundles[]; extern const char __stop_paravirt_bundles[]; extern const char __start_paravirt_insts[]; extern const char __stop_paravirt_insts[]; extern const char __start_paravirt_branches[]; extern const char __stop_paravirt_branches[]; paravirt_patch_apply_bundle((const struct paravirt_patch_site_bundle *) __start_paravirt_bundles, (const struct paravirt_patch_site_bundle *) __stop_paravirt_bundles); paravirt_patch_apply_inst((const struct paravirt_patch_site_inst *) __start_paravirt_insts, (const struct paravirt_patch_site_inst *) __stop_paravirt_insts); paravirt_patch_apply_branch((const struct paravirt_patch_site_branch *) __start_paravirt_branches, (const struct paravirt_patch_site_branch *) __stop_paravirt_branches); } /* * Local variables: * mode: C * c-set-style: "linux" * c-basic-offset: 8 * tab-width: 8 * indent-tabs-mode: t * End: */