1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2014 ARM Limited |
4 | */ |
5 | |
6 | #include <linux/cpu.h> |
7 | #include <linux/init.h> |
8 | #include <linux/list.h> |
9 | #include <linux/perf_event.h> |
10 | #include <linux/sched.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/sysctl.h> |
13 | #include <linux/uaccess.h> |
14 | |
15 | #include <asm/cpufeature.h> |
16 | #include <asm/insn.h> |
17 | #include <asm/sysreg.h> |
18 | #include <asm/system_misc.h> |
19 | #include <asm/traps.h> |
20 | |
21 | #define CREATE_TRACE_POINTS |
22 | #include "trace-events-emulation.h" |
23 | |
24 | /* |
25 | * The runtime support for deprecated instruction support can be in one of |
26 | * following three states - |
27 | * |
28 | * 0 = undef |
29 | * 1 = emulate (software emulation) |
30 | * 2 = hw (supported in hardware) |
31 | */ |
32 | enum insn_emulation_mode { |
33 | INSN_UNDEF, |
34 | INSN_EMULATE, |
35 | INSN_HW, |
36 | }; |
37 | |
38 | enum legacy_insn_status { |
39 | INSN_DEPRECATED, |
40 | INSN_OBSOLETE, |
41 | INSN_UNAVAILABLE, |
42 | }; |
43 | |
44 | struct insn_emulation { |
45 | const char *name; |
46 | enum legacy_insn_status status; |
47 | bool (*try_emulate)(struct pt_regs *regs, |
48 | u32 insn); |
49 | int (*set_hw_mode)(bool enable); |
50 | |
51 | int current_mode; |
52 | int min; |
53 | int max; |
54 | |
55 | /* sysctl for this emulation */ |
56 | struct ctl_table sysctl; |
57 | }; |
58 | |
59 | #define ARM_OPCODE_CONDTEST_FAIL 0 |
60 | #define ARM_OPCODE_CONDTEST_PASS 1 |
61 | #define ARM_OPCODE_CONDTEST_UNCOND 2 |
62 | |
63 | #define ARM_OPCODE_CONDITION_UNCOND 0xf |
64 | |
65 | static unsigned int __maybe_unused aarch32_check_condition(u32 opcode, u32 psr) |
66 | { |
67 | u32 cc_bits = opcode >> 28; |
68 | |
69 | if (cc_bits != ARM_OPCODE_CONDITION_UNCOND) { |
70 | if ((*aarch32_opcode_cond_checks[cc_bits])(psr)) |
71 | return ARM_OPCODE_CONDTEST_PASS; |
72 | else |
73 | return ARM_OPCODE_CONDTEST_FAIL; |
74 | } |
75 | return ARM_OPCODE_CONDTEST_UNCOND; |
76 | } |
77 | |
78 | #ifdef CONFIG_SWP_EMULATION |
79 | /* |
80 | * Implement emulation of the SWP/SWPB instructions using load-exclusive and |
81 | * store-exclusive. |
82 | * |
83 | * Syntax of SWP{B} instruction: SWP{B}<c> <Rt>, <Rt2>, [<Rn>] |
84 | * Where: Rt = destination |
85 | * Rt2 = source |
86 | * Rn = address |
87 | */ |
88 | |
89 | /* |
90 | * Error-checking SWP macros implemented using ldxr{b}/stxr{b} |
91 | */ |
92 | |
93 | /* Arbitrary constant to ensure forward-progress of the LL/SC loop */ |
94 | #define __SWP_LL_SC_LOOPS 4 |
95 | |
96 | #define __user_swpX_asm(data, addr, res, temp, temp2, B) \ |
97 | do { \ |
98 | uaccess_enable_privileged(); \ |
99 | __asm__ __volatile__( \ |
100 | " mov %w3, %w6\n" \ |
101 | "0: ldxr"B" %w2, [%4]\n" \ |
102 | "1: stxr"B" %w0, %w1, [%4]\n" \ |
103 | " cbz %w0, 2f\n" \ |
104 | " sub %w3, %w3, #1\n" \ |
105 | " cbnz %w3, 0b\n" \ |
106 | " mov %w0, %w5\n" \ |
107 | " b 3f\n" \ |
108 | "2:\n" \ |
109 | " mov %w1, %w2\n" \ |
110 | "3:\n" \ |
111 | _ASM_EXTABLE_UACCESS_ERR(0b, 3b, %w0) \ |
112 | _ASM_EXTABLE_UACCESS_ERR(1b, 3b, %w0) \ |
113 | : "=&r" (res), "+r" (data), "=&r" (temp), "=&r" (temp2) \ |
114 | : "r" ((unsigned long)addr), "i" (-EAGAIN), \ |
115 | "i" (__SWP_LL_SC_LOOPS) \ |
116 | : "memory"); \ |
117 | uaccess_disable_privileged(); \ |
118 | } while (0) |
119 | |
120 | #define __user_swp_asm(data, addr, res, temp, temp2) \ |
121 | __user_swpX_asm(data, addr, res, temp, temp2, "") |
122 | #define __user_swpb_asm(data, addr, res, temp, temp2) \ |
123 | __user_swpX_asm(data, addr, res, temp, temp2, "b") |
124 | |
125 | /* |
126 | * Bit 22 of the instruction encoding distinguishes between |
127 | * the SWP and SWPB variants (bit set means SWPB). |
128 | */ |
129 | #define TYPE_SWPB (1 << 22) |
130 | |
131 | static int emulate_swpX(unsigned int address, unsigned int *data, |
132 | unsigned int type) |
133 | { |
134 | unsigned int res = 0; |
135 | |
136 | if ((type != TYPE_SWPB) && (address & 0x3)) { |
137 | /* SWP to unaligned address not permitted */ |
138 | pr_debug("SWP instruction on unaligned pointer!\n" ); |
139 | return -EFAULT; |
140 | } |
141 | |
142 | while (1) { |
143 | unsigned long temp, temp2; |
144 | |
145 | if (type == TYPE_SWPB) |
146 | __user_swpb_asm(*data, address, res, temp, temp2); |
147 | else |
148 | __user_swp_asm(*data, address, res, temp, temp2); |
149 | |
150 | if (likely(res != -EAGAIN) || signal_pending(current)) |
151 | break; |
152 | |
153 | cond_resched(); |
154 | } |
155 | |
156 | return res; |
157 | } |
158 | |
159 | /* |
160 | * swp_handler logs the id of calling process, dissects the instruction, sanity |
161 | * checks the memory location, calls emulate_swpX for the actual operation and |
162 | * deals with fixup/error handling before returning |
163 | */ |
164 | static int swp_handler(struct pt_regs *regs, u32 instr) |
165 | { |
166 | u32 destreg, data, type, address = 0; |
167 | const void __user *user_ptr; |
168 | int rn, rt2, res = 0; |
169 | |
170 | perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc); |
171 | |
172 | type = instr & TYPE_SWPB; |
173 | |
174 | switch (aarch32_check_condition(instr, regs->pstate)) { |
175 | case ARM_OPCODE_CONDTEST_PASS: |
176 | break; |
177 | case ARM_OPCODE_CONDTEST_FAIL: |
178 | /* Condition failed - return to next instruction */ |
179 | goto ret; |
180 | case ARM_OPCODE_CONDTEST_UNCOND: |
181 | /* If unconditional encoding - not a SWP, undef */ |
182 | return -EFAULT; |
183 | default: |
184 | return -EINVAL; |
185 | } |
186 | |
187 | rn = aarch32_insn_extract_reg_num(instr, A32_RN_OFFSET); |
188 | rt2 = aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET); |
189 | |
190 | address = (u32)regs->user_regs.regs[rn]; |
191 | data = (u32)regs->user_regs.regs[rt2]; |
192 | destreg = aarch32_insn_extract_reg_num(instr, A32_RT_OFFSET); |
193 | |
194 | pr_debug("addr in r%d->0x%08x, dest is r%d, source in r%d->0x%08x)\n" , |
195 | rn, address, destreg, |
196 | aarch32_insn_extract_reg_num(instr, A32_RT2_OFFSET), data); |
197 | |
198 | /* Check access in reasonable access range for both SWP and SWPB */ |
199 | user_ptr = (const void __user *)(unsigned long)(address & ~3); |
200 | if (!access_ok(user_ptr, 4)) { |
201 | pr_debug("SWP{B} emulation: access to 0x%08x not allowed!\n" , |
202 | address); |
203 | goto fault; |
204 | } |
205 | |
206 | res = emulate_swpX(address, &data, type); |
207 | if (res == -EFAULT) |
208 | goto fault; |
209 | else if (res == 0) |
210 | regs->user_regs.regs[destreg] = data; |
211 | |
212 | ret: |
213 | if (type == TYPE_SWPB) |
214 | trace_instruction_emulation("swpb" , regs->pc); |
215 | else |
216 | trace_instruction_emulation("swp" , regs->pc); |
217 | |
218 | pr_warn_ratelimited("\"%s\" (%ld) uses obsolete SWP{B} instruction at 0x%llx\n" , |
219 | current->comm, (unsigned long)current->pid, regs->pc); |
220 | |
221 | arm64_skip_faulting_instruction(regs, 4); |
222 | return 0; |
223 | |
224 | fault: |
225 | pr_debug("SWP{B} emulation: access caused memory abort!\n" ); |
226 | arm64_notify_segfault(address); |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | static bool try_emulate_swp(struct pt_regs *regs, u32 insn) |
232 | { |
233 | /* SWP{B} only exists in ARM state and does not exist in Thumb */ |
234 | if (!compat_user_mode(regs) || compat_thumb_mode(regs)) |
235 | return false; |
236 | |
237 | if ((insn & 0x0fb00ff0) != 0x01000090) |
238 | return false; |
239 | |
240 | return swp_handler(regs, insn) == 0; |
241 | } |
242 | |
243 | static struct insn_emulation insn_swp = { |
244 | .name = "swp" , |
245 | .status = INSN_OBSOLETE, |
246 | .try_emulate = try_emulate_swp, |
247 | .set_hw_mode = NULL, |
248 | }; |
249 | #endif /* CONFIG_SWP_EMULATION */ |
250 | |
251 | #ifdef CONFIG_CP15_BARRIER_EMULATION |
252 | static int cp15barrier_handler(struct pt_regs *regs, u32 instr) |
253 | { |
254 | perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc); |
255 | |
256 | switch (aarch32_check_condition(instr, regs->pstate)) { |
257 | case ARM_OPCODE_CONDTEST_PASS: |
258 | break; |
259 | case ARM_OPCODE_CONDTEST_FAIL: |
260 | /* Condition failed - return to next instruction */ |
261 | goto ret; |
262 | case ARM_OPCODE_CONDTEST_UNCOND: |
263 | /* If unconditional encoding - not a barrier instruction */ |
264 | return -EFAULT; |
265 | default: |
266 | return -EINVAL; |
267 | } |
268 | |
269 | switch (aarch32_insn_mcr_extract_crm(instr)) { |
270 | case 10: |
271 | /* |
272 | * dmb - mcr p15, 0, Rt, c7, c10, 5 |
273 | * dsb - mcr p15, 0, Rt, c7, c10, 4 |
274 | */ |
275 | if (aarch32_insn_mcr_extract_opc2(instr) == 5) { |
276 | dmb(sy); |
277 | trace_instruction_emulation( |
278 | "mcr p15, 0, Rt, c7, c10, 5 ; dmb" , regs->pc); |
279 | } else { |
280 | dsb(sy); |
281 | trace_instruction_emulation( |
282 | "mcr p15, 0, Rt, c7, c10, 4 ; dsb" , regs->pc); |
283 | } |
284 | break; |
285 | case 5: |
286 | /* |
287 | * isb - mcr p15, 0, Rt, c7, c5, 4 |
288 | * |
289 | * Taking an exception or returning from one acts as an |
290 | * instruction barrier. So no explicit barrier needed here. |
291 | */ |
292 | trace_instruction_emulation( |
293 | "mcr p15, 0, Rt, c7, c5, 4 ; isb" , regs->pc); |
294 | break; |
295 | } |
296 | |
297 | ret: |
298 | pr_warn_ratelimited("\"%s\" (%ld) uses deprecated CP15 Barrier instruction at 0x%llx\n" , |
299 | current->comm, (unsigned long)current->pid, regs->pc); |
300 | |
301 | arm64_skip_faulting_instruction(regs, 4); |
302 | return 0; |
303 | } |
304 | |
305 | static int cp15_barrier_set_hw_mode(bool enable) |
306 | { |
307 | if (enable) |
308 | sysreg_clear_set(sctlr_el1, 0, SCTLR_EL1_CP15BEN); |
309 | else |
310 | sysreg_clear_set(sctlr_el1, SCTLR_EL1_CP15BEN, 0); |
311 | return 0; |
312 | } |
313 | |
314 | static bool try_emulate_cp15_barrier(struct pt_regs *regs, u32 insn) |
315 | { |
316 | if (!compat_user_mode(regs) || compat_thumb_mode(regs)) |
317 | return false; |
318 | |
319 | if ((insn & 0x0fff0fdf) == 0x0e070f9a) |
320 | return cp15barrier_handler(regs, insn) == 0; |
321 | |
322 | if ((insn & 0x0fff0fff) == 0x0e070f95) |
323 | return cp15barrier_handler(regs, insn) == 0; |
324 | |
325 | return false; |
326 | } |
327 | |
328 | static struct insn_emulation insn_cp15_barrier = { |
329 | .name = "cp15_barrier" , |
330 | .status = INSN_DEPRECATED, |
331 | .try_emulate = try_emulate_cp15_barrier, |
332 | .set_hw_mode = cp15_barrier_set_hw_mode, |
333 | }; |
334 | #endif /* CONFIG_CP15_BARRIER_EMULATION */ |
335 | |
336 | #ifdef CONFIG_SETEND_EMULATION |
337 | static int setend_set_hw_mode(bool enable) |
338 | { |
339 | if (!cpu_supports_mixed_endian_el0()) |
340 | return -EINVAL; |
341 | |
342 | if (enable) |
343 | sysreg_clear_set(sctlr_el1, SCTLR_EL1_SED, 0); |
344 | else |
345 | sysreg_clear_set(sctlr_el1, 0, SCTLR_EL1_SED); |
346 | return 0; |
347 | } |
348 | |
349 | static int compat_setend_handler(struct pt_regs *regs, u32 big_endian) |
350 | { |
351 | char *insn; |
352 | |
353 | perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc); |
354 | |
355 | if (big_endian) { |
356 | insn = "setend be" ; |
357 | regs->pstate |= PSR_AA32_E_BIT; |
358 | } else { |
359 | insn = "setend le" ; |
360 | regs->pstate &= ~PSR_AA32_E_BIT; |
361 | } |
362 | |
363 | trace_instruction_emulation(insn, regs->pc); |
364 | pr_warn_ratelimited("\"%s\" (%ld) uses deprecated setend instruction at 0x%llx\n" , |
365 | current->comm, (unsigned long)current->pid, regs->pc); |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | static int a32_setend_handler(struct pt_regs *regs, u32 instr) |
371 | { |
372 | int rc = compat_setend_handler(regs, (instr >> 9) & 1); |
373 | arm64_skip_faulting_instruction(regs, 4); |
374 | return rc; |
375 | } |
376 | |
377 | static int t16_setend_handler(struct pt_regs *regs, u32 instr) |
378 | { |
379 | int rc = compat_setend_handler(regs, (instr >> 3) & 1); |
380 | arm64_skip_faulting_instruction(regs, 2); |
381 | return rc; |
382 | } |
383 | |
384 | static bool try_emulate_setend(struct pt_regs *regs, u32 insn) |
385 | { |
386 | if (compat_thumb_mode(regs) && |
387 | (insn & 0xfffffff7) == 0x0000b650) |
388 | return t16_setend_handler(regs, insn) == 0; |
389 | |
390 | if (compat_user_mode(regs) && |
391 | (insn & 0xfffffdff) == 0xf1010000) |
392 | return a32_setend_handler(regs, insn) == 0; |
393 | |
394 | return false; |
395 | } |
396 | |
397 | static struct insn_emulation insn_setend = { |
398 | .name = "setend" , |
399 | .status = INSN_DEPRECATED, |
400 | .try_emulate = try_emulate_setend, |
401 | .set_hw_mode = setend_set_hw_mode, |
402 | }; |
403 | #endif /* CONFIG_SETEND_EMULATION */ |
404 | |
405 | static struct insn_emulation *insn_emulations[] = { |
406 | #ifdef CONFIG_SWP_EMULATION |
407 | &insn_swp, |
408 | #endif |
409 | #ifdef CONFIG_CP15_BARRIER_EMULATION |
410 | &insn_cp15_barrier, |
411 | #endif |
412 | #ifdef CONFIG_SETEND_EMULATION |
413 | &insn_setend, |
414 | #endif |
415 | }; |
416 | |
417 | static DEFINE_MUTEX(insn_emulation_mutex); |
418 | |
419 | static void enable_insn_hw_mode(void *data) |
420 | { |
421 | struct insn_emulation *insn = data; |
422 | if (insn->set_hw_mode) |
423 | insn->set_hw_mode(true); |
424 | } |
425 | |
426 | static void disable_insn_hw_mode(void *data) |
427 | { |
428 | struct insn_emulation *insn = data; |
429 | if (insn->set_hw_mode) |
430 | insn->set_hw_mode(false); |
431 | } |
432 | |
433 | /* Run set_hw_mode(mode) on all active CPUs */ |
434 | static int run_all_cpu_set_hw_mode(struct insn_emulation *insn, bool enable) |
435 | { |
436 | if (!insn->set_hw_mode) |
437 | return -EINVAL; |
438 | if (enable) |
439 | on_each_cpu(func: enable_insn_hw_mode, info: (void *)insn, wait: true); |
440 | else |
441 | on_each_cpu(func: disable_insn_hw_mode, info: (void *)insn, wait: true); |
442 | return 0; |
443 | } |
444 | |
445 | /* |
446 | * Run set_hw_mode for all insns on a starting CPU. |
447 | * Returns: |
448 | * 0 - If all the hooks ran successfully. |
449 | * -EINVAL - At least one hook is not supported by the CPU. |
450 | */ |
451 | static int run_all_insn_set_hw_mode(unsigned int cpu) |
452 | { |
453 | int rc = 0; |
454 | unsigned long flags; |
455 | |
456 | /* |
457 | * Disable IRQs to serialize against an IPI from |
458 | * run_all_cpu_set_hw_mode(), ensuring the HW is programmed to the most |
459 | * recent enablement state if the two race with one another. |
460 | */ |
461 | local_irq_save(flags); |
462 | for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { |
463 | struct insn_emulation *insn = insn_emulations[i]; |
464 | bool enable = READ_ONCE(insn->current_mode) == INSN_HW; |
465 | if (insn->set_hw_mode && insn->set_hw_mode(enable)) { |
466 | pr_warn("CPU[%u] cannot support the emulation of %s" , |
467 | cpu, insn->name); |
468 | rc = -EINVAL; |
469 | } |
470 | } |
471 | local_irq_restore(flags); |
472 | |
473 | return rc; |
474 | } |
475 | |
476 | static int update_insn_emulation_mode(struct insn_emulation *insn, |
477 | enum insn_emulation_mode prev) |
478 | { |
479 | int ret = 0; |
480 | |
481 | switch (prev) { |
482 | case INSN_UNDEF: /* Nothing to be done */ |
483 | break; |
484 | case INSN_EMULATE: |
485 | break; |
486 | case INSN_HW: |
487 | if (!run_all_cpu_set_hw_mode(insn, enable: false)) |
488 | pr_notice("Disabled %s support\n" , insn->name); |
489 | break; |
490 | } |
491 | |
492 | switch (insn->current_mode) { |
493 | case INSN_UNDEF: |
494 | break; |
495 | case INSN_EMULATE: |
496 | break; |
497 | case INSN_HW: |
498 | ret = run_all_cpu_set_hw_mode(insn, enable: true); |
499 | if (!ret) |
500 | pr_notice("Enabled %s support\n" , insn->name); |
501 | break; |
502 | } |
503 | |
504 | return ret; |
505 | } |
506 | |
507 | static int emulation_proc_handler(struct ctl_table *table, int write, |
508 | void *buffer, size_t *lenp, |
509 | loff_t *ppos) |
510 | { |
511 | int ret = 0; |
512 | struct insn_emulation *insn = container_of(table->data, struct insn_emulation, current_mode); |
513 | enum insn_emulation_mode prev_mode = insn->current_mode; |
514 | |
515 | mutex_lock(&insn_emulation_mutex); |
516 | ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); |
517 | |
518 | if (ret || !write || prev_mode == insn->current_mode) |
519 | goto ret; |
520 | |
521 | ret = update_insn_emulation_mode(insn, prev: prev_mode); |
522 | if (ret) { |
523 | /* Mode change failed, revert to previous mode. */ |
524 | WRITE_ONCE(insn->current_mode, prev_mode); |
525 | update_insn_emulation_mode(insn, prev: INSN_UNDEF); |
526 | } |
527 | ret: |
528 | mutex_unlock(lock: &insn_emulation_mutex); |
529 | return ret; |
530 | } |
531 | |
532 | static void __init register_insn_emulation(struct insn_emulation *insn) |
533 | { |
534 | struct ctl_table *sysctl; |
535 | |
536 | insn->min = INSN_UNDEF; |
537 | |
538 | switch (insn->status) { |
539 | case INSN_DEPRECATED: |
540 | insn->current_mode = INSN_EMULATE; |
541 | /* Disable the HW mode if it was turned on at early boot time */ |
542 | run_all_cpu_set_hw_mode(insn, enable: false); |
543 | insn->max = INSN_HW; |
544 | break; |
545 | case INSN_OBSOLETE: |
546 | insn->current_mode = INSN_UNDEF; |
547 | insn->max = INSN_EMULATE; |
548 | break; |
549 | case INSN_UNAVAILABLE: |
550 | insn->current_mode = INSN_UNDEF; |
551 | insn->max = INSN_UNDEF; |
552 | break; |
553 | } |
554 | |
555 | /* Program the HW if required */ |
556 | update_insn_emulation_mode(insn, prev: INSN_UNDEF); |
557 | |
558 | if (insn->status != INSN_UNAVAILABLE) { |
559 | sysctl = &insn->sysctl; |
560 | |
561 | sysctl->mode = 0644; |
562 | sysctl->maxlen = sizeof(int); |
563 | |
564 | sysctl->procname = insn->name; |
565 | sysctl->data = &insn->current_mode; |
566 | sysctl->extra1 = &insn->min; |
567 | sysctl->extra2 = &insn->max; |
568 | sysctl->proc_handler = emulation_proc_handler; |
569 | |
570 | register_sysctl_sz(path: "abi" , table: sysctl, table_size: 1); |
571 | } |
572 | } |
573 | |
574 | bool try_emulate_armv8_deprecated(struct pt_regs *regs, u32 insn) |
575 | { |
576 | for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { |
577 | struct insn_emulation *ie = insn_emulations[i]; |
578 | |
579 | if (ie->status == INSN_UNAVAILABLE) |
580 | continue; |
581 | |
582 | /* |
583 | * A trap may race with the mode being changed |
584 | * INSN_EMULATE<->INSN_HW. Try to emulate the instruction to |
585 | * avoid a spurious UNDEF. |
586 | */ |
587 | if (READ_ONCE(ie->current_mode) == INSN_UNDEF) |
588 | continue; |
589 | |
590 | if (ie->try_emulate(regs, insn)) |
591 | return true; |
592 | } |
593 | |
594 | return false; |
595 | } |
596 | |
597 | /* |
598 | * Invoked as core_initcall, which guarantees that the instruction |
599 | * emulation is ready for userspace. |
600 | */ |
601 | static int __init armv8_deprecated_init(void) |
602 | { |
603 | #ifdef CONFIG_SETEND_EMULATION |
604 | if (!system_supports_mixed_endian_el0()) { |
605 | insn_setend.status = INSN_UNAVAILABLE; |
606 | pr_info("setend instruction emulation is not supported on this system\n" ); |
607 | } |
608 | |
609 | #endif |
610 | for (int i = 0; i < ARRAY_SIZE(insn_emulations); i++) { |
611 | struct insn_emulation *ie = insn_emulations[i]; |
612 | |
613 | if (ie->status == INSN_UNAVAILABLE) |
614 | continue; |
615 | |
616 | register_insn_emulation(insn: ie); |
617 | } |
618 | |
619 | cpuhp_setup_state_nocalls(state: CPUHP_AP_ARM64_ISNDEP_STARTING, |
620 | name: "arm64/isndep:starting" , |
621 | startup: run_all_insn_set_hw_mode, NULL); |
622 | return 0; |
623 | } |
624 | |
625 | core_initcall(armv8_deprecated_init); |
626 | |