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