arm64: Add framework for legacy instruction emulation
Typically, providing support for legacy instructions requires emulating the behaviour of instructions whose encodings have become undefined. If the instructions haven't been removed from the architecture, there maybe an option in the implementation to turn on/off the support for these instructions. Create common infrastructure to support legacy instruction emulation. In addition to emulation, also provide an option to support hardware execution when supported. The default execution mode (one of undef, emulate, hw exeuction) is dependent on the state of the instruction (deprecated or obsolete) in the architecture and can specified at the time of registering the instruction handlers. The runtime state of the emulation can be controlled by writing to individual nodes in sysctl. The expected default behaviour is documented as part of this patch. Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Punit Agrawal <punit.agrawal@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
0be0e44c18
commit
587064b610
33
Documentation/arm64/legacy_instructions.txt
Normal file
33
Documentation/arm64/legacy_instructions.txt
Normal file
|
@ -0,0 +1,33 @@
|
|||
The arm64 port of the Linux kernel provides infrastructure to support
|
||||
emulation of instructions which have been deprecated, or obsoleted in
|
||||
the architecture. The infrastructure code uses undefined instruction
|
||||
hooks to support emulation. Where available it also allows turning on
|
||||
the instruction execution in hardware.
|
||||
|
||||
The emulation mode can be controlled by writing to sysctl nodes
|
||||
(/proc/sys/abi). The following explains the different execution
|
||||
behaviours and the corresponding values of the sysctl nodes -
|
||||
|
||||
* Undef
|
||||
Value: 0
|
||||
Generates undefined instruction abort. Default for instructions that
|
||||
have been obsoleted in the architecture, e.g., SWP
|
||||
|
||||
* Emulate
|
||||
Value: 1
|
||||
Uses software emulation. To aid migration of software, in this mode
|
||||
usage of emulated instruction is traced as well as rate limited
|
||||
warnings are issued. This is the default for deprecated
|
||||
instructions, .e.g., CP15 barriers
|
||||
|
||||
* Hardware Execution
|
||||
Value: 2
|
||||
Although marked as deprecated, some implementations may support the
|
||||
enabling/disabling of hardware support for the execution of these
|
||||
instructions. Using hardware execution generally provides better
|
||||
performance, but at the loss of ability to gather runtime statistics
|
||||
about the use of the deprecated instructions.
|
||||
|
||||
The default mode depends on the status of the instruction in the
|
||||
architecture. Deprecated instructions should default to emulation
|
||||
while obsolete instructions must be undefined by default.
|
|
@ -164,6 +164,24 @@ config ARCH_XGENE
|
|||
help
|
||||
This enables support for AppliedMicro X-Gene SOC Family
|
||||
|
||||
comment "Processor Features"
|
||||
|
||||
menuconfig ARMV8_DEPRECATED
|
||||
bool "Emulate deprecated/obsolete ARMv8 instructions"
|
||||
depends on COMPAT
|
||||
help
|
||||
Legacy software support may require certain instructions
|
||||
that have been deprecated or obsoleted in the architecture.
|
||||
|
||||
Enable this config to enable selective emulation of these
|
||||
features.
|
||||
|
||||
If unsure, say Y
|
||||
|
||||
if ARMV8_DEPRECATED
|
||||
|
||||
endif
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Bus support"
|
||||
|
|
|
@ -32,6 +32,7 @@ arm64-obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
|||
arm64-obj-$(CONFIG_KGDB) += kgdb.o
|
||||
arm64-obj-$(CONFIG_EFI) += efi.o efi-stub.o efi-entry.o
|
||||
arm64-obj-$(CONFIG_PCI) += pci.o
|
||||
arm64-obj-$(CONFIG_ARMV8_DEPRECATED) += armv8_deprecated.o
|
||||
|
||||
obj-y += $(arm64-obj-y) vdso/
|
||||
obj-m += $(arm64-obj-m)
|
||||
|
|
216
arch/arm64/kernel/armv8_deprecated.c
Normal file
216
arch/arm64/kernel/armv8_deprecated.c
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright (C) 2014 ARM Limited
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysctl.h>
|
||||
|
||||
#include <asm/traps.h>
|
||||
|
||||
/*
|
||||
* The runtime support for deprecated instruction support can be in one of
|
||||
* following three states -
|
||||
*
|
||||
* 0 = undef
|
||||
* 1 = emulate (software emulation)
|
||||
* 2 = hw (supported in hardware)
|
||||
*/
|
||||
enum insn_emulation_mode {
|
||||
INSN_UNDEF,
|
||||
INSN_EMULATE,
|
||||
INSN_HW,
|
||||
};
|
||||
|
||||
enum legacy_insn_status {
|
||||
INSN_DEPRECATED,
|
||||
INSN_OBSOLETE,
|
||||
};
|
||||
|
||||
struct insn_emulation_ops {
|
||||
const char *name;
|
||||
enum legacy_insn_status status;
|
||||
struct undef_hook *hooks;
|
||||
int (*set_hw_mode)(bool enable);
|
||||
};
|
||||
|
||||
struct insn_emulation {
|
||||
struct list_head node;
|
||||
struct insn_emulation_ops *ops;
|
||||
int current_mode;
|
||||
int min;
|
||||
int max;
|
||||
};
|
||||
|
||||
static LIST_HEAD(insn_emulation);
|
||||
static int nr_insn_emulated;
|
||||
static DEFINE_RAW_SPINLOCK(insn_emulation_lock);
|
||||
|
||||
static void register_emulation_hooks(struct insn_emulation_ops *ops)
|
||||
{
|
||||
struct undef_hook *hook;
|
||||
|
||||
BUG_ON(!ops->hooks);
|
||||
|
||||
for (hook = ops->hooks; hook->instr_mask; hook++)
|
||||
register_undef_hook(hook);
|
||||
|
||||
pr_notice("Registered %s emulation handler\n", ops->name);
|
||||
}
|
||||
|
||||
static void remove_emulation_hooks(struct insn_emulation_ops *ops)
|
||||
{
|
||||
struct undef_hook *hook;
|
||||
|
||||
BUG_ON(!ops->hooks);
|
||||
|
||||
for (hook = ops->hooks; hook->instr_mask; hook++)
|
||||
unregister_undef_hook(hook);
|
||||
|
||||
pr_notice("Removed %s emulation handler\n", ops->name);
|
||||
}
|
||||
|
||||
static int update_insn_emulation_mode(struct insn_emulation *insn,
|
||||
enum insn_emulation_mode prev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch (prev) {
|
||||
case INSN_UNDEF: /* Nothing to be done */
|
||||
break;
|
||||
case INSN_EMULATE:
|
||||
remove_emulation_hooks(insn->ops);
|
||||
break;
|
||||
case INSN_HW:
|
||||
if (insn->ops->set_hw_mode) {
|
||||
insn->ops->set_hw_mode(false);
|
||||
pr_notice("Disabled %s support\n", insn->ops->name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (insn->current_mode) {
|
||||
case INSN_UNDEF:
|
||||
break;
|
||||
case INSN_EMULATE:
|
||||
register_emulation_hooks(insn->ops);
|
||||
break;
|
||||
case INSN_HW:
|
||||
if (insn->ops->set_hw_mode && insn->ops->set_hw_mode(true))
|
||||
pr_notice("Enabled %s support\n", insn->ops->name);
|
||||
else
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void register_insn_emulation(struct insn_emulation_ops *ops)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct insn_emulation *insn;
|
||||
|
||||
insn = kzalloc(sizeof(*insn), GFP_KERNEL);
|
||||
insn->ops = ops;
|
||||
insn->min = INSN_UNDEF;
|
||||
|
||||
switch (ops->status) {
|
||||
case INSN_DEPRECATED:
|
||||
insn->current_mode = INSN_EMULATE;
|
||||
insn->max = INSN_HW;
|
||||
break;
|
||||
case INSN_OBSOLETE:
|
||||
insn->current_mode = INSN_UNDEF;
|
||||
insn->max = INSN_EMULATE;
|
||||
break;
|
||||
}
|
||||
|
||||
raw_spin_lock_irqsave(&insn_emulation_lock, flags);
|
||||
list_add(&insn->node, &insn_emulation);
|
||||
nr_insn_emulated++;
|
||||
raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
|
||||
|
||||
/* Register any handlers if required */
|
||||
update_insn_emulation_mode(insn, INSN_UNDEF);
|
||||
}
|
||||
|
||||
static int emulation_proc_handler(struct ctl_table *table, int write,
|
||||
void __user *buffer, size_t *lenp,
|
||||
loff_t *ppos)
|
||||
{
|
||||
int ret = 0;
|
||||
struct insn_emulation *insn = (struct insn_emulation *) table->data;
|
||||
enum insn_emulation_mode prev_mode = insn->current_mode;
|
||||
|
||||
table->data = &insn->current_mode;
|
||||
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
|
||||
|
||||
if (ret || !write || prev_mode == insn->current_mode)
|
||||
goto ret;
|
||||
|
||||
ret = update_insn_emulation_mode(insn, prev_mode);
|
||||
if (!ret) {
|
||||
/* Mode change failed, revert to previous mode. */
|
||||
insn->current_mode = prev_mode;
|
||||
update_insn_emulation_mode(insn, INSN_UNDEF);
|
||||
}
|
||||
ret:
|
||||
table->data = insn;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct ctl_table ctl_abi[] = {
|
||||
{
|
||||
.procname = "abi",
|
||||
.mode = 0555,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
static void register_insn_emulation_sysctl(struct ctl_table *table)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i = 0;
|
||||
struct insn_emulation *insn;
|
||||
struct ctl_table *insns_sysctl, *sysctl;
|
||||
|
||||
insns_sysctl = kzalloc(sizeof(*sysctl) * (nr_insn_emulated + 1),
|
||||
GFP_KERNEL);
|
||||
|
||||
raw_spin_lock_irqsave(&insn_emulation_lock, flags);
|
||||
list_for_each_entry(insn, &insn_emulation, node) {
|
||||
sysctl = &insns_sysctl[i];
|
||||
|
||||
sysctl->mode = 0644;
|
||||
sysctl->maxlen = sizeof(int);
|
||||
|
||||
sysctl->procname = insn->ops->name;
|
||||
sysctl->data = insn;
|
||||
sysctl->extra1 = &insn->min;
|
||||
sysctl->extra2 = &insn->max;
|
||||
sysctl->proc_handler = emulation_proc_handler;
|
||||
i++;
|
||||
}
|
||||
raw_spin_unlock_irqrestore(&insn_emulation_lock, flags);
|
||||
|
||||
table->child = insns_sysctl;
|
||||
register_sysctl_table(table);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoked as late_initcall, since not needed before init spawned.
|
||||
*/
|
||||
static int __init armv8_deprecated_init(void)
|
||||
{
|
||||
register_insn_emulation_sysctl(ctl_abi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
late_initcall(armv8_deprecated_init);
|
Loading…
Reference in a new issue