xref: /openbmc/linux/arch/loongarch/kernel/inst.c (revision c699ce1a)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
4  */
5 #include <linux/sizes.h>
6 #include <linux/uaccess.h>
7 
8 #include <asm/cacheflush.h>
9 #include <asm/inst.h>
10 
11 static DEFINE_RAW_SPINLOCK(patch_lock);
12 
13 int larch_insn_read(void *addr, u32 *insnp)
14 {
15 	int ret;
16 	u32 val;
17 
18 	ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
19 	if (!ret)
20 		*insnp = val;
21 
22 	return ret;
23 }
24 
25 int larch_insn_write(void *addr, u32 insn)
26 {
27 	int ret;
28 	unsigned long flags = 0;
29 
30 	raw_spin_lock_irqsave(&patch_lock, flags);
31 	ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
32 	raw_spin_unlock_irqrestore(&patch_lock, flags);
33 
34 	return ret;
35 }
36 
37 int larch_insn_patch_text(void *addr, u32 insn)
38 {
39 	int ret;
40 	u32 *tp = addr;
41 
42 	if ((unsigned long)tp & 3)
43 		return -EINVAL;
44 
45 	ret = larch_insn_write(tp, insn);
46 	if (!ret)
47 		flush_icache_range((unsigned long)tp,
48 				   (unsigned long)tp + LOONGARCH_INSN_SIZE);
49 
50 	return ret;
51 }
52 
53 u32 larch_insn_gen_nop(void)
54 {
55 	return INSN_NOP;
56 }
57 
58 u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
59 {
60 	long offset = dest - pc;
61 	unsigned int immediate_l, immediate_h;
62 	union loongarch_instruction insn;
63 
64 	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
65 		pr_warn("The generated b instruction is out of range.\n");
66 		return INSN_BREAK;
67 	}
68 
69 	offset >>= 2;
70 
71 	immediate_l = offset & 0xffff;
72 	offset >>= 16;
73 	immediate_h = offset & 0x3ff;
74 
75 	insn.reg0i26_format.opcode = b_op;
76 	insn.reg0i26_format.immediate_l = immediate_l;
77 	insn.reg0i26_format.immediate_h = immediate_h;
78 
79 	return insn.word;
80 }
81 
82 u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
83 {
84 	long offset = dest - pc;
85 	unsigned int immediate_l, immediate_h;
86 	union loongarch_instruction insn;
87 
88 	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
89 		pr_warn("The generated bl instruction is out of range.\n");
90 		return INSN_BREAK;
91 	}
92 
93 	offset >>= 2;
94 
95 	immediate_l = offset & 0xffff;
96 	offset >>= 16;
97 	immediate_h = offset & 0x3ff;
98 
99 	insn.reg0i26_format.opcode = bl_op;
100 	insn.reg0i26_format.immediate_l = immediate_l;
101 	insn.reg0i26_format.immediate_h = immediate_h;
102 
103 	return insn.word;
104 }
105 
106 u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
107 {
108 	union loongarch_instruction insn;
109 
110 	insn.reg3_format.opcode = or_op;
111 	insn.reg3_format.rd = rd;
112 	insn.reg3_format.rj = rj;
113 	insn.reg3_format.rk = rk;
114 
115 	return insn.word;
116 }
117 
118 u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
119 {
120 	return larch_insn_gen_or(rd, rj, 0);
121 }
122 
123 u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
124 {
125 	union loongarch_instruction insn;
126 
127 	insn.reg1i20_format.opcode = lu12iw_op;
128 	insn.reg1i20_format.rd = rd;
129 	insn.reg1i20_format.immediate = imm;
130 
131 	return insn.word;
132 }
133 
134 u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
135 {
136 	union loongarch_instruction insn;
137 
138 	insn.reg1i20_format.opcode = lu32id_op;
139 	insn.reg1i20_format.rd = rd;
140 	insn.reg1i20_format.immediate = imm;
141 
142 	return insn.word;
143 }
144 
145 u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
146 {
147 	union loongarch_instruction insn;
148 
149 	insn.reg2i12_format.opcode = lu52id_op;
150 	insn.reg2i12_format.rd = rd;
151 	insn.reg2i12_format.rj = rj;
152 	insn.reg2i12_format.immediate = imm;
153 
154 	return insn.word;
155 }
156 
157 u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest)
158 {
159 	union loongarch_instruction insn;
160 
161 	insn.reg2i16_format.opcode = jirl_op;
162 	insn.reg2i16_format.rd = rd;
163 	insn.reg2i16_format.rj = rj;
164 	insn.reg2i16_format.immediate = (dest - pc) >> 2;
165 
166 	return insn.word;
167 }
168