aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm64/kernel
diff options
context:
space:
mode:
authorPunit Agrawal <punit.agrawal@arm.com>2014-11-18 06:41:24 -0500
committerWill Deacon <will.deacon@arm.com>2014-11-20 11:33:53 -0500
commit587064b610c703f259317d00dc37bf6d40f4fc74 (patch)
tree4170a2e03c6d046c45811f22ab662b988d73ae48 /arch/arm64/kernel
parent0be0e44c182c4f13df13903fd1377671d157d7b7 (diff)
arm64: Add framework for legacy instruction emulation
Typically, providing support for legacy instructions requires emulating the behaviour of instructions whose encodings have become undefined. If the instructions haven't been removed from the architecture, there maybe an option in the implementation to turn on/off the support for these instructions. Create common infrastructure to support legacy instruction emulation. In addition to emulation, also provide an option to support hardware execution when supported. The default execution mode (one of undef, emulate, hw exeuction) is dependent on the state of the instruction (deprecated or obsolete) in the architecture and can specified at the time of registering the instruction handlers. The runtime state of the emulation can be controlled by writing to individual nodes in sysctl. The expected default behaviour is documented as part of this patch. Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Punit Agrawal <punit.agrawal@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
Diffstat (limited to 'arch/arm64/kernel')
-rw-r--r--arch/arm64/kernel/Makefile1
-rw-r--r--arch/arm64/kernel/armv8_deprecated.c216
2 files changed, 217 insertions, 0 deletions
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index a4d8671d6d11..84e9e5153efe 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -32,6 +32,7 @@ arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o
32arm64-obj-$(CONFIG_KGDB) += kgdb.o 32arm64-obj-$(CONFIG_KGDB) += kgdb.o
33arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o 33arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o
34arm64-obj-$(CONFIG_PCI) += pci.o 34arm64-obj-$(CONFIG_PCI) += pci.o
35arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o
35 36
36obj-y += $(arm64-obj-y) vdso/ 37obj-y += $(arm64-obj-y) vdso/
37obj-m += $(arm64-obj-m) 38obj-m += $(arm64-obj-m)
diff --git a/arch/arm64/kernel/armv8_deprecated.c b/arch/arm64/kernel/armv8_deprecated.c
new file mode 100644
index 000000000000..db3b79da6a48
--- /dev/null
+++ b/arch/arm64/kernel/armv8_deprecated.c
@@ -0,0 +1,216 @@
1/*
2 * Copyright (C) 2014 ARM Limited
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/init.h>
10#include <linux/list.h>
11#include <linux/slab.h>
12#include <linux/sysctl.h>
13
14#include <asm/traps.h>
15
16/*
17 * The runtime support for deprecated instruction support can be in one of
18 * following three states -
19 *
20 * 0 = undef
21 * 1 = emulate (software emulation)
22 * 2 = hw (supported in hardware)
23 */
24enum insn_emulation_mode {
25 INSN_UNDEF,
26 INSN_EMULATE,
27 INSN_HW,
28};
29
30enum legacy_insn_status {
31 INSN_DEPRECATED,
32 INSN_OBSOLETE,
33};
34
35struct insn_emulation_ops {
36 const char *name;
37 enum legacy_insn_status status;
38 struct undef_hook *hooks;
39 int (*set_hw_mode)(bool enable);
40};
41
42struct insn_emulation {
43 struct list_head node;
44 struct insn_emulation_ops *ops;
45 int current_mode;
46 int min;
47 int max;
48};
49
50static LIST_HEAD(insn_emulation);
51static int nr_insn_emulated;
52static DEFINE_RAW_SPINLOCK(insn_emulation_lock);
53
54static void register_emulation_hooks(struct insn_emulation_ops *ops)
55{
56 struct undef_hook *hook;
57
58 BUG_ON(!ops->hooks);
59
60 for (hook = ops->hooks; hook->instr_mask; hook++)
61 register_undef_hook(hook);
62
63 pr_notice("Registered %s emulation handler\n", ops->name);
64}
65
66static void remove_emulation_hooks(struct insn_emulation_ops *ops)
67{
68 struct undef_hook *hook;
69
70 BUG_ON(!ops->hooks);
71
72 for (hook = ops->hooks; hook->instr_mask; hook++)
73 unregister_undef_hook(hook);
74
75 pr_notice("Removed %s emulation handler\n", ops->name);
76}
77
78static int update_insn_emulation_mode(struct insn_emulation *insn,
79 enum insn_emulation_mode prev)
80{
81 int ret = 0;
82
83 switch (prev) {
84 case INSN_UNDEF: /* Nothing to be done */
85 break;
86 case INSN_EMULATE:
87 remove_emulation_hooks(insn->ops);
88 break;
89 case INSN_HW:
90 if (insn->ops->set_hw_mode) {
91 insn->ops->set_hw_mode(false);
92 pr_notice("Disabled %s support\n", insn->ops->name);
93 }
94 break;
95 }
96
97 switch (insn->current_mode) {
98 case INSN_UNDEF:
99 break;
100 case INSN_EMULATE:
101 register_emulation_hooks(insn->ops);
102 break;
103 case INSN_HW:
104 if (insn->ops->set_hw_mode && insn->ops->set_hw_mode(true))
105 pr_notice("Enabled %s support\n", insn->ops->name);
106 else
107 ret = -EINVAL;
108 break;
109 }
110
111 return ret;
112}
113
114static void register_insn_emulation(struct insn_emulation_ops *ops)
115{
116 unsigned long flags;
117 struct insn_emulation *insn;
118
119 insn = kzalloc(sizeof(*insn), GFP_KERNEL);
120 insn->ops = ops;
121 insn->min = INSN_UNDEF;
122
123 switch (ops->status) {
124 case INSN_DEPRECATED:
125 insn->current_mode = INSN_EMULATE;
126 insn->max = INSN_HW;
127 break;
128 case INSN_OBSOLETE:
129 insn->current_mode = INSN_UNDEF;
130 insn->max = INSN_EMULATE;
131 break;
132 }
133
134 raw_spin_lock_irqsave(&insn_emulation_lock, flags);
135 list_add(&insn->node, &insn_emulation);
136 nr_insn_emulated++;
137 raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
138
139 /* Register any handlers if required */
140 update_insn_emulation_mode(insn, INSN_UNDEF);
141}
142
143static int emulation_proc_handler(struct ctl_table *table, int write,
144 void __user *buffer, size_t *lenp,
145 loff_t *ppos)
146{
147 int ret = 0;
148 struct insn_emulation *insn = (struct insn_emulation *) table->data;
149 enum insn_emulation_mode prev_mode = insn->current_mode;
150
151 table->data = &insn->current_mode;
152 ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
153
154 if (ret || !write || prev_mode == insn->current_mode)
155 goto ret;
156
157 ret = update_insn_emulation_mode(insn, prev_mode);
158 if (!ret) {
159 /* Mode change failed, revert to previous mode. */
160 insn->current_mode = prev_mode;
161 update_insn_emulation_mode(insn, INSN_UNDEF);
162 }
163ret:
164 table->data = insn;
165 return ret;
166}
167
168static struct ctl_table ctl_abi[] = {
169 {
170 .procname = "abi",
171 .mode = 0555,
172 },
173 { }
174};
175
176static void register_insn_emulation_sysctl(struct ctl_table *table)
177{
178 unsigned long flags;
179 int i = 0;
180 struct insn_emulation *insn;
181 struct ctl_table *insns_sysctl, *sysctl;
182
183 insns_sysctl = kzalloc(sizeof(*sysctl) * (nr_insn_emulated + 1),
184 GFP_KERNEL);
185
186 raw_spin_lock_irqsave(&insn_emulation_lock, flags);
187 list_for_each_entry(insn, &insn_emulation, node) {
188 sysctl = &insns_sysctl[i];
189
190 sysctl->mode = 0644;
191 sysctl->maxlen = sizeof(int);
192
193 sysctl->procname = insn->ops->name;
194 sysctl->data = insn;
195 sysctl->extra1 = &insn->min;
196 sysctl->extra2 = &insn->max;
197 sysctl->proc_handler = emulation_proc_handler;
198 i++;
199 }
200 raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
201
202 table->child = insns_sysctl;
203 register_sysctl_table(table);
204}
205
206/*
207 * Invoked as late_initcall, since not needed before init spawned.
208 */
209static int __init armv8_deprecated_init(void)
210{
211 register_insn_emulation_sysctl(ctl_abi);
212
213 return 0;
214}
215
216late_initcall(armv8_deprecated_init);