xref: /linux/arch/riscv/kernel/traps_misaligned.c (revision ab93e0dd72c37d378dd936f031ffb83ff2bd87ce)
1956d705dSDamien Le Moal // SPDX-License-Identifier: GPL-2.0-only
2956d705dSDamien Le Moal /*
3956d705dSDamien Le Moal  * Copyright (C) 2020 Western Digital Corporation or its affiliates.
4956d705dSDamien Le Moal  */
5956d705dSDamien Le Moal #include <linux/kernel.h>
6956d705dSDamien Le Moal #include <linux/init.h>
7956d705dSDamien Le Moal #include <linux/mm.h>
8956d705dSDamien Le Moal #include <linux/module.h>
989c12fecSClément Léger #include <linux/perf_event.h>
10956d705dSDamien Le Moal #include <linux/irq.h>
1176ad33e1SKrzysztof Kozlowski #include <linux/stringify.h>
12956d705dSDamien Le Moal 
13956d705dSDamien Le Moal #include <asm/processor.h>
14956d705dSDamien Le Moal #include <asm/ptrace.h>
15956d705dSDamien Le Moal #include <asm/csr.h>
167c832321SClément Léger #include <asm/entry-common.h>
1771c54b3dSClément Léger #include <asm/hwprobe.h>
1871c54b3dSClément Léger #include <asm/cpufeature.h>
19cf5a8abcSClément Léger #include <asm/sbi.h>
20d1703dc7SJesse Taube #include <asm/vector.h>
21956d705dSDamien Le Moal 
22956d705dSDamien Le Moal #define INSN_MATCH_LB			0x3
23956d705dSDamien Le Moal #define INSN_MASK_LB			0x707f
24956d705dSDamien Le Moal #define INSN_MATCH_LH			0x1003
25956d705dSDamien Le Moal #define INSN_MASK_LH			0x707f
26956d705dSDamien Le Moal #define INSN_MATCH_LW			0x2003
27956d705dSDamien Le Moal #define INSN_MASK_LW			0x707f
28956d705dSDamien Le Moal #define INSN_MATCH_LD			0x3003
29956d705dSDamien Le Moal #define INSN_MASK_LD			0x707f
30956d705dSDamien Le Moal #define INSN_MATCH_LBU			0x4003
31956d705dSDamien Le Moal #define INSN_MASK_LBU			0x707f
32956d705dSDamien Le Moal #define INSN_MATCH_LHU			0x5003
33956d705dSDamien Le Moal #define INSN_MASK_LHU			0x707f
34956d705dSDamien Le Moal #define INSN_MATCH_LWU			0x6003
35956d705dSDamien Le Moal #define INSN_MASK_LWU			0x707f
36956d705dSDamien Le Moal #define INSN_MATCH_SB			0x23
37956d705dSDamien Le Moal #define INSN_MASK_SB			0x707f
38956d705dSDamien Le Moal #define INSN_MATCH_SH			0x1023
39956d705dSDamien Le Moal #define INSN_MASK_SH			0x707f
40956d705dSDamien Le Moal #define INSN_MATCH_SW			0x2023
41956d705dSDamien Le Moal #define INSN_MASK_SW			0x707f
42956d705dSDamien Le Moal #define INSN_MATCH_SD			0x3023
43956d705dSDamien Le Moal #define INSN_MASK_SD			0x707f
44956d705dSDamien Le Moal 
45956d705dSDamien Le Moal #define INSN_MATCH_FLW			0x2007
46956d705dSDamien Le Moal #define INSN_MASK_FLW			0x707f
47956d705dSDamien Le Moal #define INSN_MATCH_FLD			0x3007
48956d705dSDamien Le Moal #define INSN_MASK_FLD			0x707f
49956d705dSDamien Le Moal #define INSN_MATCH_FLQ			0x4007
50956d705dSDamien Le Moal #define INSN_MASK_FLQ			0x707f
51956d705dSDamien Le Moal #define INSN_MATCH_FSW			0x2027
52956d705dSDamien Le Moal #define INSN_MASK_FSW			0x707f
53956d705dSDamien Le Moal #define INSN_MATCH_FSD			0x3027
54956d705dSDamien Le Moal #define INSN_MASK_FSD			0x707f
55956d705dSDamien Le Moal #define INSN_MATCH_FSQ			0x4027
56956d705dSDamien Le Moal #define INSN_MASK_FSQ			0x707f
57956d705dSDamien Le Moal 
58956d705dSDamien Le Moal #define INSN_MATCH_C_LD			0x6000
59956d705dSDamien Le Moal #define INSN_MASK_C_LD			0xe003
60956d705dSDamien Le Moal #define INSN_MATCH_C_SD			0xe000
61956d705dSDamien Le Moal #define INSN_MASK_C_SD			0xe003
62956d705dSDamien Le Moal #define INSN_MATCH_C_LW			0x4000
63956d705dSDamien Le Moal #define INSN_MASK_C_LW			0xe003
64956d705dSDamien Le Moal #define INSN_MATCH_C_SW			0xc000
65956d705dSDamien Le Moal #define INSN_MASK_C_SW			0xe003
66956d705dSDamien Le Moal #define INSN_MATCH_C_LDSP		0x6002
67956d705dSDamien Le Moal #define INSN_MASK_C_LDSP		0xe003
68956d705dSDamien Le Moal #define INSN_MATCH_C_SDSP		0xe002
69956d705dSDamien Le Moal #define INSN_MASK_C_SDSP		0xe003
70956d705dSDamien Le Moal #define INSN_MATCH_C_LWSP		0x4002
71956d705dSDamien Le Moal #define INSN_MASK_C_LWSP		0xe003
72956d705dSDamien Le Moal #define INSN_MATCH_C_SWSP		0xc002
73956d705dSDamien Le Moal #define INSN_MASK_C_SWSP		0xe003
74956d705dSDamien Le Moal 
75956d705dSDamien Le Moal #define INSN_MATCH_C_FLD		0x2000
76956d705dSDamien Le Moal #define INSN_MASK_C_FLD			0xe003
77956d705dSDamien Le Moal #define INSN_MATCH_C_FLW		0x6000
78956d705dSDamien Le Moal #define INSN_MASK_C_FLW			0xe003
79956d705dSDamien Le Moal #define INSN_MATCH_C_FSD		0xa000
80956d705dSDamien Le Moal #define INSN_MASK_C_FSD			0xe003
81956d705dSDamien Le Moal #define INSN_MATCH_C_FSW		0xe000
82956d705dSDamien Le Moal #define INSN_MASK_C_FSW			0xe003
83956d705dSDamien Le Moal #define INSN_MATCH_C_FLDSP		0x2002
84956d705dSDamien Le Moal #define INSN_MASK_C_FLDSP		0xe003
85956d705dSDamien Le Moal #define INSN_MATCH_C_FSDSP		0xa002
86956d705dSDamien Le Moal #define INSN_MASK_C_FSDSP		0xe003
87956d705dSDamien Le Moal #define INSN_MATCH_C_FLWSP		0x6002
88956d705dSDamien Le Moal #define INSN_MASK_C_FLWSP		0xe003
89956d705dSDamien Le Moal #define INSN_MATCH_C_FSWSP		0xe002
90956d705dSDamien Le Moal #define INSN_MASK_C_FSWSP		0xe003
91956d705dSDamien Le Moal 
927b30b1b0SNylon Chen #define INSN_MATCH_C_LHU		0x8400
937b30b1b0SNylon Chen #define INSN_MASK_C_LHU			0xfc43
947b30b1b0SNylon Chen #define INSN_MATCH_C_LH			0x8440
957b30b1b0SNylon Chen #define INSN_MASK_C_LH			0xfc43
967b30b1b0SNylon Chen #define INSN_MATCH_C_SH			0x8c00
977b30b1b0SNylon Chen #define INSN_MASK_C_SH			0xfc43
987b30b1b0SNylon Chen 
99956d705dSDamien Le Moal #define INSN_LEN(insn)			((((insn) & 0x3) < 0x3) ? 2 : 4)
100956d705dSDamien Le Moal 
101956d705dSDamien Le Moal #if defined(CONFIG_64BIT)
102956d705dSDamien Le Moal #define LOG_REGBYTES			3
103956d705dSDamien Le Moal #define XLEN				64
104956d705dSDamien Le Moal #else
105956d705dSDamien Le Moal #define LOG_REGBYTES			2
106956d705dSDamien Le Moal #define XLEN				32
107956d705dSDamien Le Moal #endif
108956d705dSDamien Le Moal #define REGBYTES			(1 << LOG_REGBYTES)
109956d705dSDamien Le Moal #define XLEN_MINUS_16			((XLEN) - 16)
110956d705dSDamien Le Moal 
111956d705dSDamien Le Moal #define SH_RD				7
112956d705dSDamien Le Moal #define SH_RS1				15
113956d705dSDamien Le Moal #define SH_RS2				20
114956d705dSDamien Le Moal #define SH_RS2C				2
115956d705dSDamien Le Moal 
116956d705dSDamien Le Moal #define RV_X(x, s, n)			(((x) >> (s)) & ((1 << (n)) - 1))
117956d705dSDamien Le Moal #define RVC_LW_IMM(x)			((RV_X(x, 6, 1) << 2) | \
118956d705dSDamien Le Moal 					 (RV_X(x, 10, 3) << 3) | \
119956d705dSDamien Le Moal 					 (RV_X(x, 5, 1) << 6))
120956d705dSDamien Le Moal #define RVC_LD_IMM(x)			((RV_X(x, 10, 3) << 3) | \
121956d705dSDamien Le Moal 					 (RV_X(x, 5, 2) << 6))
122956d705dSDamien Le Moal #define RVC_LWSP_IMM(x)			((RV_X(x, 4, 3) << 2) | \
123956d705dSDamien Le Moal 					 (RV_X(x, 12, 1) << 5) | \
124956d705dSDamien Le Moal 					 (RV_X(x, 2, 2) << 6))
125956d705dSDamien Le Moal #define RVC_LDSP_IMM(x)			((RV_X(x, 5, 2) << 3) | \
126956d705dSDamien Le Moal 					 (RV_X(x, 12, 1) << 5) | \
127956d705dSDamien Le Moal 					 (RV_X(x, 2, 3) << 6))
128956d705dSDamien Le Moal #define RVC_SWSP_IMM(x)			((RV_X(x, 9, 4) << 2) | \
129956d705dSDamien Le Moal 					 (RV_X(x, 7, 2) << 6))
130956d705dSDamien Le Moal #define RVC_SDSP_IMM(x)			((RV_X(x, 10, 3) << 3) | \
131956d705dSDamien Le Moal 					 (RV_X(x, 7, 3) << 6))
132956d705dSDamien Le Moal #define RVC_RS1S(insn)			(8 + RV_X(insn, SH_RD, 3))
133956d705dSDamien Le Moal #define RVC_RS2S(insn)			(8 + RV_X(insn, SH_RS2C, 3))
134956d705dSDamien Le Moal #define RVC_RS2(insn)			RV_X(insn, SH_RS2C, 5)
135956d705dSDamien Le Moal 
136956d705dSDamien Le Moal #define SHIFT_RIGHT(x, y)		\
137956d705dSDamien Le Moal 	((y) < 0 ? ((x) << -(y)) : ((x) >> (y)))
138956d705dSDamien Le Moal 
139956d705dSDamien Le Moal #define REG_MASK			\
140956d705dSDamien Le Moal 	((1 << (5 + LOG_REGBYTES)) - (1 << LOG_REGBYTES))
141956d705dSDamien Le Moal 
142956d705dSDamien Le Moal #define REG_OFFSET(insn, pos)		\
143956d705dSDamien Le Moal 	(SHIFT_RIGHT((insn), (pos) - LOG_REGBYTES) & REG_MASK)
144956d705dSDamien Le Moal 
145956d705dSDamien Le Moal #define REG_PTR(insn, pos, regs)	\
146956d705dSDamien Le Moal 	(ulong *)((ulong)(regs) + REG_OFFSET(insn, pos))
147956d705dSDamien Le Moal 
148956d705dSDamien Le Moal #define GET_RS1(insn, regs)		(*REG_PTR(insn, SH_RS1, regs))
149956d705dSDamien Le Moal #define GET_RS2(insn, regs)		(*REG_PTR(insn, SH_RS2, regs))
150956d705dSDamien Le Moal #define GET_RS1S(insn, regs)		(*REG_PTR(RVC_RS1S(insn), 0, regs))
151956d705dSDamien Le Moal #define GET_RS2S(insn, regs)		(*REG_PTR(RVC_RS2S(insn), 0, regs))
152956d705dSDamien Le Moal #define GET_RS2C(insn, regs)		(*REG_PTR(insn, SH_RS2C, regs))
153956d705dSDamien Le Moal #define GET_SP(regs)			(*REG_PTR(2, 0, regs))
154956d705dSDamien Le Moal #define SET_RD(insn, regs, val)		(*REG_PTR(insn, SH_RD, regs) = (val))
155956d705dSDamien Le Moal #define IMM_I(insn)			((s32)(insn) >> 20)
156956d705dSDamien Le Moal #define IMM_S(insn)			(((s32)(insn) >> 25 << 5) | \
157956d705dSDamien Le Moal 					 (s32)(((insn) >> 7) & 0x1f))
158956d705dSDamien Le Moal #define MASK_FUNCT3			0x7000
159956d705dSDamien Le Moal 
160956d705dSDamien Le Moal #define GET_PRECISION(insn) (((insn) >> 25) & 3)
161956d705dSDamien Le Moal #define GET_RM(insn) (((insn) >> 12) & 7)
162956d705dSDamien Le Moal #define PRECISION_S 0
163956d705dSDamien Le Moal #define PRECISION_D 1
164956d705dSDamien Le Moal 
1657c586a55SClément Léger #ifdef CONFIG_FPU
1667c586a55SClément Léger 
1677c586a55SClément Léger #define FP_GET_RD(insn)		(insn >> 7 & 0x1F)
1687c586a55SClément Léger 
1697c586a55SClément Léger extern void put_f32_reg(unsigned long fp_reg, unsigned long value);
1707c586a55SClément Léger 
set_f32_rd(unsigned long insn,struct pt_regs * regs,unsigned long val)1717c586a55SClément Léger static int set_f32_rd(unsigned long insn, struct pt_regs *regs,
1727c586a55SClément Léger 		      unsigned long val)
1737c586a55SClément Léger {
1747c586a55SClément Léger 	unsigned long fp_reg = FP_GET_RD(insn);
1757c586a55SClément Léger 
1767c586a55SClément Léger 	put_f32_reg(fp_reg, val);
1777c586a55SClément Léger 	regs->status |= SR_FS_DIRTY;
1787c586a55SClément Léger 
1797c586a55SClément Léger 	return 0;
1807c586a55SClément Léger }
1817c586a55SClément Léger 
1827c586a55SClément Léger extern void put_f64_reg(unsigned long fp_reg, unsigned long value);
1837c586a55SClément Léger 
set_f64_rd(unsigned long insn,struct pt_regs * regs,u64 val)1847c586a55SClément Léger static int set_f64_rd(unsigned long insn, struct pt_regs *regs, u64 val)
1857c586a55SClément Léger {
1867c586a55SClément Léger 	unsigned long fp_reg = FP_GET_RD(insn);
1877c586a55SClément Léger 	unsigned long value;
1887c586a55SClément Léger 
1897c586a55SClément Léger #if __riscv_xlen == 32
1907c586a55SClément Léger 	value = (unsigned long) &val;
1917c586a55SClément Léger #else
1927c586a55SClément Léger 	value = val;
1937c586a55SClément Léger #endif
1947c586a55SClément Léger 	put_f64_reg(fp_reg, value);
1957c586a55SClément Léger 	regs->status |= SR_FS_DIRTY;
1967c586a55SClément Léger 
1977c586a55SClément Léger 	return 0;
1987c586a55SClément Léger }
1997c586a55SClément Léger 
2007c586a55SClément Léger #if __riscv_xlen == 32
2017c586a55SClément Léger extern void get_f64_reg(unsigned long fp_reg, u64 *value);
2027c586a55SClément Léger 
get_f64_rs(unsigned long insn,u8 fp_reg_offset,struct pt_regs * regs)2037c586a55SClément Léger static u64 get_f64_rs(unsigned long insn, u8 fp_reg_offset,
2047c586a55SClément Léger 		      struct pt_regs *regs)
2057c586a55SClément Léger {
2067c586a55SClément Léger 	unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F;
2077c586a55SClément Léger 	u64 val;
2087c586a55SClément Léger 
2097c586a55SClément Léger 	get_f64_reg(fp_reg, &val);
2107c586a55SClément Léger 	regs->status |= SR_FS_DIRTY;
2117c586a55SClément Léger 
2127c586a55SClément Léger 	return val;
2137c586a55SClément Léger }
2147c586a55SClément Léger #else
2157c586a55SClément Léger 
2167c586a55SClément Léger extern unsigned long get_f64_reg(unsigned long fp_reg);
2177c586a55SClément Léger 
get_f64_rs(unsigned long insn,u8 fp_reg_offset,struct pt_regs * regs)2187c586a55SClément Léger static unsigned long get_f64_rs(unsigned long insn, u8 fp_reg_offset,
2197c586a55SClément Léger 				struct pt_regs *regs)
2207c586a55SClément Léger {
2217c586a55SClément Léger 	unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F;
2227c586a55SClément Léger 	unsigned long val;
2237c586a55SClément Léger 
2247c586a55SClément Léger 	val = get_f64_reg(fp_reg);
2257c586a55SClément Léger 	regs->status |= SR_FS_DIRTY;
2267c586a55SClément Léger 
2277c586a55SClément Léger 	return val;
2287c586a55SClément Léger }
2297c586a55SClément Léger 
2307c586a55SClément Léger #endif
2317c586a55SClément Léger 
2327c586a55SClément Léger extern unsigned long get_f32_reg(unsigned long fp_reg);
2337c586a55SClément Léger 
get_f32_rs(unsigned long insn,u8 fp_reg_offset,struct pt_regs * regs)2347c586a55SClément Léger static unsigned long get_f32_rs(unsigned long insn, u8 fp_reg_offset,
2357c586a55SClément Léger 				struct pt_regs *regs)
2367c586a55SClément Léger {
2377c586a55SClément Léger 	unsigned long fp_reg = (insn >> fp_reg_offset) & 0x1F;
2387c586a55SClément Léger 	unsigned long val;
2397c586a55SClément Léger 
2407c586a55SClément Léger 	val = get_f32_reg(fp_reg);
2417c586a55SClément Léger 	regs->status |= SR_FS_DIRTY;
2427c586a55SClément Léger 
2437c586a55SClément Léger 	return val;
2447c586a55SClément Léger }
2457c586a55SClément Léger 
2467c586a55SClément Léger #else /* CONFIG_FPU */
set_f32_rd(unsigned long insn,struct pt_regs * regs,unsigned long val)2477c586a55SClément Léger static void set_f32_rd(unsigned long insn, struct pt_regs *regs,
2487c586a55SClément Léger 		       unsigned long val) {}
2497c586a55SClément Léger 
set_f64_rd(unsigned long insn,struct pt_regs * regs,u64 val)2507c586a55SClément Léger static void set_f64_rd(unsigned long insn, struct pt_regs *regs, u64 val) {}
2517c586a55SClément Léger 
get_f64_rs(unsigned long insn,u8 fp_reg_offset,struct pt_regs * regs)2527c586a55SClément Léger static unsigned long get_f64_rs(unsigned long insn, u8 fp_reg_offset,
2537c586a55SClément Léger 				struct pt_regs *regs)
2547c586a55SClément Léger {
2557c586a55SClément Léger 	return 0;
2567c586a55SClément Léger }
2577c586a55SClément Léger 
get_f32_rs(unsigned long insn,u8 fp_reg_offset,struct pt_regs * regs)2587c586a55SClément Léger static unsigned long get_f32_rs(unsigned long insn, u8 fp_reg_offset,
2597c586a55SClément Léger 				struct pt_regs *regs)
2607c586a55SClément Léger {
2617c586a55SClément Léger 	return 0;
2627c586a55SClément Léger }
2637c586a55SClément Léger 
2647c586a55SClément Léger #endif
2657c586a55SClément Léger 
2667c586a55SClément Léger #define GET_F64_RS2(insn, regs) (get_f64_rs(insn, 20, regs))
2677c586a55SClément Léger #define GET_F64_RS2C(insn, regs) (get_f64_rs(insn, 2, regs))
2687c586a55SClément Léger #define GET_F64_RS2S(insn, regs) (get_f64_rs(RVC_RS2S(insn), 0, regs))
2697c586a55SClément Léger 
2707c586a55SClément Léger #define GET_F32_RS2(insn, regs) (get_f32_rs(insn, 20, regs))
2717c586a55SClément Léger #define GET_F32_RS2C(insn, regs) (get_f32_rs(insn, 2, regs))
2727c586a55SClément Léger #define GET_F32_RS2S(insn, regs) (get_f32_rs(RVC_RS2S(insn), 0, regs))
2737c586a55SClément Léger 
27444138150SClément Léger #define __read_insn(regs, insn, insn_addr, type)	\
2757c832321SClément Léger ({							\
2767c832321SClément Léger 	int __ret;					\
2777c832321SClément Léger 							\
2787c832321SClément Léger 	if (user_mode(regs)) {				\
279897e8aecSClément Léger 		__ret = get_user(insn, (type __user *) insn_addr); \
2807c832321SClément Léger 	} else {					\
28144138150SClément Léger 		insn = *(type *)insn_addr;		\
2827c832321SClément Léger 		__ret = 0;				\
2837c832321SClément Léger 	}						\
2847c832321SClément Léger 							\
2857c832321SClément Léger 	__ret;						\
2867c832321SClément Léger })
2877c832321SClément Léger 
get_insn(struct pt_regs * regs,ulong epc,ulong * r_insn)2887c832321SClément Léger static inline int get_insn(struct pt_regs *regs, ulong epc, ulong *r_insn)
2897c832321SClément Léger {
2907c832321SClément Léger 	ulong insn = 0;
2917c832321SClément Léger 
2927c832321SClément Léger 	if (epc & 0x2) {
2937c832321SClément Léger 		ulong tmp = 0;
2947c832321SClément Léger 
29544138150SClément Léger 		if (__read_insn(regs, insn, epc, u16))
2967c832321SClément Léger 			return -EFAULT;
2977c832321SClément Léger 		/* __get_user() uses regular "lw" which sign extend the loaded
2987c832321SClément Léger 		 * value make sure to clear higher order bits in case we "or" it
2997c832321SClément Léger 		 * below with the upper 16 bits half.
3007c832321SClément Léger 		 */
3017c832321SClément Léger 		insn &= GENMASK(15, 0);
3027c832321SClément Léger 		if ((insn & __INSN_LENGTH_MASK) != __INSN_LENGTH_32) {
3037c832321SClément Léger 			*r_insn = insn;
3047c832321SClément Léger 			return 0;
3057c832321SClément Léger 		}
30644138150SClément Léger 		epc += sizeof(u16);
30744138150SClément Léger 		if (__read_insn(regs, tmp, epc, u16))
3087c832321SClément Léger 			return -EFAULT;
3097c832321SClément Léger 		*r_insn = (tmp << 16) | insn;
3107c832321SClément Léger 
3117c832321SClément Léger 		return 0;
3127c832321SClément Léger 	} else {
31344138150SClément Léger 		if (__read_insn(regs, insn, epc, u32))
3147c832321SClément Léger 			return -EFAULT;
3157c832321SClément Léger 		if ((insn & __INSN_LENGTH_MASK) == __INSN_LENGTH_32) {
3167c832321SClément Léger 			*r_insn = insn;
3177c832321SClément Léger 			return 0;
3187c832321SClément Léger 		}
3197c832321SClément Léger 		insn &= GENMASK(15, 0);
3207c832321SClément Léger 		*r_insn = insn;
3217c832321SClément Léger 
3227c832321SClément Léger 		return 0;
3237c832321SClément Léger 	}
3247c832321SClément Léger }
325956d705dSDamien Le Moal 
326956d705dSDamien Le Moal union reg_data {
327956d705dSDamien Le Moal 	u8 data_bytes[8];
328956d705dSDamien Le Moal 	ulong data_ulong;
329956d705dSDamien Le Moal 	u64 data_u64;
330956d705dSDamien Le Moal };
331956d705dSDamien Le Moal 
332bc38f613SClément Léger /* sysctl hooks */
333bc38f613SClément Léger int unaligned_enabled __read_mostly = 1;	/* Enabled by default */
334bc38f613SClément Léger 
335d1703dc7SJesse Taube #ifdef CONFIG_RISCV_VECTOR_MISALIGNED
handle_vector_misaligned_load(struct pt_regs * regs)336d1703dc7SJesse Taube static int handle_vector_misaligned_load(struct pt_regs *regs)
337d1703dc7SJesse Taube {
338d1703dc7SJesse Taube 	unsigned long epc = regs->epc;
339d1703dc7SJesse Taube 	unsigned long insn;
340d1703dc7SJesse Taube 
341d1703dc7SJesse Taube 	if (get_insn(regs, epc, &insn))
342d1703dc7SJesse Taube 		return -1;
343d1703dc7SJesse Taube 
344d1703dc7SJesse Taube 	/* Only return 0 when in check_vector_unaligned_access_emulated */
345d1703dc7SJesse Taube 	if (*this_cpu_ptr(&vector_misaligned_access) == RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN) {
346d1703dc7SJesse Taube 		*this_cpu_ptr(&vector_misaligned_access) = RISCV_HWPROBE_MISALIGNED_VECTOR_UNSUPPORTED;
347d1703dc7SJesse Taube 		regs->epc = epc + INSN_LEN(insn);
348d1703dc7SJesse Taube 		return 0;
349d1703dc7SJesse Taube 	}
350d1703dc7SJesse Taube 
351d1703dc7SJesse Taube 	/* If vector instruction we don't emulate it yet */
352d1703dc7SJesse Taube 	regs->epc = epc;
353d1703dc7SJesse Taube 	return -1;
354d1703dc7SJesse Taube }
355d1703dc7SJesse Taube #else
handle_vector_misaligned_load(struct pt_regs * regs)356d1703dc7SJesse Taube static int handle_vector_misaligned_load(struct pt_regs *regs)
357d1703dc7SJesse Taube {
358d1703dc7SJesse Taube 	return -1;
359d1703dc7SJesse Taube }
360d1703dc7SJesse Taube #endif
361d1703dc7SJesse Taube 
handle_scalar_misaligned_load(struct pt_regs * regs)362d1703dc7SJesse Taube static int handle_scalar_misaligned_load(struct pt_regs *regs)
363956d705dSDamien Le Moal {
364956d705dSDamien Le Moal 	union reg_data val;
365956d705dSDamien Le Moal 	unsigned long epc = regs->epc;
3667c832321SClément Léger 	unsigned long insn;
3677c832321SClément Léger 	unsigned long addr = regs->badaddr;
36844138150SClément Léger 	int fp = 0, shift = 0, len = 0;
369956d705dSDamien Le Moal 
37089c12fecSClément Léger 	perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, addr);
37189c12fecSClément Léger 
3721f528887SEvan Green 	*this_cpu_ptr(&misaligned_access_speed) = RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED;
37371c54b3dSClément Léger 
374bc38f613SClément Léger 	if (!unaligned_enabled)
375bc38f613SClément Léger 		return -1;
376bc38f613SClément Léger 
3779f23a5d2SClément Léger 	if (user_mode(regs) && (current->thread.align_ctl & PR_UNALIGN_SIGBUS))
3789f23a5d2SClément Léger 		return -1;
3799f23a5d2SClément Léger 
3807c832321SClément Léger 	if (get_insn(regs, epc, &insn))
3817c832321SClément Léger 		return -1;
3827c832321SClément Léger 
383956d705dSDamien Le Moal 	regs->epc = 0;
384956d705dSDamien Le Moal 
385956d705dSDamien Le Moal 	if ((insn & INSN_MASK_LW) == INSN_MATCH_LW) {
386956d705dSDamien Le Moal 		len = 4;
387956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
388956d705dSDamien Le Moal #if defined(CONFIG_64BIT)
389956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_LD) == INSN_MATCH_LD) {
390956d705dSDamien Le Moal 		len = 8;
391956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
392956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_LWU) == INSN_MATCH_LWU) {
393956d705dSDamien Le Moal 		len = 4;
394956d705dSDamien Le Moal #endif
395956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_FLD) == INSN_MATCH_FLD) {
396956d705dSDamien Le Moal 		fp = 1;
397956d705dSDamien Le Moal 		len = 8;
398956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_FLW) == INSN_MATCH_FLW) {
399956d705dSDamien Le Moal 		fp = 1;
400956d705dSDamien Le Moal 		len = 4;
401956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_LH) == INSN_MATCH_LH) {
402956d705dSDamien Le Moal 		len = 2;
403956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
404956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_LHU) == INSN_MATCH_LHU) {
405956d705dSDamien Le Moal 		len = 2;
406956d705dSDamien Le Moal #if defined(CONFIG_64BIT)
407956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_LD) == INSN_MATCH_C_LD) {
408956d705dSDamien Le Moal 		len = 8;
409956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
410956d705dSDamien Le Moal 		insn = RVC_RS2S(insn) << SH_RD;
411956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_LDSP) == INSN_MATCH_C_LDSP &&
412956d705dSDamien Le Moal 		   ((insn >> SH_RD) & 0x1f)) {
413956d705dSDamien Le Moal 		len = 8;
414956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
415956d705dSDamien Le Moal #endif
416956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_LW) == INSN_MATCH_C_LW) {
417956d705dSDamien Le Moal 		len = 4;
418956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
419956d705dSDamien Le Moal 		insn = RVC_RS2S(insn) << SH_RD;
420956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_LWSP) == INSN_MATCH_C_LWSP &&
421956d705dSDamien Le Moal 		   ((insn >> SH_RD) & 0x1f)) {
422956d705dSDamien Le Moal 		len = 4;
423956d705dSDamien Le Moal 		shift = 8 * (sizeof(unsigned long) - len);
424956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_FLD) == INSN_MATCH_C_FLD) {
425956d705dSDamien Le Moal 		fp = 1;
426956d705dSDamien Le Moal 		len = 8;
427956d705dSDamien Le Moal 		insn = RVC_RS2S(insn) << SH_RD;
428956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_FLDSP) == INSN_MATCH_C_FLDSP) {
429956d705dSDamien Le Moal 		fp = 1;
430956d705dSDamien Le Moal 		len = 8;
431956d705dSDamien Le Moal #if defined(CONFIG_32BIT)
432956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_FLW) == INSN_MATCH_C_FLW) {
433956d705dSDamien Le Moal 		fp = 1;
434956d705dSDamien Le Moal 		len = 4;
435956d705dSDamien Le Moal 		insn = RVC_RS2S(insn) << SH_RD;
436956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_FLWSP) == INSN_MATCH_C_FLWSP) {
437956d705dSDamien Le Moal 		fp = 1;
438956d705dSDamien Le Moal 		len = 4;
439956d705dSDamien Le Moal #endif
4407b30b1b0SNylon Chen 	} else if ((insn & INSN_MASK_C_LHU) == INSN_MATCH_C_LHU) {
4417b30b1b0SNylon Chen 		len = 2;
4427b30b1b0SNylon Chen 		insn = RVC_RS2S(insn) << SH_RD;
4437b30b1b0SNylon Chen 	} else if ((insn & INSN_MASK_C_LH) == INSN_MATCH_C_LH) {
4447b30b1b0SNylon Chen 		len = 2;
4457b30b1b0SNylon Chen 		shift = 8 * (sizeof(ulong) - len);
4467b30b1b0SNylon Chen 		insn = RVC_RS2S(insn) << SH_RD;
447956d705dSDamien Le Moal 	} else {
448956d705dSDamien Le Moal 		regs->epc = epc;
449956d705dSDamien Le Moal 		return -1;
450956d705dSDamien Le Moal 	}
451956d705dSDamien Le Moal 
4527c586a55SClément Léger 	if (!IS_ENABLED(CONFIG_FPU) && fp)
4537c586a55SClément Léger 		return -EOPNOTSUPP;
4547c586a55SClément Léger 
455956d705dSDamien Le Moal 	val.data_u64 = 0;
45644138150SClément Léger 	if (user_mode(regs)) {
45761a74ad2SNylon Chen 		if (copy_from_user(&val, (u8 __user *)addr, len))
4587c832321SClément Léger 			return -1;
45944138150SClément Léger 	} else {
46044138150SClément Léger 		memcpy(&val, (u8 *)addr, len);
4617c832321SClément Léger 	}
462956d705dSDamien Le Moal 
4637c586a55SClément Léger 	if (!fp)
464956d705dSDamien Le Moal 		SET_RD(insn, regs, (long)(val.data_ulong << shift) >> shift);
4657c586a55SClément Léger 	else if (len == 8)
4667c586a55SClément Léger 		set_f64_rd(insn, regs, val.data_u64);
4677c586a55SClément Léger 	else
4687c586a55SClément Léger 		set_f32_rd(insn, regs, val.data_ulong);
469956d705dSDamien Le Moal 
470956d705dSDamien Le Moal 	regs->epc = epc + INSN_LEN(insn);
471956d705dSDamien Le Moal 
472956d705dSDamien Le Moal 	return 0;
473956d705dSDamien Le Moal }
474956d705dSDamien Le Moal 
handle_scalar_misaligned_store(struct pt_regs * regs)475d1703dc7SJesse Taube static int handle_scalar_misaligned_store(struct pt_regs *regs)
476956d705dSDamien Le Moal {
477956d705dSDamien Le Moal 	union reg_data val;
478956d705dSDamien Le Moal 	unsigned long epc = regs->epc;
4797c832321SClément Léger 	unsigned long insn;
4807c832321SClément Léger 	unsigned long addr = regs->badaddr;
48144138150SClément Léger 	int len = 0, fp = 0;
482956d705dSDamien Le Moal 
48389c12fecSClément Léger 	perf_sw_event(PERF_COUNT_SW_ALIGNMENT_FAULTS, 1, regs, addr);
48489c12fecSClément Léger 
485bc38f613SClément Léger 	if (!unaligned_enabled)
486bc38f613SClément Léger 		return -1;
487bc38f613SClément Léger 
4889f23a5d2SClément Léger 	if (user_mode(regs) && (current->thread.align_ctl & PR_UNALIGN_SIGBUS))
4899f23a5d2SClément Léger 		return -1;
4909f23a5d2SClément Léger 
4917c832321SClément Léger 	if (get_insn(regs, epc, &insn))
4927c832321SClément Léger 		return -1;
4937c832321SClément Léger 
494956d705dSDamien Le Moal 	regs->epc = 0;
495956d705dSDamien Le Moal 
496956d705dSDamien Le Moal 	val.data_ulong = GET_RS2(insn, regs);
497956d705dSDamien Le Moal 
498956d705dSDamien Le Moal 	if ((insn & INSN_MASK_SW) == INSN_MATCH_SW) {
499956d705dSDamien Le Moal 		len = 4;
500956d705dSDamien Le Moal #if defined(CONFIG_64BIT)
501956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_SD) == INSN_MATCH_SD) {
502956d705dSDamien Le Moal 		len = 8;
503956d705dSDamien Le Moal #endif
5047c586a55SClément Léger 	} else if ((insn & INSN_MASK_FSD) == INSN_MATCH_FSD) {
5057c586a55SClément Léger 		fp = 1;
5067c586a55SClément Léger 		len = 8;
5077c586a55SClément Léger 		val.data_u64 = GET_F64_RS2(insn, regs);
5087c586a55SClément Léger 	} else if ((insn & INSN_MASK_FSW) == INSN_MATCH_FSW) {
5097c586a55SClément Léger 		fp = 1;
5107c586a55SClément Léger 		len = 4;
5117c586a55SClément Léger 		val.data_ulong = GET_F32_RS2(insn, regs);
512956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_SH) == INSN_MATCH_SH) {
513956d705dSDamien Le Moal 		len = 2;
514956d705dSDamien Le Moal #if defined(CONFIG_64BIT)
515956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_SD) == INSN_MATCH_C_SD) {
516956d705dSDamien Le Moal 		len = 8;
517956d705dSDamien Le Moal 		val.data_ulong = GET_RS2S(insn, regs);
51822e0eb04SClément Léger 	} else if ((insn & INSN_MASK_C_SDSP) == INSN_MATCH_C_SDSP) {
519956d705dSDamien Le Moal 		len = 8;
520956d705dSDamien Le Moal 		val.data_ulong = GET_RS2C(insn, regs);
521956d705dSDamien Le Moal #endif
522956d705dSDamien Le Moal 	} else if ((insn & INSN_MASK_C_SW) == INSN_MATCH_C_SW) {
523956d705dSDamien Le Moal 		len = 4;
524956d705dSDamien Le Moal 		val.data_ulong = GET_RS2S(insn, regs);
52522e0eb04SClément Léger 	} else if ((insn & INSN_MASK_C_SWSP) == INSN_MATCH_C_SWSP) {
526956d705dSDamien Le Moal 		len = 4;
527956d705dSDamien Le Moal 		val.data_ulong = GET_RS2C(insn, regs);
5287c586a55SClément Léger 	} else if ((insn & INSN_MASK_C_FSD) == INSN_MATCH_C_FSD) {
5297c586a55SClément Léger 		fp = 1;
5307c586a55SClément Léger 		len = 8;
5317c586a55SClément Léger 		val.data_u64 = GET_F64_RS2S(insn, regs);
5327c586a55SClément Léger 	} else if ((insn & INSN_MASK_C_FSDSP) == INSN_MATCH_C_FSDSP) {
5337c586a55SClément Léger 		fp = 1;
5347c586a55SClément Léger 		len = 8;
5357c586a55SClément Léger 		val.data_u64 = GET_F64_RS2C(insn, regs);
5367c586a55SClément Léger #if !defined(CONFIG_64BIT)
5377c586a55SClément Léger 	} else if ((insn & INSN_MASK_C_FSW) == INSN_MATCH_C_FSW) {
5387c586a55SClément Léger 		fp = 1;
5397c586a55SClément Léger 		len = 4;
5407c586a55SClément Léger 		val.data_ulong = GET_F32_RS2S(insn, regs);
5417c586a55SClément Léger 	} else if ((insn & INSN_MASK_C_FSWSP) == INSN_MATCH_C_FSWSP) {
5427c586a55SClément Léger 		fp = 1;
5437c586a55SClément Léger 		len = 4;
5447c586a55SClément Léger 		val.data_ulong = GET_F32_RS2C(insn, regs);
5457c586a55SClément Léger #endif
5467b30b1b0SNylon Chen 	} else if ((insn & INSN_MASK_C_SH) == INSN_MATCH_C_SH) {
5477b30b1b0SNylon Chen 		len = 2;
5487b30b1b0SNylon Chen 		val.data_ulong = GET_RS2S(insn, regs);
549956d705dSDamien Le Moal 	} else {
550956d705dSDamien Le Moal 		regs->epc = epc;
551956d705dSDamien Le Moal 		return -1;
552956d705dSDamien Le Moal 	}
553956d705dSDamien Le Moal 
5547c586a55SClément Léger 	if (!IS_ENABLED(CONFIG_FPU) && fp)
5557c586a55SClément Léger 		return -EOPNOTSUPP;
5567c586a55SClément Léger 
55744138150SClément Léger 	if (user_mode(regs)) {
55861a74ad2SNylon Chen 		if (copy_to_user((u8 __user *)addr, &val, len))
5597c832321SClément Léger 			return -1;
56044138150SClément Léger 	} else {
56144138150SClément Léger 		memcpy((u8 *)addr, &val, len);
5627c832321SClément Léger 	}
563956d705dSDamien Le Moal 
564956d705dSDamien Le Moal 	regs->epc = epc + INSN_LEN(insn);
565956d705dSDamien Le Moal 
566956d705dSDamien Le Moal 	return 0;
567956d705dSDamien Le Moal }
56871c54b3dSClément Léger 
handle_misaligned_load(struct pt_regs * regs)569d1703dc7SJesse Taube int handle_misaligned_load(struct pt_regs *regs)
57071c54b3dSClément Léger {
571d1703dc7SJesse Taube 	unsigned long epc = regs->epc;
572d1703dc7SJesse Taube 	unsigned long insn;
573d1703dc7SJesse Taube 
574d1703dc7SJesse Taube 	if (IS_ENABLED(CONFIG_RISCV_VECTOR_MISALIGNED)) {
575d1703dc7SJesse Taube 		if (get_insn(regs, epc, &insn))
576d1703dc7SJesse Taube 			return -1;
577d1703dc7SJesse Taube 
578d1703dc7SJesse Taube 		if (insn_is_vector(insn))
579d1703dc7SJesse Taube 			return handle_vector_misaligned_load(regs);
580d1703dc7SJesse Taube 	}
581d1703dc7SJesse Taube 
582d1703dc7SJesse Taube 	if (IS_ENABLED(CONFIG_RISCV_SCALAR_MISALIGNED))
583d1703dc7SJesse Taube 		return handle_scalar_misaligned_load(regs);
584d1703dc7SJesse Taube 
585d1703dc7SJesse Taube 	return -1;
586d1703dc7SJesse Taube }
587d1703dc7SJesse Taube 
handle_misaligned_store(struct pt_regs * regs)588d1703dc7SJesse Taube int handle_misaligned_store(struct pt_regs *regs)
589d1703dc7SJesse Taube {
590d1703dc7SJesse Taube 	if (IS_ENABLED(CONFIG_RISCV_SCALAR_MISALIGNED))
591d1703dc7SJesse Taube 		return handle_scalar_misaligned_store(regs);
592d1703dc7SJesse Taube 
593d1703dc7SJesse Taube 	return -1;
594d1703dc7SJesse Taube }
595d1703dc7SJesse Taube 
596d1703dc7SJesse Taube #ifdef CONFIG_RISCV_VECTOR_MISALIGNED
check_vector_unaligned_access_emulated(struct work_struct * work __always_unused)597d1703dc7SJesse Taube void check_vector_unaligned_access_emulated(struct work_struct *work __always_unused)
598d1703dc7SJesse Taube {
599d1703dc7SJesse Taube 	long *mas_ptr = this_cpu_ptr(&vector_misaligned_access);
600d1703dc7SJesse Taube 	unsigned long tmp_var;
601d1703dc7SJesse Taube 
602d1703dc7SJesse Taube 	*mas_ptr = RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN;
603d1703dc7SJesse Taube 
604d1703dc7SJesse Taube 	kernel_vector_begin();
605d1703dc7SJesse Taube 	/*
606d1703dc7SJesse Taube 	 * In pre-13.0.0 versions of GCC, vector registers cannot appear in
607d1703dc7SJesse Taube 	 * the clobber list. This inline asm clobbers v0, but since we do not
608d1703dc7SJesse Taube 	 * currently build the kernel with V enabled, the v0 clobber arg is not
609d1703dc7SJesse Taube 	 * needed (as the compiler will not emit vector code itself). If the kernel
610d1703dc7SJesse Taube 	 * is changed to build with V enabled, the clobber arg will need to be
611d1703dc7SJesse Taube 	 * added here.
612d1703dc7SJesse Taube 	 */
613d1703dc7SJesse Taube 	__asm__ __volatile__ (
614d1703dc7SJesse Taube 		".balign 4\n\t"
615d1703dc7SJesse Taube 		".option push\n\t"
616d1703dc7SJesse Taube 		".option arch, +zve32x\n\t"
617d1703dc7SJesse Taube 		"       vsetivli zero, 1, e16, m1, ta, ma\n\t"	// Vectors of 16b
618d1703dc7SJesse Taube 		"       vle16.v v0, (%[ptr])\n\t"		// Load bytes
619d1703dc7SJesse Taube 		".option pop\n\t"
620d1703dc7SJesse Taube 		: : [ptr] "r" ((u8 *)&tmp_var + 1));
621d1703dc7SJesse Taube 	kernel_vector_end();
622d1703dc7SJesse Taube }
623d1703dc7SJesse Taube 
check_vector_unaligned_access_emulated_all_cpus(void)624a00e022bSAndrew Jones bool __init check_vector_unaligned_access_emulated_all_cpus(void)
625d1703dc7SJesse Taube {
626d1703dc7SJesse Taube 	int cpu;
627d1703dc7SJesse Taube 
6289f9f6fddSClément Léger 	/*
6299f9f6fddSClément Léger 	 * While being documented as very slow, schedule_on_each_cpu() is used since
6309f9f6fddSClément Léger 	 * kernel_vector_begin() expects irqs to be enabled or it will panic()
6319f9f6fddSClément Léger 	 */
632d1703dc7SJesse Taube 	schedule_on_each_cpu(check_vector_unaligned_access_emulated);
633d1703dc7SJesse Taube 
634d1703dc7SJesse Taube 	for_each_online_cpu(cpu)
635d1703dc7SJesse Taube 		if (per_cpu(vector_misaligned_access, cpu)
636d1703dc7SJesse Taube 		    == RISCV_HWPROBE_MISALIGNED_VECTOR_UNKNOWN)
637d1703dc7SJesse Taube 			return false;
638d1703dc7SJesse Taube 
639d1703dc7SJesse Taube 	return true;
640d1703dc7SJesse Taube }
641d1703dc7SJesse Taube #else
check_vector_unaligned_access_emulated_all_cpus(void)642a00e022bSAndrew Jones bool __init check_vector_unaligned_access_emulated_all_cpus(void)
643d1703dc7SJesse Taube {
644d1703dc7SJesse Taube 	return false;
645d1703dc7SJesse Taube }
646d1703dc7SJesse Taube #endif
647d1703dc7SJesse Taube 
all_cpus_unaligned_scalar_access_emulated(void)6484eaaa65eSClément Léger static bool all_cpus_unaligned_scalar_access_emulated(void)
6494eaaa65eSClément Léger {
6504eaaa65eSClément Léger 	int cpu;
6514eaaa65eSClément Léger 
6524eaaa65eSClément Léger 	for_each_online_cpu(cpu)
6534eaaa65eSClément Léger 		if (per_cpu(misaligned_access_speed, cpu) !=
6544eaaa65eSClément Léger 		    RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED)
6554eaaa65eSClément Léger 			return false;
6564eaaa65eSClément Léger 
6574eaaa65eSClément Léger 	return true;
6584eaaa65eSClément Léger }
6594eaaa65eSClément Léger 
660d1703dc7SJesse Taube #ifdef CONFIG_RISCV_SCALAR_MISALIGNED
661d1703dc7SJesse Taube 
662d1703dc7SJesse Taube static bool unaligned_ctl __read_mostly;
663d1703dc7SJesse Taube 
check_unaligned_access_emulated(void * arg __always_unused)6649f9f6fddSClément Léger static void check_unaligned_access_emulated(void *arg __always_unused)
66571c54b3dSClément Léger {
6668d20a739SJesse Taube 	int cpu = smp_processor_id();
66771c54b3dSClément Léger 	long *mas_ptr = per_cpu_ptr(&misaligned_access_speed, cpu);
66871c54b3dSClément Léger 	unsigned long tmp_var, tmp_val;
66971c54b3dSClément Léger 
6701f528887SEvan Green 	*mas_ptr = RISCV_HWPROBE_MISALIGNED_SCALAR_UNKNOWN;
67171c54b3dSClément Léger 
67271c54b3dSClément Léger 	__asm__ __volatile__ (
67371c54b3dSClément Léger 		"       "REG_L" %[tmp], 1(%[ptr])\n"
67471c54b3dSClément Léger 		: [tmp] "=r" (tmp_val) : [ptr] "r" (&tmp_var) : "memory");
675cf5a8abcSClément Léger }
676cf5a8abcSClément Léger 
cpu_online_check_unaligned_access_emulated(unsigned int cpu)677cf5a8abcSClément Léger static int cpu_online_check_unaligned_access_emulated(unsigned int cpu)
678cf5a8abcSClément Léger {
679cf5a8abcSClément Léger 	long *mas_ptr = per_cpu_ptr(&misaligned_access_speed, cpu);
680cf5a8abcSClément Léger 
681cf5a8abcSClément Léger 	check_unaligned_access_emulated(NULL);
68271c54b3dSClément Léger 
68371c54b3dSClément Léger 	/*
68471c54b3dSClément Léger 	 * If unaligned_ctl is already set, this means that we detected that all
68571c54b3dSClément Léger 	 * CPUS uses emulated misaligned access at boot time. If that changed
68671c54b3dSClément Léger 	 * when hotplugging the new cpu, this is something we don't handle.
68771c54b3dSClément Léger 	 */
6888d20a739SJesse Taube 	if (unlikely(unaligned_ctl && (*mas_ptr != RISCV_HWPROBE_MISALIGNED_SCALAR_EMULATED))) {
68971c54b3dSClément Léger 		pr_crit("CPU misaligned accesses non homogeneous (expected all emulated)\n");
690cf5a8abcSClément Léger 		return -EINVAL;
69171c54b3dSClément Léger 	}
692cf5a8abcSClément Léger 
693cf5a8abcSClément Léger 	return 0;
69471c54b3dSClément Léger }
69571c54b3dSClément Léger 
check_unaligned_access_emulated_all_cpus(void)696a00e022bSAndrew Jones bool __init check_unaligned_access_emulated_all_cpus(void)
69771c54b3dSClément Léger {
69871c54b3dSClément Léger 	/*
69971c54b3dSClément Léger 	 * We can only support PR_UNALIGN controls if all CPUs have misaligned
70071c54b3dSClément Léger 	 * accesses emulated since tasks requesting such control can run on any
70171c54b3dSClément Léger 	 * CPU.
70271c54b3dSClément Léger 	 */
7039f9f6fddSClément Léger 	on_each_cpu(check_unaligned_access_emulated, NULL, 1);
7048d20a739SJesse Taube 
7054eaaa65eSClément Léger 	if (!all_cpus_unaligned_scalar_access_emulated())
7066e5ce7f2SCharlie Jenkins 		return false;
7076e5ce7f2SCharlie Jenkins 
70871c54b3dSClément Léger 	unaligned_ctl = true;
7096e5ce7f2SCharlie Jenkins 	return true;
71071c54b3dSClément Léger }
71171c54b3dSClément Léger 
unaligned_ctl_available(void)71271c54b3dSClément Léger bool unaligned_ctl_available(void)
71371c54b3dSClément Léger {
71471c54b3dSClément Léger 	return unaligned_ctl;
71571c54b3dSClément Léger }
716d1703dc7SJesse Taube #else
check_unaligned_access_emulated_all_cpus(void)717a00e022bSAndrew Jones bool __init check_unaligned_access_emulated_all_cpus(void)
718d1703dc7SJesse Taube {
719d1703dc7SJesse Taube 	return false;
720d1703dc7SJesse Taube }
cpu_online_check_unaligned_access_emulated(unsigned int cpu)721cf5a8abcSClément Léger static int cpu_online_check_unaligned_access_emulated(unsigned int cpu)
722cf5a8abcSClément Léger {
723cf5a8abcSClément Léger 	return 0;
724cf5a8abcSClément Léger }
725d1703dc7SJesse Taube #endif
726cf5a8abcSClément Léger 
727cf5a8abcSClément Léger static bool misaligned_traps_delegated;
728cf5a8abcSClément Léger 
729*7977448bSClément Léger #ifdef CONFIG_RISCV_SBI
730*7977448bSClément Léger 
cpu_online_sbi_unaligned_setup(unsigned int cpu)731cf5a8abcSClément Léger static int cpu_online_sbi_unaligned_setup(unsigned int cpu)
732cf5a8abcSClément Léger {
733cf5a8abcSClément Léger 	if (sbi_fwft_set(SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0) &&
734cf5a8abcSClément Léger 	    misaligned_traps_delegated) {
735cf5a8abcSClément Léger 		pr_crit("Misaligned trap delegation non homogeneous (expected delegated)");
736cf5a8abcSClément Léger 		return -EINVAL;
737cf5a8abcSClément Léger 	}
738cf5a8abcSClément Léger 
739cf5a8abcSClément Léger 	return 0;
740cf5a8abcSClément Léger }
741cf5a8abcSClément Léger 
unaligned_access_init(void)742cf5a8abcSClément Léger void __init unaligned_access_init(void)
743cf5a8abcSClément Léger {
744cf5a8abcSClément Léger 	int ret;
745cf5a8abcSClément Léger 
746cf5a8abcSClément Léger 	ret = sbi_fwft_set_online_cpus(SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0);
747cf5a8abcSClément Léger 	if (ret)
748cf5a8abcSClément Léger 		return;
749cf5a8abcSClément Léger 
750cf5a8abcSClément Léger 	misaligned_traps_delegated = true;
751cf5a8abcSClément Léger 	pr_info("SBI misaligned access exception delegation ok\n");
752cf5a8abcSClément Léger 	/*
753cf5a8abcSClément Léger 	 * Note that we don't have to take any specific action here, if
754cf5a8abcSClément Léger 	 * the delegation is successful, then
755cf5a8abcSClément Léger 	 * check_unaligned_access_emulated() will verify that indeed the
756cf5a8abcSClément Léger 	 * platform traps on misaligned accesses.
757cf5a8abcSClément Léger 	 */
758cf5a8abcSClément Léger }
759cf5a8abcSClément Léger #else
unaligned_access_init(void)760cf5a8abcSClément Léger void __init unaligned_access_init(void) {}
761cf5a8abcSClément Léger 
cpu_online_sbi_unaligned_setup(unsigned int cpu __always_unused)762cf5a8abcSClément Léger static int cpu_online_sbi_unaligned_setup(unsigned int cpu __always_unused)
763cf5a8abcSClément Léger {
764cf5a8abcSClément Léger 	return 0;
765cf5a8abcSClément Léger }
766*7977448bSClément Léger 
767cf5a8abcSClément Léger #endif
768cf5a8abcSClément Léger 
cpu_online_unaligned_access_init(unsigned int cpu)769cf5a8abcSClément Léger int cpu_online_unaligned_access_init(unsigned int cpu)
770cf5a8abcSClément Léger {
771cf5a8abcSClément Léger 	int ret;
772cf5a8abcSClément Léger 
773cf5a8abcSClément Léger 	ret = cpu_online_sbi_unaligned_setup(cpu);
774cf5a8abcSClément Léger 	if (ret)
775cf5a8abcSClément Léger 		return ret;
776cf5a8abcSClément Léger 
777cf5a8abcSClément Léger 	return cpu_online_check_unaligned_access_emulated(cpu);
778cf5a8abcSClément Léger }
779*7977448bSClément Léger 
misaligned_traps_can_delegate(void)780*7977448bSClément Léger bool misaligned_traps_can_delegate(void)
781*7977448bSClément Léger {
782*7977448bSClément Léger 	/*
783*7977448bSClément Léger 	 * Either we successfully requested misaligned traps delegation for all
784*7977448bSClément Léger 	 * CPUs, or the SBI does not implement the FWFT extension but delegated
785*7977448bSClément Léger 	 * the exception by default.
786*7977448bSClément Léger 	 */
787*7977448bSClément Léger 	return misaligned_traps_delegated ||
788*7977448bSClément Léger 	       all_cpus_unaligned_scalar_access_emulated();
789*7977448bSClément Léger }
790*7977448bSClément Léger EXPORT_SYMBOL_GPL(misaligned_traps_can_delegate);
791