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