drm/rockchip: Don't change hdmi reference clock rate
[drm/drm-misc.git] / arch / arm64 / kernel / pi / patch-scs.c
blob55d0cd64ef71e692ee7acd41f03937570023cf90
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2022 - Google LLC
4 * Author: Ard Biesheuvel <ardb@google.com>
5 */
7 #include <linux/errno.h>
8 #include <linux/init.h>
9 #include <linux/linkage.h>
10 #include <linux/types.h>
12 #include <asm/scs.h>
14 #include "pi.h"
16 bool dynamic_scs_is_enabled;
19 // This minimal DWARF CFI parser is partially based on the code in
20 // arch/arc/kernel/unwind.c, and on the document below:
21 // https://refspecs.linuxbase.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
24 #define DW_CFA_nop 0x00
25 #define DW_CFA_set_loc 0x01
26 #define DW_CFA_advance_loc1 0x02
27 #define DW_CFA_advance_loc2 0x03
28 #define DW_CFA_advance_loc4 0x04
29 #define DW_CFA_offset_extended 0x05
30 #define DW_CFA_restore_extended 0x06
31 #define DW_CFA_undefined 0x07
32 #define DW_CFA_same_value 0x08
33 #define DW_CFA_register 0x09
34 #define DW_CFA_remember_state 0x0a
35 #define DW_CFA_restore_state 0x0b
36 #define DW_CFA_def_cfa 0x0c
37 #define DW_CFA_def_cfa_register 0x0d
38 #define DW_CFA_def_cfa_offset 0x0e
39 #define DW_CFA_def_cfa_expression 0x0f
40 #define DW_CFA_expression 0x10
41 #define DW_CFA_offset_extended_sf 0x11
42 #define DW_CFA_def_cfa_sf 0x12
43 #define DW_CFA_def_cfa_offset_sf 0x13
44 #define DW_CFA_val_offset 0x14
45 #define DW_CFA_val_offset_sf 0x15
46 #define DW_CFA_val_expression 0x16
47 #define DW_CFA_lo_user 0x1c
48 #define DW_CFA_negate_ra_state 0x2d
49 #define DW_CFA_GNU_args_size 0x2e
50 #define DW_CFA_GNU_negative_offset_extended 0x2f
51 #define DW_CFA_hi_user 0x3f
53 #define DW_EH_PE_sdata4 0x0b
54 #define DW_EH_PE_sdata8 0x0c
55 #define DW_EH_PE_pcrel 0x10
57 enum {
58 PACIASP = 0xd503233f,
59 AUTIASP = 0xd50323bf,
60 SCS_PUSH = 0xf800865e,
61 SCS_POP = 0xf85f8e5e,
64 static void __always_inline scs_patch_loc(u64 loc)
66 u32 insn = le32_to_cpup((void *)loc);
68 switch (insn) {
69 case PACIASP:
70 *(u32 *)loc = cpu_to_le32(SCS_PUSH);
71 break;
72 case AUTIASP:
73 *(u32 *)loc = cpu_to_le32(SCS_POP);
74 break;
75 default:
77 * While the DW_CFA_negate_ra_state directive is guaranteed to
78 * appear right after a PACIASP/AUTIASP instruction, it may
79 * also appear after a DW_CFA_restore_state directive that
80 * restores a state that is only partially accurate, and is
81 * followed by DW_CFA_negate_ra_state directive to toggle the
82 * PAC bit again. So we permit other instructions here, and ignore
83 * them.
85 return;
87 if (IS_ENABLED(CONFIG_ARM64_WORKAROUND_CLEAN_CACHE))
88 asm("dc civac, %0" :: "r"(loc));
89 else
90 asm(ALTERNATIVE("dc cvau, %0", "nop", ARM64_HAS_CACHE_IDC)
91 :: "r"(loc));
95 * Skip one uleb128/sleb128 encoded quantity from the opcode stream. All bytes
96 * except the last one have bit #7 set.
98 static int __always_inline skip_xleb128(const u8 **opcode, int size)
100 u8 c;
102 do {
103 c = *(*opcode)++;
104 size--;
105 } while (c & BIT(7));
107 return size;
110 struct eh_frame {
112 * The size of this frame if 0 < size < U32_MAX, 0 terminates the list.
114 u32 size;
117 * The first frame is a Common Information Entry (CIE) frame, followed
118 * by one or more Frame Description Entry (FDE) frames. In the former
119 * case, this field is 0, otherwise it is the negated offset relative
120 * to the associated CIE frame.
122 u32 cie_id_or_pointer;
124 union {
125 struct { // CIE
126 u8 version;
127 u8 augmentation_string[3];
128 u8 code_alignment_factor;
129 u8 data_alignment_factor;
130 u8 return_address_register;
131 u8 augmentation_data_size;
132 u8 fde_pointer_format;
135 struct { // FDE
136 s32 initial_loc;
137 s32 range;
138 u8 opcodes[];
141 struct { // FDE
142 s64 initial_loc64;
143 s64 range64;
144 u8 opcodes64[];
149 static int scs_handle_fde_frame(const struct eh_frame *frame,
150 int code_alignment_factor,
151 bool use_sdata8,
152 bool dry_run)
154 int size = frame->size - offsetof(struct eh_frame, opcodes) + 4;
155 u64 loc = (u64)offset_to_ptr(&frame->initial_loc);
156 const u8 *opcode = frame->opcodes;
157 int l;
159 if (use_sdata8) {
160 loc = (u64)&frame->initial_loc64 + frame->initial_loc64;
161 opcode = frame->opcodes64;
162 size -= 8;
165 // assume single byte uleb128_t for augmentation data size
166 if (*opcode & BIT(7))
167 return EDYNSCS_INVALID_FDE_AUGM_DATA_SIZE;
169 l = *opcode++;
170 opcode += l;
171 size -= l + 1;
174 * Starting from 'loc', apply the CFA opcodes that advance the location
175 * pointer, and identify the locations of the PAC instructions.
177 while (size-- > 0) {
178 switch (*opcode++) {
179 case DW_CFA_nop:
180 case DW_CFA_remember_state:
181 case DW_CFA_restore_state:
182 break;
184 case DW_CFA_advance_loc1:
185 loc += *opcode++ * code_alignment_factor;
186 size--;
187 break;
189 case DW_CFA_advance_loc2:
190 loc += *opcode++ * code_alignment_factor;
191 loc += (*opcode++ << 8) * code_alignment_factor;
192 size -= 2;
193 break;
195 case DW_CFA_def_cfa:
196 case DW_CFA_offset_extended:
197 size = skip_xleb128(&opcode, size);
198 fallthrough;
199 case DW_CFA_def_cfa_offset:
200 case DW_CFA_def_cfa_offset_sf:
201 case DW_CFA_def_cfa_register:
202 case DW_CFA_same_value:
203 case DW_CFA_restore_extended:
204 case 0x80 ... 0xbf:
205 size = skip_xleb128(&opcode, size);
206 break;
208 case DW_CFA_negate_ra_state:
209 if (!dry_run)
210 scs_patch_loc(loc - 4);
211 break;
213 case 0x40 ... 0x7f:
214 // advance loc
215 loc += (opcode[-1] & 0x3f) * code_alignment_factor;
216 break;
218 case 0xc0 ... 0xff:
219 break;
221 default:
222 return EDYNSCS_INVALID_CFA_OPCODE;
225 return 0;
228 int scs_patch(const u8 eh_frame[], int size)
230 int code_alignment_factor = 1;
231 bool fde_use_sdata8 = false;
232 const u8 *p = eh_frame;
234 while (size > 4) {
235 const struct eh_frame *frame = (const void *)p;
236 int ret;
238 if (frame->size == 0 ||
239 frame->size == U32_MAX ||
240 frame->size > size)
241 break;
243 if (frame->cie_id_or_pointer == 0) {
245 * Require presence of augmentation data (z) with a
246 * specifier for the size of the FDE initial_loc and
247 * range fields (R), and nothing else.
249 if (strcmp(frame->augmentation_string, "zR"))
250 return EDYNSCS_INVALID_CIE_HEADER;
253 * The code alignment factor is a uleb128 encoded field
254 * but given that the only sensible values are 1 or 4,
255 * there is no point in decoding the whole thing. Also
256 * sanity check the size of the data alignment factor
257 * field, and the values of the return address register
258 * and augmentation data size fields.
260 if ((frame->code_alignment_factor & BIT(7)) ||
261 (frame->data_alignment_factor & BIT(7)) ||
262 frame->return_address_register != 30 ||
263 frame->augmentation_data_size != 1)
264 return EDYNSCS_INVALID_CIE_HEADER;
266 code_alignment_factor = frame->code_alignment_factor;
268 switch (frame->fde_pointer_format) {
269 case DW_EH_PE_pcrel | DW_EH_PE_sdata4:
270 fde_use_sdata8 = false;
271 break;
272 case DW_EH_PE_pcrel | DW_EH_PE_sdata8:
273 fde_use_sdata8 = true;
274 break;
275 default:
276 return EDYNSCS_INVALID_CIE_SDATA_SIZE;
278 } else {
279 ret = scs_handle_fde_frame(frame, code_alignment_factor,
280 fde_use_sdata8, true);
281 if (ret)
282 return ret;
283 scs_handle_fde_frame(frame, code_alignment_factor,
284 fde_use_sdata8, false);
287 p += sizeof(frame->size) + frame->size;
288 size -= sizeof(frame->size) + frame->size;
290 return 0;