blob: db3b79da6a4809773f9f41bde35fd31ff285abcc [file] [log] [blame]
Punit Agrawal587064b2014-11-18 11:41:24 +00001/*
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);