1*b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * Instruction-patching support.
41da177e4SLinus Torvalds *
51da177e4SLinus Torvalds * Copyright (C) 2003 Hewlett-Packard Co
61da177e4SLinus Torvalds * David Mosberger-Tang <davidm@hpl.hp.com>
71da177e4SLinus Torvalds */
81da177e4SLinus Torvalds #include <linux/init.h>
91da177e4SLinus Torvalds #include <linux/string.h>
101da177e4SLinus Torvalds
111da177e4SLinus Torvalds #include <asm/patch.h>
121da177e4SLinus Torvalds #include <asm/processor.h>
131da177e4SLinus Torvalds #include <asm/sections.h>
141da177e4SLinus Torvalds #include <asm/unistd.h>
151da177e4SLinus Torvalds
161da177e4SLinus Torvalds /*
171da177e4SLinus Torvalds * This was adapted from code written by Tony Luck:
181da177e4SLinus Torvalds *
191da177e4SLinus Torvalds * The 64-bit value in a "movl reg=value" is scattered between the two words of the bundle
201da177e4SLinus Torvalds * like this:
211da177e4SLinus Torvalds *
221da177e4SLinus Torvalds * 6 6 5 4 3 2 1
231da177e4SLinus Torvalds * 3210987654321098765432109876543210987654321098765432109876543210
241da177e4SLinus Torvalds * ABBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCDEEEEEFFFFFFFFFGGGGGGG
251da177e4SLinus Torvalds *
261da177e4SLinus Torvalds * CCCCCCCCCCCCCCCCCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
271da177e4SLinus Torvalds * xxxxAFFFFFFFFFEEEEEDxGGGGGGGxxxxxxxxxxxxxBBBBBBBBBBBBBBBBBBBBBBB
281da177e4SLinus Torvalds */
291da177e4SLinus Torvalds static u64
get_imm64(u64 insn_addr)301da177e4SLinus Torvalds get_imm64 (u64 insn_addr)
311da177e4SLinus Torvalds {
321da177e4SLinus Torvalds u64 *p = (u64 *) (insn_addr & -16); /* mask out slot number */
331da177e4SLinus Torvalds
341da177e4SLinus Torvalds return ( (p[1] & 0x0800000000000000UL) << 4) | /*A*/
351da177e4SLinus Torvalds ((p[1] & 0x00000000007fffffUL) << 40) | /*B*/
361da177e4SLinus Torvalds ((p[0] & 0xffffc00000000000UL) >> 24) | /*C*/
371da177e4SLinus Torvalds ((p[1] & 0x0000100000000000UL) >> 23) | /*D*/
381da177e4SLinus Torvalds ((p[1] & 0x0003e00000000000UL) >> 29) | /*E*/
391da177e4SLinus Torvalds ((p[1] & 0x07fc000000000000UL) >> 43) | /*F*/
401da177e4SLinus Torvalds ((p[1] & 0x000007f000000000UL) >> 36); /*G*/
411da177e4SLinus Torvalds }
421da177e4SLinus Torvalds
431da177e4SLinus Torvalds /* Patch instruction with "val" where "mask" has 1 bits. */
441da177e4SLinus Torvalds void
ia64_patch(u64 insn_addr,u64 mask,u64 val)451da177e4SLinus Torvalds ia64_patch (u64 insn_addr, u64 mask, u64 val)
461da177e4SLinus Torvalds {
471da177e4SLinus Torvalds u64 m0, m1, v0, v1, b0, b1, *b = (u64 *) (insn_addr & -16);
481da177e4SLinus Torvalds # define insn_mask ((1UL << 41) - 1)
491da177e4SLinus Torvalds unsigned long shift;
501da177e4SLinus Torvalds
511da177e4SLinus Torvalds b0 = b[0]; b1 = b[1];
521da177e4SLinus Torvalds shift = 5 + 41 * (insn_addr % 16); /* 5 bits of template, then 3 x 41-bit instructions */
531da177e4SLinus Torvalds if (shift >= 64) {
541da177e4SLinus Torvalds m1 = mask << (shift - 64);
551da177e4SLinus Torvalds v1 = val << (shift - 64);
561da177e4SLinus Torvalds } else {
571da177e4SLinus Torvalds m0 = mask << shift; m1 = mask >> (64 - shift);
581da177e4SLinus Torvalds v0 = val << shift; v1 = val >> (64 - shift);
591da177e4SLinus Torvalds b[0] = (b0 & ~m0) | (v0 & m0);
601da177e4SLinus Torvalds }
611da177e4SLinus Torvalds b[1] = (b1 & ~m1) | (v1 & m1);
621da177e4SLinus Torvalds }
631da177e4SLinus Torvalds
641da177e4SLinus Torvalds void
ia64_patch_imm64(u64 insn_addr,u64 val)651da177e4SLinus Torvalds ia64_patch_imm64 (u64 insn_addr, u64 val)
661da177e4SLinus Torvalds {
679c184a07SH. J. Lu /* The assembler may generate offset pointing to either slot 1
689c184a07SH. J. Lu or slot 2 for a long (2-slot) instruction, occupying slots 1
699c184a07SH. J. Lu and 2. */
709c184a07SH. J. Lu insn_addr &= -16UL;
719c184a07SH. J. Lu ia64_patch(insn_addr + 2,
721da177e4SLinus Torvalds 0x01fffefe000UL, ( ((val & 0x8000000000000000UL) >> 27) /* bit 63 -> 36 */
731da177e4SLinus Torvalds | ((val & 0x0000000000200000UL) << 0) /* bit 21 -> 21 */
741da177e4SLinus Torvalds | ((val & 0x00000000001f0000UL) << 6) /* bit 16 -> 22 */
751da177e4SLinus Torvalds | ((val & 0x000000000000ff80UL) << 20) /* bit 7 -> 27 */
761da177e4SLinus Torvalds | ((val & 0x000000000000007fUL) << 13) /* bit 0 -> 13 */));
779c184a07SH. J. Lu ia64_patch(insn_addr + 1, 0x1ffffffffffUL, val >> 22);
781da177e4SLinus Torvalds }
791da177e4SLinus Torvalds
801da177e4SLinus Torvalds void
ia64_patch_imm60(u64 insn_addr,u64 val)811da177e4SLinus Torvalds ia64_patch_imm60 (u64 insn_addr, u64 val)
821da177e4SLinus Torvalds {
839c184a07SH. J. Lu /* The assembler may generate offset pointing to either slot 1
849c184a07SH. J. Lu or slot 2 for a long (2-slot) instruction, occupying slots 1
859c184a07SH. J. Lu and 2. */
869c184a07SH. J. Lu insn_addr &= -16UL;
879c184a07SH. J. Lu ia64_patch(insn_addr + 2,
881da177e4SLinus Torvalds 0x011ffffe000UL, ( ((val & 0x0800000000000000UL) >> 23) /* bit 59 -> 36 */
891da177e4SLinus Torvalds | ((val & 0x00000000000fffffUL) << 13) /* bit 0 -> 13 */));
909c184a07SH. J. Lu ia64_patch(insn_addr + 1, 0x1fffffffffcUL, val >> 18);
911da177e4SLinus Torvalds }
921da177e4SLinus Torvalds
931da177e4SLinus Torvalds /*
941da177e4SLinus Torvalds * We need sometimes to load the physical address of a kernel
951da177e4SLinus Torvalds * object. Often we can convert the virtual address to physical
961da177e4SLinus Torvalds * at execution time, but sometimes (either for performance reasons
971da177e4SLinus Torvalds * or during error recovery) we cannot to this. Patch the marked
981da177e4SLinus Torvalds * bundles to load the physical address.
991da177e4SLinus Torvalds */
1001da177e4SLinus Torvalds void __init
ia64_patch_vtop(unsigned long start,unsigned long end)1011da177e4SLinus Torvalds ia64_patch_vtop (unsigned long start, unsigned long end)
1021da177e4SLinus Torvalds {
1031da177e4SLinus Torvalds s32 *offp = (s32 *) start;
1041da177e4SLinus Torvalds u64 ip;
1051da177e4SLinus Torvalds
1061da177e4SLinus Torvalds while (offp < (s32 *) end) {
1071da177e4SLinus Torvalds ip = (u64) offp + *offp;
1081da177e4SLinus Torvalds
1091da177e4SLinus Torvalds /* replace virtual address with corresponding physical address: */
1101da177e4SLinus Torvalds ia64_patch_imm64(ip, ia64_tpa(get_imm64(ip)));
1111da177e4SLinus Torvalds ia64_fc((void *) ip);
1121da177e4SLinus Torvalds ++offp;
1131da177e4SLinus Torvalds }
1141da177e4SLinus Torvalds ia64_sync_i();
1151da177e4SLinus Torvalds ia64_srlz_i();
1161da177e4SLinus Torvalds }
1171da177e4SLinus Torvalds
1184dcc29e1STony Luck /*
1194dcc29e1STony Luck * Disable the RSE workaround by turning the conditional branch
1204dcc29e1STony Luck * that we tagged in each place the workaround was used into an
1214dcc29e1STony Luck * unconditional branch.
1224dcc29e1STony Luck */
1234dcc29e1STony Luck void __init
ia64_patch_rse(unsigned long start,unsigned long end)1244dcc29e1STony Luck ia64_patch_rse (unsigned long start, unsigned long end)
1254dcc29e1STony Luck {
1264dcc29e1STony Luck s32 *offp = (s32 *) start;
1274dcc29e1STony Luck u64 ip, *b;
1284dcc29e1STony Luck
1294dcc29e1STony Luck while (offp < (s32 *) end) {
1304dcc29e1STony Luck ip = (u64) offp + *offp;
1314dcc29e1STony Luck
1324dcc29e1STony Luck b = (u64 *)(ip & -16);
1334dcc29e1STony Luck b[1] &= ~0xf800000L;
1344dcc29e1STony Luck ia64_fc((void *) ip);
1354dcc29e1STony Luck ++offp;
1364dcc29e1STony Luck }
1374dcc29e1STony Luck ia64_sync_i();
1384dcc29e1STony Luck ia64_srlz_i();
1394dcc29e1STony Luck }
1404dcc29e1STony Luck
141914a4ea4SChen, Kenneth W void __init
ia64_patch_mckinley_e9(unsigned long start,unsigned long end)1421da177e4SLinus Torvalds ia64_patch_mckinley_e9 (unsigned long start, unsigned long end)
1431da177e4SLinus Torvalds {
1441da177e4SLinus Torvalds static int first_time = 1;
1451da177e4SLinus Torvalds int need_workaround;
1461da177e4SLinus Torvalds s32 *offp = (s32 *) start;
1471da177e4SLinus Torvalds u64 *wp;
1481da177e4SLinus Torvalds
1491da177e4SLinus Torvalds need_workaround = (local_cpu_data->family == 0x1f && local_cpu_data->model == 0);
1501da177e4SLinus Torvalds
1511da177e4SLinus Torvalds if (first_time) {
1521da177e4SLinus Torvalds first_time = 0;
1531da177e4SLinus Torvalds if (need_workaround)
1541da177e4SLinus Torvalds printk(KERN_INFO "Leaving McKinley Errata 9 workaround enabled\n");
1551da177e4SLinus Torvalds }
1561da177e4SLinus Torvalds if (need_workaround)
1571da177e4SLinus Torvalds return;
1581da177e4SLinus Torvalds
1591da177e4SLinus Torvalds while (offp < (s32 *) end) {
1601da177e4SLinus Torvalds wp = (u64 *) ia64_imva((char *) offp + *offp);
1614fe01c68SHidetoshi Seto wp[0] = 0x0000000100000011UL; /* nop.m 0; nop.i 0; br.ret.sptk.many b6 */
1624fe01c68SHidetoshi Seto wp[1] = 0x0084006880000200UL;
1634fe01c68SHidetoshi Seto wp[2] = 0x0000000100000000UL; /* nop.m 0; nop.i 0; nop.i 0 */
1644fe01c68SHidetoshi Seto wp[3] = 0x0004000000000200UL;
1651da177e4SLinus Torvalds ia64_fc(wp); ia64_fc(wp + 2);
1661da177e4SLinus Torvalds ++offp;
1671da177e4SLinus Torvalds }
1681da177e4SLinus Torvalds ia64_sync_i();
1691da177e4SLinus Torvalds ia64_srlz_i();
1701da177e4SLinus Torvalds }
1711da177e4SLinus Torvalds
172914a4ea4SChen, Kenneth W static void __init
patch_fsyscall_table(unsigned long start,unsigned long end)1731da177e4SLinus Torvalds patch_fsyscall_table (unsigned long start, unsigned long end)
1741da177e4SLinus Torvalds {
175e55645ecSLuis R. Rodriguez extern unsigned long fsyscall_table[NR_syscalls];
1761da177e4SLinus Torvalds s32 *offp = (s32 *) start;
1771da177e4SLinus Torvalds u64 ip;
1781da177e4SLinus Torvalds
1791da177e4SLinus Torvalds while (offp < (s32 *) end) {
1801da177e4SLinus Torvalds ip = (u64) ia64_imva((char *) offp + *offp);
181e55645ecSLuis R. Rodriguez ia64_patch_imm64(ip, (u64) fsyscall_table);
1821da177e4SLinus Torvalds ia64_fc((void *) ip);
1831da177e4SLinus Torvalds ++offp;
1841da177e4SLinus Torvalds }
1851da177e4SLinus Torvalds ia64_sync_i();
1861da177e4SLinus Torvalds ia64_srlz_i();
1871da177e4SLinus Torvalds }
1881da177e4SLinus Torvalds
189914a4ea4SChen, Kenneth W static void __init
patch_brl_fsys_bubble_down(unsigned long start,unsigned long end)1901da177e4SLinus Torvalds patch_brl_fsys_bubble_down (unsigned long start, unsigned long end)
1911da177e4SLinus Torvalds {
192e55645ecSLuis R. Rodriguez extern char fsys_bubble_down[];
1931da177e4SLinus Torvalds s32 *offp = (s32 *) start;
1941da177e4SLinus Torvalds u64 ip;
1951da177e4SLinus Torvalds
1961da177e4SLinus Torvalds while (offp < (s32 *) end) {
1971da177e4SLinus Torvalds ip = (u64) offp + *offp;
1981da177e4SLinus Torvalds ia64_patch_imm60((u64) ia64_imva((void *) ip),
1991da177e4SLinus Torvalds (u64) (fsys_bubble_down - (ip & -16)) / 16);
2001da177e4SLinus Torvalds ia64_fc((void *) ip);
2011da177e4SLinus Torvalds ++offp;
2021da177e4SLinus Torvalds }
2031da177e4SLinus Torvalds ia64_sync_i();
2041da177e4SLinus Torvalds ia64_srlz_i();
2051da177e4SLinus Torvalds }
2061da177e4SLinus Torvalds
207914a4ea4SChen, Kenneth W void __init
ia64_patch_gate(void)2081da177e4SLinus Torvalds ia64_patch_gate (void)
2091da177e4SLinus Torvalds {
210e55645ecSLuis R. Rodriguez # define START(name) ((unsigned long) __start_gate_##name##_patchlist)
211e55645ecSLuis R. Rodriguez # define END(name) ((unsigned long)__end_gate_##name##_patchlist)
2121da177e4SLinus Torvalds
213e55645ecSLuis R. Rodriguez patch_fsyscall_table(START(fsyscall), END(fsyscall));
214e55645ecSLuis R. Rodriguez patch_brl_fsys_bubble_down(START(brl_fsys_bubble_down), END(brl_fsys_bubble_down));
215e55645ecSLuis R. Rodriguez ia64_patch_vtop(START(vtop), END(vtop));
216e55645ecSLuis R. Rodriguez ia64_patch_mckinley_e9(START(mckinley_e9), END(mckinley_e9));
2171da177e4SLinus Torvalds }
218a0776ec8SChen, Kenneth W
ia64_patch_phys_stack_reg(unsigned long val)219a0776ec8SChen, Kenneth W void ia64_patch_phys_stack_reg(unsigned long val)
220a0776ec8SChen, Kenneth W {
221a0776ec8SChen, Kenneth W s32 * offp = (s32 *) __start___phys_stack_reg_patchlist;
222a0776ec8SChen, Kenneth W s32 * end = (s32 *) __end___phys_stack_reg_patchlist;
223a0776ec8SChen, Kenneth W u64 ip, mask, imm;
224a0776ec8SChen, Kenneth W
225a0776ec8SChen, Kenneth W /* see instruction format A4: adds r1 = imm13, r3 */
226a0776ec8SChen, Kenneth W mask = (0x3fUL << 27) | (0x7f << 13);
227a0776ec8SChen, Kenneth W imm = (((val >> 7) & 0x3f) << 27) | (val & 0x7f) << 13;
228a0776ec8SChen, Kenneth W
229a0776ec8SChen, Kenneth W while (offp < end) {
230a0776ec8SChen, Kenneth W ip = (u64) offp + *offp;
231a0776ec8SChen, Kenneth W ia64_patch(ip, mask, imm);
2327120569cSIsaku Yamahata ia64_fc((void *)ip);
233a0776ec8SChen, Kenneth W ++offp;
234a0776ec8SChen, Kenneth W }
235a0776ec8SChen, Kenneth W ia64_sync_i();
236a0776ec8SChen, Kenneth W ia64_srlz_i();
237a0776ec8SChen, Kenneth W }
238