armv8_deprecated.c revision ab12a809254a343a42f7655e752a9d2eb65364db
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);
217