1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2587064b6SPunit Agrawal /*
3587064b6SPunit Agrawal * Copyright (C) 2014 ARM Limited
4587064b6SPunit Agrawal */
5587064b6SPunit Agrawal
6c852f320SPunit Agrawal #include <linux/cpu.h>
7587064b6SPunit Agrawal #include <linux/init.h>
8587064b6SPunit Agrawal #include <linux/list.h>
9bd35a4adSPunit Agrawal #include <linux/perf_event.h>
10bd35a4adSPunit Agrawal #include <linux/sched.h>
11587064b6SPunit Agrawal #include <linux/slab.h>
12587064b6SPunit Agrawal #include <linux/sysctl.h>
1392faa7beSVincenzo Frascino #include <linux/uaccess.h>
14587064b6SPunit Agrawal
15338d4f49SJames Morse #include <asm/cpufeature.h>
16bd35a4adSPunit Agrawal #include <asm/insn.h>
17870828e5SJames Morse #include <asm/sysreg.h>
18bd35a4adSPunit Agrawal #include <asm/system_misc.h>
19587064b6SPunit Agrawal #include <asm/traps.h>
20587064b6SPunit Agrawal
21d784e298SPunit Agrawal #define CREATE_TRACE_POINTS
22d784e298SPunit Agrawal #include "trace-events-emulation.h"
23d784e298SPunit Agrawal
24587064b6SPunit Agrawal /*
25587064b6SPunit Agrawal * The runtime support for deprecated instruction support can be in one of
26587064b6SPunit Agrawal * following three states -
27587064b6SPunit Agrawal *
28587064b6SPunit Agrawal * 0 = undef
29587064b6SPunit Agrawal * 1 = emulate (software emulation)
30587064b6SPunit Agrawal * 2 = hw (supported in hardware)
31587064b6SPunit Agrawal */
32587064b6SPunit Agrawal enum insn_emulation_mode {
33587064b6SPunit Agrawal INSN_UNDEF,
34587064b6SPunit Agrawal INSN_EMULATE,
35587064b6SPunit Agrawal INSN_HW,
36587064b6SPunit Agrawal };
37587064b6SPunit Agrawal
38587064b6SPunit Agrawal enum legacy_insn_status {
39587064b6SPunit Agrawal INSN_DEPRECATED,
40587064b6SPunit Agrawal INSN_OBSOLETE,
41124c49b1SMark Rutland INSN_UNAVAILABLE,
42587064b6SPunit Agrawal };
43587064b6SPunit Agrawal
44b4453cc8SMark Rutland struct insn_emulation {
45587064b6SPunit Agrawal const char *name;
46587064b6SPunit Agrawal enum legacy_insn_status status;
47124c49b1SMark Rutland bool (*try_emulate)(struct pt_regs *regs,
48124c49b1SMark Rutland u32 insn);
49587064b6SPunit Agrawal int (*set_hw_mode)(bool enable);
50124c49b1SMark Rutland
51587064b6SPunit Agrawal int current_mode;
52587064b6SPunit Agrawal int min;
53587064b6SPunit Agrawal int max;
54124c49b1SMark Rutland
55de8a660bSJoel Granados /* sysctl for this emulation */
56de8a660bSJoel Granados struct ctl_table sysctl;
57587064b6SPunit Agrawal };
58587064b6SPunit Agrawal
590c5f4162SMark Rutland #define ARM_OPCODE_CONDTEST_FAIL 0
600c5f4162SMark Rutland #define ARM_OPCODE_CONDTEST_PASS 1
610c5f4162SMark Rutland #define ARM_OPCODE_CONDTEST_UNCOND 2
620c5f4162SMark Rutland
630c5f4162SMark Rutland #define ARM_OPCODE_CONDITION_UNCOND 0xf
640c5f4162SMark Rutland
aarch32_check_condition(u32 opcode,u32 psr)65223d3a0dSRen Zhijie static unsigned int __maybe_unused aarch32_check_condition(u32 opcode, u32 psr)
660c5f4162SMark Rutland {
670c5f4162SMark Rutland u32 cc_bits = opcode >> 28;
680c5f4162SMark Rutland
690c5f4162SMark Rutland if (cc_bits != ARM_OPCODE_CONDITION_UNCOND) {
700c5f4162SMark Rutland if ((*aarch32_opcode_cond_checks[cc_bits])(psr))
710c5f4162SMark Rutland return ARM_OPCODE_CONDTEST_PASS;
720c5f4162SMark Rutland else
730c5f4162SMark Rutland return ARM_OPCODE_CONDTEST_FAIL;
740c5f4162SMark Rutland }
750c5f4162SMark Rutland return ARM_OPCODE_CONDTEST_UNCOND;
760c5f4162SMark Rutland }
770c5f4162SMark Rutland
78124c49b1SMark Rutland #ifdef CONFIG_SWP_EMULATION
79587064b6SPunit Agrawal /*
80bd35a4adSPunit Agrawal * Implement emulation of the SWP/SWPB instructions using load-exclusive and
81bd35a4adSPunit Agrawal * store-exclusive.
82bd35a4adSPunit Agrawal *
83bd35a4adSPunit Agrawal * Syntax of SWP{B} instruction: SWP{B}<c> <Rt>, <Rt2>, [<Rn>]
84bd35a4adSPunit Agrawal * Where: Rt = destination
85bd35a4adSPunit Agrawal * Rt2 = source
86bd35a4adSPunit Agrawal * Rn = address
87bd35a4adSPunit Agrawal */
88bd35a4adSPunit Agrawal
89bd35a4adSPunit Agrawal /*
90bd35a4adSPunit Agrawal * Error-checking SWP macros implemented using ldxr{b}/stxr{b}
91bd35a4adSPunit Agrawal */
921c5b51dfSWill Deacon
931c5b51dfSWill Deacon /* Arbitrary constant to ensure forward-progress of the LL/SC loop */
941c5b51dfSWill Deacon #define __SWP_LL_SC_LOOPS 4
951c5b51dfSWill Deacon
961c5b51dfSWill Deacon #define __user_swpX_asm(data, addr, res, temp, temp2, B) \
97bd38967dSCatalin Marinas do { \
98923e1e7dSMark Rutland uaccess_enable_privileged(); \
99bd35a4adSPunit Agrawal __asm__ __volatile__( \
1002e77a62cSMark Rutland " mov %w3, %w6\n" \
1011c5b51dfSWill Deacon "0: ldxr"B" %w2, [%4]\n" \
1021c5b51dfSWill Deacon "1: stxr"B" %w0, %w1, [%4]\n" \
103bd35a4adSPunit Agrawal " cbz %w0, 2f\n" \
1041c5b51dfSWill Deacon " sub %w3, %w3, #1\n" \
1051c5b51dfSWill Deacon " cbnz %w3, 0b\n" \
1061c5b51dfSWill Deacon " mov %w0, %w5\n" \
107589cb22bSWill Deacon " b 3f\n" \
108bd35a4adSPunit Agrawal "2:\n" \
109589cb22bSWill Deacon " mov %w1, %w2\n" \
110589cb22bSWill Deacon "3:\n" \
1112e77a62cSMark Rutland _ASM_EXTABLE_UACCESS_ERR(0b, 3b, %w0) \
1122e77a62cSMark Rutland _ASM_EXTABLE_UACCESS_ERR(1b, 3b, %w0) \
1131c5b51dfSWill Deacon : "=&r" (res), "+r" (data), "=&r" (temp), "=&r" (temp2) \
11455de49f9SMark Rutland : "r" ((unsigned long)addr), "i" (-EAGAIN), \
1151c5b51dfSWill Deacon "i" (__SWP_LL_SC_LOOPS) \
116bd38967dSCatalin Marinas : "memory"); \
117923e1e7dSMark Rutland uaccess_disable_privileged(); \
118bd38967dSCatalin Marinas } while (0)
119bd35a4adSPunit Agrawal
1201c5b51dfSWill Deacon #define __user_swp_asm(data, addr, res, temp, temp2) \
1211c5b51dfSWill Deacon __user_swpX_asm(data, addr, res, temp, temp2, "")
1221c5b51dfSWill Deacon #define __user_swpb_asm(data, addr, res, temp, temp2) \
1231c5b51dfSWill Deacon __user_swpX_asm(data, addr, res, temp, temp2, "b")
124bd35a4adSPunit Agrawal
125bd35a4adSPunit Agrawal /*
126bd35a4adSPunit Agrawal * Bit 22 of the instruction encoding distinguishes between
127bd35a4adSPunit Agrawal * the SWP and SWPB variants (bit set means SWPB).
128bd35a4adSPunit Agrawal */
129bd35a4adSPunit Agrawal #define TYPE_SWPB (1 << 22)
130bd35a4adSPunit Agrawal
emulate_swpX(unsigned int address,unsigned int * data,unsigned int type)131bd35a4adSPunit Agrawal static int emulate_swpX(unsigned int address, unsigned int *data,
132bd35a4adSPunit Agrawal unsigned int type)
133bd35a4adSPunit Agrawal {
134bd35a4adSPunit Agrawal unsigned int res = 0;
135bd35a4adSPunit Agrawal
136bd35a4adSPunit Agrawal if ((type != TYPE_SWPB) && (address & 0x3)) {
137bd35a4adSPunit Agrawal /* SWP to unaligned address not permitted */
138bd35a4adSPunit Agrawal pr_debug("SWP instruction on unaligned pointer!\n");
139bd35a4adSPunit Agrawal return -EFAULT;
140bd35a4adSPunit Agrawal }
141bd35a4adSPunit Agrawal
142bd35a4adSPunit Agrawal while (1) {
1431c5b51dfSWill Deacon unsigned long temp, temp2;
144bd35a4adSPunit Agrawal
145bd35a4adSPunit Agrawal if (type == TYPE_SWPB)
1461c5b51dfSWill Deacon __user_swpb_asm(*data, address, res, temp, temp2);
147bd35a4adSPunit Agrawal else
1481c5b51dfSWill Deacon __user_swp_asm(*data, address, res, temp, temp2);
149bd35a4adSPunit Agrawal
150bd35a4adSPunit Agrawal if (likely(res != -EAGAIN) || signal_pending(current))
151bd35a4adSPunit Agrawal break;
152bd35a4adSPunit Agrawal
153bd35a4adSPunit Agrawal cond_resched();
154bd35a4adSPunit Agrawal }
155bd35a4adSPunit Agrawal
156bd35a4adSPunit Agrawal return res;
157bd35a4adSPunit Agrawal }
158bd35a4adSPunit Agrawal
159bd35a4adSPunit Agrawal /*
160bd35a4adSPunit Agrawal * swp_handler logs the id of calling process, dissects the instruction, sanity
161bd35a4adSPunit Agrawal * checks the memory location, calls emulate_swpX for the actual operation and
162bd35a4adSPunit Agrawal * deals with fixup/error handling before returning
163bd35a4adSPunit Agrawal */
swp_handler(struct pt_regs * regs,u32 instr)164bd35a4adSPunit Agrawal static int swp_handler(struct pt_regs *regs, u32 instr)
165bd35a4adSPunit Agrawal {
166bd35a4adSPunit Agrawal u32 destreg, data, type, address = 0;
1679085b34dSRobin Murphy const void __user *user_ptr;
168bd35a4adSPunit Agrawal int rn, rt2, res = 0;
169bd35a4adSPunit Agrawal
170bd35a4adSPunit Agrawal perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc);
171bd35a4adSPunit Agrawal
172bd35a4adSPunit Agrawal type = instr & TYPE_SWPB;
173bd35a4adSPunit Agrawal
1742af3ec08SDavid A. Long switch (aarch32_check_condition(instr, regs->pstate)) {
175bd35a4adSPunit Agrawal case ARM_OPCODE_CONDTEST_PASS:
176bd35a4adSPunit Agrawal break;
177bd35a4adSPunit Agrawal case ARM_OPCODE_CONDTEST_FAIL:
178bd35a4adSPunit Agrawal /* Condition failed - return to next instruction */
179bd35a4adSPunit Agrawal goto ret;
180bd35a4adSPunit Agrawal case ARM_OPCODE_CONDTEST_UNCOND:
181bd35a4adSPunit Agrawal /* If unconditional encoding - not a SWP, undef */
182bd35a4adSPunit Agrawal return -EFAULT;
183bd35a4adSPunit Agrawal default:
184bd35a4adSPunit Agrawal return -EINVAL;
185bd35a4adSPunit Agrawal }
186bd35a4adSPunit Agrawal
187bd35a4adSPunit Agrawal rn = aarch32_insn_extract_reg_num(instr, A32_RN_OFFSET);
188bd35a4adSPunit Agrawal rt2 = aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET);
189bd35a4adSPunit Agrawal
190bd35a4adSPunit Agrawal address = (u32)regs->user_regs.regs[rn];
191bd35a4adSPunit Agrawal data = (u32)regs->user_regs.regs[rt2];
192bd35a4adSPunit Agrawal destreg = aarch32_insn_extract_reg_num(instr, A32_RT_OFFSET);
193bd35a4adSPunit Agrawal
194bd35a4adSPunit Agrawal pr_debug("addr in r%d->0x%08x, dest is r%d, source in r%d->0x%08x)\n",
195bd35a4adSPunit Agrawal rn, address, destreg,
196bd35a4adSPunit Agrawal aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET), data);
197bd35a4adSPunit Agrawal
198bd35a4adSPunit Agrawal /* Check access in reasonable access range for both SWP and SWPB */
1999085b34dSRobin Murphy user_ptr = (const void __user *)(unsigned long)(address & ~3);
20096d4f267SLinus Torvalds if (!access_ok(user_ptr, 4)) {
201bd35a4adSPunit Agrawal pr_debug("SWP{B} emulation: access to 0x%08x not allowed!\n",
202bd35a4adSPunit Agrawal address);
203bd35a4adSPunit Agrawal goto fault;
204bd35a4adSPunit Agrawal }
205bd35a4adSPunit Agrawal
206bd35a4adSPunit Agrawal res = emulate_swpX(address, &data, type);
207bd35a4adSPunit Agrawal if (res == -EFAULT)
208bd35a4adSPunit Agrawal goto fault;
209bd35a4adSPunit Agrawal else if (res == 0)
210bd35a4adSPunit Agrawal regs->user_regs.regs[destreg] = data;
211bd35a4adSPunit Agrawal
212bd35a4adSPunit Agrawal ret:
213d784e298SPunit Agrawal if (type == TYPE_SWPB)
214d784e298SPunit Agrawal trace_instruction_emulation("swpb", regs->pc);
215d784e298SPunit Agrawal else
216d784e298SPunit Agrawal trace_instruction_emulation("swp", regs->pc);
217d784e298SPunit Agrawal
218bd35a4adSPunit Agrawal pr_warn_ratelimited("\"%s\" (%ld) uses obsolete SWP{B} instruction at 0x%llx\n",
219bd35a4adSPunit Agrawal current->comm, (unsigned long)current->pid, regs->pc);
220bd35a4adSPunit Agrawal
2216436beeeSJulien Thierry arm64_skip_faulting_instruction(regs, 4);
222bd35a4adSPunit Agrawal return 0;
223bd35a4adSPunit Agrawal
224bd35a4adSPunit Agrawal fault:
225390bf177SAndre Przywara pr_debug("SWP{B} emulation: access caused memory abort!\n");
2262c9120f3SWill Deacon arm64_notify_segfault(address);
227bd35a4adSPunit Agrawal
228bd35a4adSPunit Agrawal return 0;
229bd35a4adSPunit Agrawal }
230bd35a4adSPunit Agrawal
try_emulate_swp(struct pt_regs * regs,u32 insn)231124c49b1SMark Rutland static bool try_emulate_swp(struct pt_regs *regs, u32 insn)
232bd35a4adSPunit Agrawal {
233124c49b1SMark Rutland /* SWP{B} only exists in ARM state and does not exist in Thumb */
234124c49b1SMark Rutland if (!compat_user_mode(regs) || compat_thumb_mode(regs))
235124c49b1SMark Rutland return false;
236124c49b1SMark Rutland
237124c49b1SMark Rutland if ((insn & 0x0fb00ff0) != 0x01000090)
238124c49b1SMark Rutland return false;
239124c49b1SMark Rutland
240124c49b1SMark Rutland return swp_handler(regs, insn) == 0;
241124c49b1SMark Rutland }
242bd35a4adSPunit Agrawal
243b4453cc8SMark Rutland static struct insn_emulation insn_swp = {
244bd35a4adSPunit Agrawal .name = "swp",
245bd35a4adSPunit Agrawal .status = INSN_OBSOLETE,
246124c49b1SMark Rutland .try_emulate = try_emulate_swp,
247bd35a4adSPunit Agrawal .set_hw_mode = NULL,
248bd35a4adSPunit Agrawal };
249124c49b1SMark Rutland #endif /* CONFIG_SWP_EMULATION */
250bd35a4adSPunit Agrawal
251124c49b1SMark Rutland #ifdef CONFIG_CP15_BARRIER_EMULATION
cp15barrier_handler(struct pt_regs * regs,u32 instr)252c852f320SPunit Agrawal static int cp15barrier_handler(struct pt_regs *regs, u32 instr)
253c852f320SPunit Agrawal {
254c852f320SPunit Agrawal perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc);
255c852f320SPunit Agrawal
2562af3ec08SDavid A. Long switch (aarch32_check_condition(instr, regs->pstate)) {
257c852f320SPunit Agrawal case ARM_OPCODE_CONDTEST_PASS:
258c852f320SPunit Agrawal break;
259c852f320SPunit Agrawal case ARM_OPCODE_CONDTEST_FAIL:
260c852f320SPunit Agrawal /* Condition failed - return to next instruction */
261c852f320SPunit Agrawal goto ret;
262c852f320SPunit Agrawal case ARM_OPCODE_CONDTEST_UNCOND:
263c852f320SPunit Agrawal /* If unconditional encoding - not a barrier instruction */
264c852f320SPunit Agrawal return -EFAULT;
265c852f320SPunit Agrawal default:
266c852f320SPunit Agrawal return -EINVAL;
267c852f320SPunit Agrawal }
268c852f320SPunit Agrawal
269c852f320SPunit Agrawal switch (aarch32_insn_mcr_extract_crm(instr)) {
270c852f320SPunit Agrawal case 10:
271c852f320SPunit Agrawal /*
272c852f320SPunit Agrawal * dmb - mcr p15, 0, Rt, c7, c10, 5
273c852f320SPunit Agrawal * dsb - mcr p15, 0, Rt, c7, c10, 4
274c852f320SPunit Agrawal */
275d784e298SPunit Agrawal if (aarch32_insn_mcr_extract_opc2(instr) == 5) {
276c852f320SPunit Agrawal dmb(sy);
277d784e298SPunit Agrawal trace_instruction_emulation(
278d784e298SPunit Agrawal "mcr p15, 0, Rt, c7, c10, 5 ; dmb", regs->pc);
279d784e298SPunit Agrawal } else {
280c852f320SPunit Agrawal dsb(sy);
281d784e298SPunit Agrawal trace_instruction_emulation(
282d784e298SPunit Agrawal "mcr p15, 0, Rt, c7, c10, 4 ; dsb", regs->pc);
283d784e298SPunit Agrawal }
284c852f320SPunit Agrawal break;
285c852f320SPunit Agrawal case 5:
286c852f320SPunit Agrawal /*
287c852f320SPunit Agrawal * isb - mcr p15, 0, Rt, c7, c5, 4
288c852f320SPunit Agrawal *
289c852f320SPunit Agrawal * Taking an exception or returning from one acts as an
290c852f320SPunit Agrawal * instruction barrier. So no explicit barrier needed here.
291c852f320SPunit Agrawal */
292d784e298SPunit Agrawal trace_instruction_emulation(
293d784e298SPunit Agrawal "mcr p15, 0, Rt, c7, c5, 4 ; isb", regs->pc);
294c852f320SPunit Agrawal break;
295c852f320SPunit Agrawal }
296c852f320SPunit Agrawal
297c852f320SPunit Agrawal ret:
298c852f320SPunit Agrawal pr_warn_ratelimited("\"%s\" (%ld) uses deprecated CP15 Barrier instruction at 0x%llx\n",
299c852f320SPunit Agrawal current->comm, (unsigned long)current->pid, regs->pc);
300c852f320SPunit Agrawal
3016436beeeSJulien Thierry arm64_skip_faulting_instruction(regs, 4);
302c852f320SPunit Agrawal return 0;
303c852f320SPunit Agrawal }
304c852f320SPunit Agrawal
cp15_barrier_set_hw_mode(bool enable)305c852f320SPunit Agrawal static int cp15_barrier_set_hw_mode(bool enable)
306c852f320SPunit Agrawal {
307736d474fSSuzuki K. Poulose if (enable)
30825be597aSMark Rutland sysreg_clear_set(sctlr_el1, 0, SCTLR_EL1_CP15BEN);
309736d474fSSuzuki K. Poulose else
31025be597aSMark Rutland sysreg_clear_set(sctlr_el1, SCTLR_EL1_CP15BEN, 0);
311736d474fSSuzuki K. Poulose return 0;
312c852f320SPunit Agrawal }
313c852f320SPunit Agrawal
try_emulate_cp15_barrier(struct pt_regs * regs,u32 insn)314124c49b1SMark Rutland static bool try_emulate_cp15_barrier(struct pt_regs *regs, u32 insn)
315c852f320SPunit Agrawal {
316124c49b1SMark Rutland if (!compat_user_mode(regs) || compat_thumb_mode(regs))
317124c49b1SMark Rutland return false;
318124c49b1SMark Rutland
319124c49b1SMark Rutland if ((insn & 0x0fff0fdf) == 0x0e070f9a)
320124c49b1SMark Rutland return cp15barrier_handler(regs, insn) == 0;
321124c49b1SMark Rutland
322124c49b1SMark Rutland if ((insn & 0x0fff0fff) == 0x0e070f95)
323124c49b1SMark Rutland return cp15barrier_handler(regs, insn) == 0;
324124c49b1SMark Rutland
325124c49b1SMark Rutland return false;
326124c49b1SMark Rutland }
327c852f320SPunit Agrawal
328b4453cc8SMark Rutland static struct insn_emulation insn_cp15_barrier = {
329c852f320SPunit Agrawal .name = "cp15_barrier",
330c852f320SPunit Agrawal .status = INSN_DEPRECATED,
331124c49b1SMark Rutland .try_emulate = try_emulate_cp15_barrier,
332c852f320SPunit Agrawal .set_hw_mode = cp15_barrier_set_hw_mode,
333c852f320SPunit Agrawal };
334124c49b1SMark Rutland #endif /* CONFIG_CP15_BARRIER_EMULATION */
335c852f320SPunit Agrawal
336124c49b1SMark Rutland #ifdef CONFIG_SETEND_EMULATION
setend_set_hw_mode(bool enable)3372d888f48SSuzuki K. Poulose static int setend_set_hw_mode(bool enable)
3382d888f48SSuzuki K. Poulose {
3392d888f48SSuzuki K. Poulose if (!cpu_supports_mixed_endian_el0())
3402d888f48SSuzuki K. Poulose return -EINVAL;
3412d888f48SSuzuki K. Poulose
3422d888f48SSuzuki K. Poulose if (enable)
34325be597aSMark Rutland sysreg_clear_set(sctlr_el1, SCTLR_EL1_SED, 0);
3442d888f48SSuzuki K. Poulose else
34525be597aSMark Rutland sysreg_clear_set(sctlr_el1, 0, SCTLR_EL1_SED);
3462d888f48SSuzuki K. Poulose return 0;
3472d888f48SSuzuki K. Poulose }
3482d888f48SSuzuki K. Poulose
compat_setend_handler(struct pt_regs * regs,u32 big_endian)3492d888f48SSuzuki K. Poulose static int compat_setend_handler(struct pt_regs *regs, u32 big_endian)
3502d888f48SSuzuki K. Poulose {
3512d888f48SSuzuki K. Poulose char *insn;
3522d888f48SSuzuki K. Poulose
3532d888f48SSuzuki K. Poulose perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc);
3542d888f48SSuzuki K. Poulose
3552d888f48SSuzuki K. Poulose if (big_endian) {
3562d888f48SSuzuki K. Poulose insn = "setend be";
357d64567f6SMark Rutland regs->pstate |= PSR_AA32_E_BIT;
3582d888f48SSuzuki K. Poulose } else {
3592d888f48SSuzuki K. Poulose insn = "setend le";
360d64567f6SMark Rutland regs->pstate &= ~PSR_AA32_E_BIT;
3612d888f48SSuzuki K. Poulose }
3622d888f48SSuzuki K. Poulose
3632d888f48SSuzuki K. Poulose trace_instruction_emulation(insn, regs->pc);
3642d888f48SSuzuki K. Poulose pr_warn_ratelimited("\"%s\" (%ld) uses deprecated setend instruction at 0x%llx\n",
3652d888f48SSuzuki K. Poulose current->comm, (unsigned long)current->pid, regs->pc);
3662d888f48SSuzuki K. Poulose
3672d888f48SSuzuki K. Poulose return 0;
3682d888f48SSuzuki K. Poulose }
3692d888f48SSuzuki K. Poulose
a32_setend_handler(struct pt_regs * regs,u32 instr)3702d888f48SSuzuki K. Poulose static int a32_setend_handler(struct pt_regs *regs, u32 instr)
3712d888f48SSuzuki K. Poulose {
3722d888f48SSuzuki K. Poulose int rc = compat_setend_handler(regs, (instr >> 9) & 1);
3736436beeeSJulien Thierry arm64_skip_faulting_instruction(regs, 4);
3742d888f48SSuzuki K. Poulose return rc;
3752d888f48SSuzuki K. Poulose }
3762d888f48SSuzuki K. Poulose
t16_setend_handler(struct pt_regs * regs,u32 instr)3772d888f48SSuzuki K. Poulose static int t16_setend_handler(struct pt_regs *regs, u32 instr)
3782d888f48SSuzuki K. Poulose {
3792d888f48SSuzuki K. Poulose int rc = compat_setend_handler(regs, (instr >> 3) & 1);
3806436beeeSJulien Thierry arm64_skip_faulting_instruction(regs, 2);
3812d888f48SSuzuki K. Poulose return rc;
3822d888f48SSuzuki K. Poulose }
3832d888f48SSuzuki K. Poulose
try_emulate_setend(struct pt_regs * regs,u32 insn)384124c49b1SMark Rutland static bool try_emulate_setend(struct pt_regs *regs, u32 insn)
3852d888f48SSuzuki K. Poulose {
386124c49b1SMark Rutland if (compat_thumb_mode(regs) &&
387124c49b1SMark Rutland (insn & 0xfffffff7) == 0x0000b650)
388124c49b1SMark Rutland return t16_setend_handler(regs, insn) == 0;
389124c49b1SMark Rutland
390124c49b1SMark Rutland if (compat_user_mode(regs) &&
391124c49b1SMark Rutland (insn & 0xfffffdff) == 0xf1010000)
392124c49b1SMark Rutland return a32_setend_handler(regs, insn) == 0;
393124c49b1SMark Rutland
394124c49b1SMark Rutland return false;
395124c49b1SMark Rutland }
3962d888f48SSuzuki K. Poulose
397b4453cc8SMark Rutland static struct insn_emulation insn_setend = {
3982d888f48SSuzuki K. Poulose .name = "setend",
3992d888f48SSuzuki K. Poulose .status = INSN_DEPRECATED,
400124c49b1SMark Rutland .try_emulate = try_emulate_setend,
4012d888f48SSuzuki K. Poulose .set_hw_mode = setend_set_hw_mode,
4022d888f48SSuzuki K. Poulose };
403124c49b1SMark Rutland #endif /* CONFIG_SETEND_EMULATION */
4042d888f48SSuzuki K. Poulose
405124c49b1SMark Rutland static struct insn_emulation *insn_emulations[] = {
406124c49b1SMark Rutland #ifdef CONFIG_SWP_EMULATION
407124c49b1SMark Rutland &insn_swp,
408124c49b1SMark Rutland #endif
409124c49b1SMark Rutland #ifdef CONFIG_CP15_BARRIER_EMULATION
410124c49b1SMark Rutland &insn_cp15_barrier,
411124c49b1SMark Rutland #endif
412124c49b1SMark Rutland #ifdef CONFIG_SETEND_EMULATION
413124c49b1SMark Rutland &insn_setend,
414124c49b1SMark Rutland #endif
415124c49b1SMark Rutland };
416124c49b1SMark Rutland
41725eeac0cSMark Rutland static DEFINE_MUTEX(insn_emulation_mutex);
41825eeac0cSMark Rutland
enable_insn_hw_mode(void * data)41925eeac0cSMark Rutland static void enable_insn_hw_mode(void *data)
42025eeac0cSMark Rutland {
4210e2cb49eSYu Zhe struct insn_emulation *insn = data;
42225eeac0cSMark Rutland if (insn->set_hw_mode)
42325eeac0cSMark Rutland insn->set_hw_mode(true);
42425eeac0cSMark Rutland }
42525eeac0cSMark Rutland
disable_insn_hw_mode(void * data)42625eeac0cSMark Rutland static void disable_insn_hw_mode(void *data)
42725eeac0cSMark Rutland {
4280e2cb49eSYu Zhe struct insn_emulation *insn = data;
42925eeac0cSMark Rutland if (insn->set_hw_mode)
43025eeac0cSMark Rutland insn->set_hw_mode(false);
43125eeac0cSMark Rutland }
43225eeac0cSMark Rutland
43325eeac0cSMark Rutland /* Run set_hw_mode(mode) on all active CPUs */
run_all_cpu_set_hw_mode(struct insn_emulation * insn,bool enable)43425eeac0cSMark Rutland static int run_all_cpu_set_hw_mode(struct insn_emulation *insn, bool enable)
43525eeac0cSMark Rutland {
43625eeac0cSMark Rutland if (!insn->set_hw_mode)
43725eeac0cSMark Rutland return -EINVAL;
43825eeac0cSMark Rutland if (enable)
43925eeac0cSMark Rutland on_each_cpu(enable_insn_hw_mode, (void *)insn, true);
44025eeac0cSMark Rutland else
44125eeac0cSMark Rutland on_each_cpu(disable_insn_hw_mode, (void *)insn, true);
44225eeac0cSMark Rutland return 0;
44325eeac0cSMark Rutland }
44425eeac0cSMark Rutland
44525eeac0cSMark Rutland /*
44625eeac0cSMark Rutland * Run set_hw_mode for all insns on a starting CPU.
44725eeac0cSMark Rutland * Returns:
44825eeac0cSMark Rutland * 0 - If all the hooks ran successfully.
44925eeac0cSMark Rutland * -EINVAL - At least one hook is not supported by the CPU.
45025eeac0cSMark Rutland */
run_all_insn_set_hw_mode(unsigned int cpu)45125eeac0cSMark Rutland static int run_all_insn_set_hw_mode(unsigned int cpu)
45225eeac0cSMark Rutland {
45325eeac0cSMark Rutland int rc = 0;
45425eeac0cSMark Rutland unsigned long flags;
45525eeac0cSMark Rutland
456124c49b1SMark Rutland /*
457124c49b1SMark Rutland * Disable IRQs to serialize against an IPI from
458124c49b1SMark Rutland * run_all_cpu_set_hw_mode(), ensuring the HW is programmed to the most
459124c49b1SMark Rutland * recent enablement state if the two race with one another.
460124c49b1SMark Rutland */
461124c49b1SMark Rutland local_irq_save(flags);
462124c49b1SMark Rutland for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) {
463124c49b1SMark Rutland struct insn_emulation *insn = insn_emulations[i];
464124c49b1SMark Rutland bool enable = READ_ONCE(insn->current_mode) == INSN_HW;
46514951beaSWei Li if (insn->status == INSN_UNAVAILABLE)
46614951beaSWei Li continue;
46714951beaSWei Li
46825eeac0cSMark Rutland if (insn->set_hw_mode && insn->set_hw_mode(enable)) {
46925eeac0cSMark Rutland pr_warn("CPU[%u] cannot support the emulation of %s",
47025eeac0cSMark Rutland cpu, insn->name);
47125eeac0cSMark Rutland rc = -EINVAL;
47225eeac0cSMark Rutland }
47325eeac0cSMark Rutland }
474124c49b1SMark Rutland local_irq_restore(flags);
475124c49b1SMark Rutland
47625eeac0cSMark Rutland return rc;
47725eeac0cSMark Rutland }
47825eeac0cSMark Rutland
update_insn_emulation_mode(struct insn_emulation * insn,enum insn_emulation_mode prev)47925eeac0cSMark Rutland static int update_insn_emulation_mode(struct insn_emulation *insn,
48025eeac0cSMark Rutland enum insn_emulation_mode prev)
48125eeac0cSMark Rutland {
48225eeac0cSMark Rutland int ret = 0;
48325eeac0cSMark Rutland
48425eeac0cSMark Rutland switch (prev) {
48525eeac0cSMark Rutland case INSN_UNDEF: /* Nothing to be done */
48625eeac0cSMark Rutland break;
48725eeac0cSMark Rutland case INSN_EMULATE:
48825eeac0cSMark Rutland break;
48925eeac0cSMark Rutland case INSN_HW:
49025eeac0cSMark Rutland if (!run_all_cpu_set_hw_mode(insn, false))
49125eeac0cSMark Rutland pr_notice("Disabled %s support\n", insn->name);
49225eeac0cSMark Rutland break;
49325eeac0cSMark Rutland }
49425eeac0cSMark Rutland
49525eeac0cSMark Rutland switch (insn->current_mode) {
49625eeac0cSMark Rutland case INSN_UNDEF:
49725eeac0cSMark Rutland break;
49825eeac0cSMark Rutland case INSN_EMULATE:
49925eeac0cSMark Rutland break;
50025eeac0cSMark Rutland case INSN_HW:
50125eeac0cSMark Rutland ret = run_all_cpu_set_hw_mode(insn, true);
50225eeac0cSMark Rutland if (!ret)
50325eeac0cSMark Rutland pr_notice("Enabled %s support\n", insn->name);
50425eeac0cSMark Rutland break;
50525eeac0cSMark Rutland }
50625eeac0cSMark Rutland
50725eeac0cSMark Rutland return ret;
50825eeac0cSMark Rutland }
50925eeac0cSMark Rutland
emulation_proc_handler(const struct ctl_table * table,int write,void * buffer,size_t * lenp,loff_t * ppos)510*78eb4ea2SJoel Granados static int emulation_proc_handler(const struct ctl_table *table, int write,
51125eeac0cSMark Rutland void *buffer, size_t *lenp,
51225eeac0cSMark Rutland loff_t *ppos)
51325eeac0cSMark Rutland {
51425eeac0cSMark Rutland int ret = 0;
51525eeac0cSMark Rutland struct insn_emulation *insn = container_of(table->data, struct insn_emulation, current_mode);
51625eeac0cSMark Rutland enum insn_emulation_mode prev_mode = insn->current_mode;
51725eeac0cSMark Rutland
51825eeac0cSMark Rutland mutex_lock(&insn_emulation_mutex);
51925eeac0cSMark Rutland ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
52025eeac0cSMark Rutland
52125eeac0cSMark Rutland if (ret || !write || prev_mode == insn->current_mode)
52225eeac0cSMark Rutland goto ret;
52325eeac0cSMark Rutland
52425eeac0cSMark Rutland ret = update_insn_emulation_mode(insn, prev_mode);
52525eeac0cSMark Rutland if (ret) {
52625eeac0cSMark Rutland /* Mode change failed, revert to previous mode. */
527124c49b1SMark Rutland WRITE_ONCE(insn->current_mode, prev_mode);
52825eeac0cSMark Rutland update_insn_emulation_mode(insn, INSN_UNDEF);
52925eeac0cSMark Rutland }
53025eeac0cSMark Rutland ret:
53125eeac0cSMark Rutland mutex_unlock(&insn_emulation_mutex);
53225eeac0cSMark Rutland return ret;
53325eeac0cSMark Rutland }
53425eeac0cSMark Rutland
register_insn_emulation(struct insn_emulation * insn)535124c49b1SMark Rutland static void __init register_insn_emulation(struct insn_emulation *insn)
53625eeac0cSMark Rutland {
537124c49b1SMark Rutland struct ctl_table *sysctl;
53825eeac0cSMark Rutland
539124c49b1SMark Rutland insn->min = INSN_UNDEF;
54025eeac0cSMark Rutland
541124c49b1SMark Rutland switch (insn->status) {
542124c49b1SMark Rutland case INSN_DEPRECATED:
543124c49b1SMark Rutland insn->current_mode = INSN_EMULATE;
544124c49b1SMark Rutland /* Disable the HW mode if it was turned on at early boot time */
545124c49b1SMark Rutland run_all_cpu_set_hw_mode(insn, false);
546124c49b1SMark Rutland insn->max = INSN_HW;
547124c49b1SMark Rutland break;
548124c49b1SMark Rutland case INSN_OBSOLETE:
549124c49b1SMark Rutland insn->current_mode = INSN_UNDEF;
550124c49b1SMark Rutland insn->max = INSN_EMULATE;
551124c49b1SMark Rutland break;
552124c49b1SMark Rutland case INSN_UNAVAILABLE:
553124c49b1SMark Rutland insn->current_mode = INSN_UNDEF;
554124c49b1SMark Rutland insn->max = INSN_UNDEF;
555124c49b1SMark Rutland break;
556124c49b1SMark Rutland }
557124c49b1SMark Rutland
558124c49b1SMark Rutland /* Program the HW if required */
559124c49b1SMark Rutland update_insn_emulation_mode(insn, INSN_UNDEF);
560124c49b1SMark Rutland
561124c49b1SMark Rutland if (insn->status != INSN_UNAVAILABLE) {
562de8a660bSJoel Granados sysctl = &insn->sysctl;
56325eeac0cSMark Rutland
56425eeac0cSMark Rutland sysctl->mode = 0644;
56525eeac0cSMark Rutland sysctl->maxlen = sizeof(int);
56625eeac0cSMark Rutland
56725eeac0cSMark Rutland sysctl->procname = insn->name;
56825eeac0cSMark Rutland sysctl->data = &insn->current_mode;
56925eeac0cSMark Rutland sysctl->extra1 = &insn->min;
57025eeac0cSMark Rutland sysctl->extra2 = &insn->max;
57125eeac0cSMark Rutland sysctl->proc_handler = emulation_proc_handler;
57225eeac0cSMark Rutland
5739edbfe92SJoel Granados register_sysctl_sz("abi", sysctl, 1);
574124c49b1SMark Rutland }
575124c49b1SMark Rutland }
576124c49b1SMark Rutland
try_emulate_armv8_deprecated(struct pt_regs * regs,u32 insn)577124c49b1SMark Rutland bool try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn)
578124c49b1SMark Rutland {
579124c49b1SMark Rutland for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) {
580124c49b1SMark Rutland struct insn_emulation *ie = insn_emulations[i];
581124c49b1SMark Rutland
582124c49b1SMark Rutland if (ie->status == INSN_UNAVAILABLE)
583124c49b1SMark Rutland continue;
584124c49b1SMark Rutland
585124c49b1SMark Rutland /*
586124c49b1SMark Rutland * A trap may race with the mode being changed
587124c49b1SMark Rutland * INSN_EMULATE<->INSN_HW. Try to emulate the instruction to
588124c49b1SMark Rutland * avoid a spurious UNDEF.
589124c49b1SMark Rutland */
590124c49b1SMark Rutland if (READ_ONCE(ie->current_mode) == INSN_UNDEF)
591124c49b1SMark Rutland continue;
592124c49b1SMark Rutland
593124c49b1SMark Rutland if (ie->try_emulate(regs, insn))
594124c49b1SMark Rutland return true;
595124c49b1SMark Rutland }
596124c49b1SMark Rutland
597124c49b1SMark Rutland return false;
59825eeac0cSMark Rutland }
59925eeac0cSMark Rutland
600bd35a4adSPunit Agrawal /*
60126415330SHanjun Guo * Invoked as core_initcall, which guarantees that the instruction
60226415330SHanjun Guo * emulation is ready for userspace.
603587064b6SPunit Agrawal */
armv8_deprecated_init(void)604587064b6SPunit Agrawal static int __init armv8_deprecated_init(void)
605587064b6SPunit Agrawal {
606124c49b1SMark Rutland #ifdef CONFIG_SETEND_EMULATION
607124c49b1SMark Rutland if (!system_supports_mixed_endian_el0()) {
608124c49b1SMark Rutland insn_setend.status = INSN_UNAVAILABLE;
609117f5727SMark Rutland pr_info("setend instruction emulation is not supported on this system\n");
6102d888f48SSuzuki K. Poulose }
6112d888f48SSuzuki K. Poulose
612124c49b1SMark Rutland #endif
613124c49b1SMark Rutland for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) {
614124c49b1SMark Rutland struct insn_emulation *ie = insn_emulations[i];
615124c49b1SMark Rutland
616124c49b1SMark Rutland if (ie->status == INSN_UNAVAILABLE)
617124c49b1SMark Rutland continue;
618124c49b1SMark Rutland
619124c49b1SMark Rutland register_insn_emulation(ie);
620124c49b1SMark Rutland }
621124c49b1SMark Rutland
62227c01a8cSSebastian Andrzej Siewior cpuhp_setup_state_nocalls(CPUHP_AP_ARM64_ISNDEP_STARTING,
62373c1b41eSThomas Gleixner "arm64/isndep:starting",
62427c01a8cSSebastian Andrzej Siewior run_all_insn_set_hw_mode, NULL);
625587064b6SPunit Agrawal return 0;
626587064b6SPunit Agrawal }
627587064b6SPunit Agrawal
628c0d8832eSSuzuki K Poulose core_initcall(armv8_deprecated_init);
629