inst.c 7.39 KB
Newer Older
1 2 3 4
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
 */
5 6 7 8
#include <linux/sizes.h>
#include <linux/uaccess.h>

#include <asm/cacheflush.h>
9 10
#include <asm/inst.h>

11 12
static DEFINE_RAW_SPINLOCK(patch_lock);

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
void simu_pc(struct pt_regs *regs, union loongarch_instruction insn)
{
	unsigned long pc = regs->csr_era;
	unsigned int rd = insn.reg1i20_format.rd;
	unsigned int imm = insn.reg1i20_format.immediate;

	if (pc & 3) {
		pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
		return;
	}

	switch (insn.reg1i20_format.opcode) {
	case pcaddi_op:
		regs->regs[rd] = pc + sign_extend64(imm << 2, 21);
		break;
	case pcaddu12i_op:
		regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
		break;
	case pcaddu18i_op:
		regs->regs[rd] = pc + sign_extend64(imm << 18, 37);
		break;
	case pcalau12i_op:
		regs->regs[rd] = pc + sign_extend64(imm << 12, 31);
		regs->regs[rd] &= ~((1 << 12) - 1);
		break;
	default:
		pr_info("%s: unknown opcode\n", __func__);
		return;
	}

	regs->csr_era += LOONGARCH_INSN_SIZE;
}

