Punit Agrawal | 587064b | 2014-11-18 11:41:24 +0000 | [diff] [blame^] | 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 | */ |
| 24 | enum insn_emulation_mode { |
| 25 | INSN_UNDEF, |
| 26 | INSN_EMULATE, |
| 27 | INSN_HW, |
| 28 | }; |
| 29 | |
| 30 | enum legacy_insn_status { |
| 31 | INSN_DEPRECATED, |
| 32 | INSN_OBSOLETE, |
| 33 | }; |
| 34 | |
| 35 | struct 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 | |
| 42 | struct 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 | |
| 50 | static LIST_HEAD(insn_emulation); |
| 51 | static int nr_insn_emulated; |
| 52 | static DEFINE_RAW_SPINLOCK(insn_emulation_lock); |
| 53 | |
| 54 | static 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 | |
| 66 | static 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 | |
| 78 | static 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 | |
| 114 | static 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 | |
| 143 | static 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 | } |
| 163 | ret: |
| 164 | table->data = insn; |
| 165 | return ret; |
| 166 | } |
| 167 | |
| 168 | static struct ctl_table ctl_abi[] = { |
| 169 | { |
| 170 | .procname = "abi", |
| 171 | .mode = 0555, |
| 172 | }, |
| 173 | { } |
| 174 | }; |
| 175 | |
| 176 | static 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 | */ |
| 209 | static int __init armv8_deprecated_init(void) |
| 210 | { |
| 211 | register_insn_emulation_sysctl(ctl_abi); |
| 212 | |
| 213 | return 0; |
| 214 | } |
| 215 | |
| 216 | late_initcall(armv8_deprecated_init); |