Merge branch '6.2/mpt-mpi' into 6.2/scsi-fixes
[linux-2.6-microblaze.git] / arch / loongarch / kernel / inst.c
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 }