1587064b6SPunit Agrawal /*
2587064b6SPunit Agrawal  *  Copyright (C) 2014 ARM Limited
3587064b6SPunit Agrawal  *
4587064b6SPunit Agrawal  * This program is free software; you can redistribute it and/or modify
5587064b6SPunit Agrawal  * it under the terms of the GNU General Public License version 2 as
6587064b6SPunit Agrawal  * published by the Free Software Foundation.
7587064b6SPunit Agrawal  */
8587064b6SPunit Agrawal 
9587064b6SPunit Agrawal #include <linux/init.h>
10587064b6SPunit Agrawal #include <linux/list.h>
11587064b6SPunit Agrawal #include <linux/slab.h>
12587064b6SPunit Agrawal #include <linux/sysctl.h>
13587064b6SPunit Agrawal 
14587064b6SPunit Agrawal #include <asm/traps.h>
15587064b6SPunit Agrawal 
16587064b6SPunit Agrawal /*
17587064b6SPunit Agrawal  * The runtime support for deprecated instruction support can be in one of
18587064b6SPunit Agrawal  * following three states -
19587064b6SPunit Agrawal  *
20587064b6SPunit Agrawal  * 0 = undef
21587064b6SPunit Agrawal  * 1 = emulate (software emulation)
22587064b6SPunit Agrawal  * 2 = hw (supported in hardware)
23587064b6SPunit Agrawal  */
24587064b6SPunit Agrawal enum insn_emulation_mode {
25587064b6SPunit Agrawal 	INSN_UNDEF,
26587064b6SPunit Agrawal 	INSN_EMULATE,
27587064b6SPunit Agrawal 	INSN_HW,
28587064b6SPunit Agrawal };
29587064b6SPunit Agrawal 
30587064b6SPunit Agrawal enum legacy_insn_status {
31587064b6SPunit Agrawal 	INSN_DEPRECATED,
32587064b6SPunit Agrawal 	INSN_OBSOLETE,
33587064b6SPunit Agrawal };
34587064b6SPunit Agrawal 
35587064b6SPunit Agrawal struct insn_emulation_ops {
36587064b6SPunit Agrawal 	const char		*name;
37587064b6SPunit Agrawal 	enum legacy_insn_status	status;
38587064b6SPunit Agrawal 	struct undef_hook	*hooks;
39587064b6SPunit Agrawal 	int			(*set_hw_mode)(bool enable);
40587064b6SPunit Agrawal };
41587064b6SPunit Agrawal 
42587064b6SPunit Agrawal struct insn_emulation {
43587064b6SPunit Agrawal 	struct list_head node;
44587064b6SPunit Agrawal 	struct insn_emulation_ops *ops;
45587064b6SPunit Agrawal 	int current_mode;
46587064b6SPunit Agrawal 	int min;
47587064b6SPunit Agrawal 	int max;
48587064b6SPunit Agrawal };
49587064b6SPunit Agrawal 
50587064b6SPunit Agrawal static LIST_HEAD(insn_emulation);
51587064b6SPunit Agrawal static int nr_insn_emulated;
52587064b6SPunit Agrawal static DEFINE_RAW_SPINLOCK(insn_emulation_lock);
53587064b6SPunit Agrawal 
54587064b6SPunit Agrawal static void register_emulation_hooks(struct insn_emulation_ops *ops)
55587064b6SPunit Agrawal {
56587064b6SPunit Agrawal 	struct undef_hook *hook;
57587064b6SPunit Agrawal 
58587064b6SPunit Agrawal 	BUG_ON(!ops->hooks);
59587064b6SPunit Agrawal 
60587064b6SPunit Agrawal 	for (hook = ops->hooks; hook->instr_mask; hook++)
61587064b6SPunit Agrawal 		register_undef_hook(hook);
62587064b6SPunit Agrawal 
63587064b6SPunit Agrawal 	pr_notice("Registered %s emulation handler\n", ops->name);
64587064b6SPunit Agrawal }
65587064b6SPunit Agrawal 
66587064b6SPunit Agrawal static void remove_emulation_hooks(struct insn_emulation_ops *ops)
67587064b6SPunit Agrawal {
68587064b6SPunit Agrawal 	struct undef_hook *hook;
69587064b6SPunit Agrawal 
70587064b6SPunit Agrawal 	BUG_ON(!ops->hooks);
71587064b6SPunit Agrawal 
72587064b6SPunit Agrawal 	for (hook = ops->hooks; hook->instr_mask; hook++)
73587064b6SPunit Agrawal 		unregister_undef_hook(hook);
74587064b6SPunit Agrawal 
75587064b6SPunit Agrawal 	pr_notice("Removed %s emulation handler\n", ops->name);
76587064b6SPunit Agrawal }
77587064b6SPunit Agrawal 
78587064b6SPunit Agrawal static int update_insn_emulation_mode(struct insn_emulation *insn,
79587064b6SPunit Agrawal 				       enum insn_emulation_mode prev)
80587064b6SPunit Agrawal {
81587064b6SPunit Agrawal 	int ret = 0;
82587064b6SPunit Agrawal 
83587064b6SPunit Agrawal 	switch (prev) {
84587064b6SPunit Agrawal 	case INSN_UNDEF: /* Nothing to be done */
85587064b6SPunit Agrawal 		break;
86587064b6SPunit Agrawal 	case INSN_EMULATE:
87587064b6SPunit Agrawal 		remove_emulation_hooks(insn->ops);
88587064b6SPunit Agrawal 		break;
89587064b6SPunit Agrawal 	case INSN_HW:
90587064b6SPunit Agrawal 		if (insn->ops->set_hw_mode) {
91587064b6SPunit Agrawal 			insn->ops->set_hw_mode(false);
92587064b6SPunit Agrawal 			pr_notice("Disabled %s support\n", insn->ops->name);
93587064b6SPunit Agrawal 		}
94587064b6SPunit Agrawal 		break;
95587064b6SPunit Agrawal 	}
96587064b6SPunit Agrawal 
97587064b6SPunit Agrawal 	switch (insn->current_mode) {
98587064b6SPunit Agrawal 	case INSN_UNDEF:
99587064b6SPunit Agrawal 		break;
100587064b6SPunit Agrawal 	case INSN_EMULATE:
101587064b6SPunit Agrawal 		register_emulation_hooks(insn->ops);
102587064b6SPunit Agrawal 		break;
103587064b6SPunit Agrawal 	case INSN_HW:
104587064b6SPunit Agrawal 		if (insn->ops->set_hw_mode && insn->ops->set_hw_mode(true))
105587064b6SPunit Agrawal 			pr_notice("Enabled %s support\n", insn->ops->name);
106587064b6SPunit Agrawal 		else
107587064b6SPunit Agrawal 			ret = -EINVAL;
108587064b6SPunit Agrawal 		break;
109587064b6SPunit Agrawal 	}
110587064b6SPunit Agrawal 
111587064b6SPunit Agrawal 	return ret;
112587064b6SPunit Agrawal }
113587064b6SPunit Agrawal 
114587064b6SPunit Agrawal static void register_insn_emulation(struct insn_emulation_ops *ops)
115587064b6SPunit Agrawal {
116587064b6SPunit Agrawal 	unsigned long flags;
117587064b6SPunit Agrawal 	struct insn_emulation *insn;
118587064b6SPunit Agrawal 
119587064b6SPunit Agrawal 	insn = kzalloc(sizeof(*insn), GFP_KERNEL);
120587064b6SPunit Agrawal 	insn->ops = ops;
121587064b6SPunit Agrawal 	insn->min = INSN_UNDEF;
122587064b6SPunit Agrawal 
123587064b6SPunit Agrawal 	switch (ops->status) {
124587064b6SPunit Agrawal 	case INSN_DEPRECATED:
125587064b6SPunit Agrawal 		insn->current_mode = INSN_EMULATE;
126587064b6SPunit Agrawal 		insn->max = INSN_HW;
127587064b6SPunit Agrawal 		break;
128587064b6SPunit Agrawal 	case INSN_OBSOLETE:
129587064b6SPunit Agrawal 		insn->current_mode = INSN_UNDEF;
130587064b6SPunit Agrawal 		insn->max = INSN_EMULATE;
131587064b6SPunit Agrawal 		break;
132587064b6SPunit Agrawal 	}
133587064b6SPunit Agrawal 
134587064b6SPunit Agrawal 	raw_spin_lock_irqsave(&insn_emulation_lock, flags);
135587064b6SPunit Agrawal 	list_add(&insn->node, &insn_emulation);
136587064b6SPunit Agrawal 	nr_insn_emulated++;
137587064b6SPunit Agrawal 	raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
138587064b6SPunit Agrawal 
139587064b6SPunit Agrawal 	/* Register any handlers if required */
140587064b6SPunit Agrawal 	update_insn_emulation_mode(insn, INSN_UNDEF);
141587064b6SPunit Agrawal }
142587064b6SPunit Agrawal 
143587064b6SPunit Agrawal static int emulation_proc_handler(struct ctl_table *table, int write,
144587064b6SPunit Agrawal 				  void __user *buffer, size_t *lenp,
145587064b6SPunit Agrawal 				  loff_t *ppos)
146587064b6SPunit Agrawal {
147587064b6SPunit Agrawal 	int ret = 0;
148587064b6SPunit Agrawal 	struct insn_emulation *insn = (struct insn_emulation *) table->data;
149587064b6SPunit Agrawal 	enum insn_emulation_mode prev_mode = insn->current_mode;
150587064b6SPunit Agrawal 
151587064b6SPunit Agrawal 	table->data = &insn->current_mode;
152587064b6SPunit Agrawal 	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
153587064b6SPunit Agrawal 
154587064b6SPunit Agrawal 	if (ret || !write || prev_mode == insn->current_mode)
155587064b6SPunit Agrawal 		goto ret;
156587064b6SPunit Agrawal 
157587064b6SPunit Agrawal 	ret = update_insn_emulation_mode(insn, prev_mode);
158587064b6SPunit Agrawal 	if (!ret) {
159587064b6SPunit Agrawal 		/* Mode change failed, revert to previous mode. */
160587064b6SPunit Agrawal 		insn->current_mode = prev_mode;
161587064b6SPunit Agrawal 		update_insn_emulation_mode(insn, INSN_UNDEF);
162587064b6SPunit Agrawal 	}
163587064b6SPunit Agrawal ret:
164587064b6SPunit Agrawal 	table->data = insn;
165587064b6SPunit Agrawal 	return ret;
166587064b6SPunit Agrawal }
167587064b6SPunit Agrawal 
168587064b6SPunit Agrawal static struct ctl_table ctl_abi[] = {
169587064b6SPunit Agrawal 	{
170587064b6SPunit Agrawal 		.procname = "abi",
171587064b6SPunit Agrawal 		.mode = 0555,
172587064b6SPunit Agrawal 	},
173587064b6SPunit Agrawal 	{ }
174587064b6SPunit Agrawal };
175587064b6SPunit Agrawal 
176587064b6SPunit Agrawal static void register_insn_emulation_sysctl(struct ctl_table *table)
177587064b6SPunit Agrawal {
178587064b6SPunit Agrawal 	unsigned long flags;
179587064b6SPunit Agrawal 	int i = 0;
180587064b6SPunit Agrawal 	struct insn_emulation *insn;
181587064b6SPunit Agrawal 	struct ctl_table *insns_sysctl, *sysctl;
182587064b6SPunit Agrawal 
183587064b6SPunit Agrawal 	insns_sysctl = kzalloc(sizeof(*sysctl) * (nr_insn_emulated + 1),
184587064b6SPunit Agrawal 			      GFP_KERNEL);
185587064b6SPunit Agrawal 
186587064b6SPunit Agrawal 	raw_spin_lock_irqsave(&insn_emulation_lock, flags);
187587064b6SPunit Agrawal 	list_for_each_entry(insn, &insn_emulation, node) {
188587064b6SPunit Agrawal 		sysctl = &insns_sysctl[i];
189587064b6SPunit Agrawal 
190587064b6SPunit Agrawal 		sysctl->mode = 0644;
191587064b6SPunit Agrawal 		sysctl->maxlen = sizeof(int);
192587064b6SPunit Agrawal 
193587064b6SPunit Agrawal 		sysctl->procname = insn->ops->name;
194587064b6SPunit Agrawal 		sysctl->data = insn;
195587064b6SPunit Agrawal 		sysctl->extra1 = &insn->min;
196587064b6SPunit Agrawal 		sysctl->extra2 = &insn->max;
197587064b6SPunit Agrawal 		sysctl->proc_handler = emulation_proc_handler;
198587064b6SPunit Agrawal 		i++;
199587064b6SPunit Agrawal 	}
200587064b6SPunit Agrawal 	raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
201587064b6SPunit Agrawal 
202587064b6SPunit Agrawal 	table->child = insns_sysctl;
203587064b6SPunit Agrawal 	register_sysctl_table(table);
204587064b6SPunit Agrawal }
205587064b6SPunit Agrawal 
206587064b6SPunit Agrawal /*
207587064b6SPunit Agrawal  * Invoked as late_initcall, since not needed before init spawned.
208587064b6SPunit Agrawal  */
209587064b6SPunit Agrawal static int __init armv8_deprecated_init(void)
210587064b6SPunit Agrawal {
211587064b6SPunit Agrawal 	register_insn_emulation_sysctl(ctl_abi);
212587064b6SPunit Agrawal 
213587064b6SPunit Agrawal 	return 0;
214587064b6SPunit Agrawal }
215587064b6SPunit Agrawal 
216587064b6SPunit Agrawal late_initcall(armv8_deprecated_init);
217