arm64: Add framework for legacy instruction emulation
authorPunit Agrawal <punit.agrawal@arm.com>
Tue, 18 Nov 2014 11:41:24 +0000 (11:41 +0000)
committerWill Deacon <will.deacon@arm.com>
Thu, 20 Nov 2014 16:33:53 +0000 (16:33 +0000)
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>
Documentation/arm64/legacy_instructions.txt [new file with mode: 0644]
arch/arm64/Kconfig
arch/arm64/kernel/Makefile
arch/arm64/kernel/armv8_deprecated.c [new file with mode: 0644]

diff --git a/Documentation/arm64/legacy_instructions.txt b/Documentation/arm64/legacy_instructions.txt
new file mode 100644 (file)
index 0000000..49d4867
--- /dev/null
@@ -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.
index 1e0e4671dd2583b6cf586f6048308949f0cf66f8..aa8f4bea373869d878e72ba9eb4aceab365b026f 100644 (file)
@@ -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"
index a4d8671d6d111d55c4e05a90520aebab04ff91a5..84e9e5153efe73e10b8098f3e8a1611c2a500e23 100644 (file)
@@ -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)
diff --git a/arch/arm64/kernel/armv8_deprecated.c b/arch/arm64/kernel/armv8_deprecated.c
new file mode 100644 (file)
index 0000000..db3b79d
--- /dev/null
@@ -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);