void simu_branch(struct pt_regs *regs, union loongarch_instruction insn)
{
	unsigned int imm, imm_l, imm_h, rd, rj;
	unsigned long pc = regs->csr_era;

	if (pc & 3) {
		pr_warn("%s: invalid pc 0x%lx\n", __func__, pc);
		return;
	}

	imm_l = insn.reg0i26_format.immediate_l;
	imm_h = insn.reg0i26_format.immediate_h;
	switch (insn.reg0i26_format.opcode) {
	case b_op:
		regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
		return;
	case bl_op:
		regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 27);
		regs->regs[1] = pc + LOONGARCH_INSN_SIZE;
		return;
	}

	imm_l = insn.reg1i21_format.immediate_l;
	imm_h = insn.reg1i21_format.immediate_h;
	rj = insn.reg1i21_format.rj;
	switch (insn.reg1i21_format.opcode) {
	case beqz_op:
		if (regs->regs[rj] == 0)
			regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		return;
	case bnez_op:
		if (regs->regs[rj] != 0)
			regs->csr_era = pc + sign_extend64((imm_h << 16 | imm_l) << 2, 22);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		return;
	}

	imm = insn.reg2i16_format.immediate;
	rj = insn.reg2i16_format.rj;
	rd = insn.reg2i16_format.rd;
	switch (insn.reg2i16_format.opcode) {
	case beq_op:
		if (regs->regs[rj] == regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case bne_op:
		if (regs->regs[rj] != regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case blt_op:
		if ((long)regs->regs[rj] < (long)regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case bge_op:
		if ((long)regs->regs[rj] >= (long)regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case bltu_op:
		if (regs->regs[rj] < regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case bgeu_op:
		if (regs->regs[rj] >= regs->regs[rd])
			regs->csr_era = pc + sign_extend64(imm << 2, 17);
		else
			regs->csr_era = pc + LOONGARCH_INSN_SIZE;
		break;
	case jirl_op:
		regs->csr_era = regs->regs[rj] + sign_extend64(imm << 2, 17);
		regs->regs[rd] = pc + LOONGARCH_INSN_SIZE;
		break;
	default:
		pr_info("%s: unknown opcode\n", __func__);
		return;
	}
}

136 137
bool insns_not_supported(union loongarch_instruction insn)
{
138 139 140 141 142 143
	switch (insn.reg3_format.opcode) {
	case amswapw_op ... ammindbdu_op:
		pr_notice("atomic memory access instructions are not supported\n");
		return true;
	}

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
	switch (insn.reg2i14_format.opcode) {
	case llw_op:
	case lld_op:
	case scw_op:
	case scd_op:
		pr_notice("ll and sc instructions are not supported\n");
		return true;
	}

	switch (insn.reg1i21_format.opcode) {
	case bceqz_op:
		pr_notice("bceqz and bcnez instructions are not supported\n");
		return true;
	}

	return false;
}

bool insns_need_simulation(union loongarch_instruction insn)
{
	if (is_pc_ins(&insn))
		return true;

	if (is_branch_ins(&insn))
		return true;

	return false;
}

void arch_simulate_insn(union loongarch_instruction insn, struct pt_regs *regs)
{
	if (is_pc_ins(&insn))
		simu_pc(regs, insn);
	else if (is_branch_ins(&insn))
		simu_branch(regs, insn);
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
int larch_insn_read(void *addr, u32 *insnp)
{
	int ret;
	u32 val;

	ret = copy_from_kernel_nofault(&val, addr, LOONGARCH_INSN_SIZE);
	if (!ret)
		*insnp = val;

	return ret;
}

int larch_insn_write(void *addr, u32 insn)
{
	int ret;
	unsigned long flags = 0;

	raw_spin_lock_irqsave(&patch_lock, flags);
	ret = copy_to_kernel_nofault(addr, &insn, LOONGARCH_INSN_SIZE);
	raw_spin_unlock_irqrestore(&patch_lock, flags);

	return ret;
}

int larch_insn_patch_text(void *addr, u32 insn)
{
	int ret;
	u32 *tp = addr;

	if ((unsigned long)tp & 3)
		return -EINVAL;

	ret = larch_insn_write(tp, insn);
	if (!ret)
		flush_icache_range((unsigned long)tp,
				   (unsigned long)tp + LOONGARCH_INSN_SIZE);

	return ret;
}

u32 larch_insn_gen_nop(void)
{
	return INSN_NOP;
}

226 227 228 229 230 231 232 233 234 235
u32 larch_insn_gen_b(unsigned long pc, unsigned long dest)
{
	long offset = dest - pc;
	union loongarch_instruction insn;

	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
		pr_warn("The generated b instruction is out of range.\n");
		return INSN_BREAK;
	}

236
	emit_b(&insn, offset >> 2);
237 238 239 240

	return insn.word;
}

241 242 243 244 245 246 247 248 249 250
u32 larch_insn_gen_bl(unsigned long pc, unsigned long dest)
{
	long offset = dest - pc;
	union loongarch_instruction insn;

	if ((offset & 3) || offset < -SZ_128M || offset >= SZ_128M) {
		pr_warn("The generated bl instruction is out of range.\n");
		return INSN_BREAK;
	}

251
	emit_bl(&insn, offset >> 2);
252 253 254 255

	return insn.word;
}

256 257 258 259 260 261 262 263 264 265 266 267 268 269
u32 larch_insn_gen_break(int imm)
{
	union loongarch_instruction insn;

	if (imm < 0 || imm >= SZ_32K) {
		pr_warn("The generated break instruction is out of range.\n");
		return INSN_BREAK;
	}

	emit_break(&insn, imm);

	return insn.word;
}

270 271 272 273
u32 larch_insn_gen_or(enum loongarch_gpr rd, enum loongarch_gpr rj, enum loongarch_gpr rk)
{
	union loongarch_instruction insn;

274
	emit_or(&insn, rd, rj, rk);
275 276 277 278 279 280 281 282 283

	return insn.word;
}

u32 larch_insn_gen_move(enum loongarch_gpr rd, enum loongarch_gpr rj)
{
	return larch_insn_gen_or(rd, rj, 0);
}

284 285 286 287
u32 larch_insn_gen_lu12iw(enum loongarch_gpr rd, int imm)
{
	union loongarch_instruction insn;

288 289 290 291 292
	if (imm < -SZ_512K || imm >= SZ_512K) {
		pr_warn("The generated lu12i.w instruction is out of range.\n");
		return INSN_BREAK;
	}

293
	emit_lu12iw(&insn, rd, imm);
294 295 296 297

	return insn.word;
}

298 299 300 301
u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
{
	union loongarch_instruction insn;

302 303 304 305 306
	if (imm < -SZ_512K || imm >= SZ_512K) {
		pr_warn("The generated lu32i.d instruction is out of range.\n");
		return INSN_BREAK;
	}

307
	emit_lu32id(&insn, rd, imm);
308 309 310 311 312 313 314 315

	return insn.word;
}

u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
{
	union loongarch_instruction insn;

316 317 318 319 320
	if (imm < -SZ_2K || imm >= SZ_2K) {
		pr_warn("The generated lu52i.d instruction is out of range.\n");
		return INSN_BREAK;
	}

321
	emit_lu52id(&insn, rd, rj, imm);
322 323 324 325

	return insn.word;
}

326
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
327 328 329
{
	union loongarch_instruction insn;

330 331 332 333 334 335
	if ((imm & 3) || imm < -SZ_128K || imm >= SZ_128K) {
		pr_warn("The generated jirl instruction is out of range.\n");
		return INSN_BREAK;
	}

	emit_jirl(&insn, rj, rd, imm >> 2);
336 337 338

	return insn.word;
}