[PATCHv4 3/5] arm64: Port SWP/SWPB emulation support from arm
Catalin Marinas
catalin.marinas at arm.com
Fri Nov 14 10:24:26 PST 2014
On Wed, Nov 12, 2014 at 11:44:50AM +0000, Punit Agrawal wrote:
> Documentation/arm64/legacy_instructions.txt | 40 +++
> arch/arm64/Kconfig | 39 +++
> arch/arm64/include/asm/insn.h | 6 +
> arch/arm64/kernel/Makefile | 1 +
> arch/arm64/kernel/armv8_deprecated.c | 419 ++++++++++++++++++++++++++++
> arch/arm64/kernel/insn.c | 8 +
I would have split this in two patches, one adding the framework and
another the emulation. I'm not too bothered (can leave them as a single
patch) but I have some comments below for grouping the code a bit as it
looks like it's a mix of API for emulation, swp emulation, the API again
making it hard to follow.
> --- /dev/null
> +++ b/arch/arm64/kernel/armv8_deprecated.c
> @@ -0,0 +1,419 @@
> +/*
> + * 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/kernel.h>
> +#include <linux/list.h>
> +#include <linux/perf_event.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/sysctl.h>
> +
> +#include <asm/insn.h>
> +#include <asm/opcodes.h>
> +#include <asm/system_misc.h>
> +#include <asm/traps.h>
> +#include <asm/uaccess.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;
> +};
Please move the register_* etc. functions here and let the swp emulation
grouped together.
> +/*
> + * Only emulate SWP/SWPB executed in ARM state/User mode.
> + * The kernel must be SWP free and SWP{B} does not exist in Thumb.
> + */
> +static struct undef_hook swp_hooks[] = {
> + {
> + .instr_mask = 0x0fb00ff0,
> + .instr_val = 0x01000090,
> + .pstate_mask = COMPAT_PSR_MODE_MASK,
> + .pstate_val = COMPAT_PSR_MODE_USR,
> + .fn = swp_handler
> + },
> + { }
> +};
It may be the email client but it looks like too much indentation here.
> +
> +static struct insn_emulation_ops swp_ops = {
> + .name = "swp",
> + .status = INSN_OBSOLETE,
> + .hooks = swp_hooks,
> + .set_hw_mode = NULL,
> +};
> +
Code below up to armv8_deprecated_init should be moved earlier.
> +static LIST_HEAD(insn_emulation);
> +static int nr_insn_emulated;
> +static DEFINE_RAW_SPINLOCK(insn_emulation_lock);
> +
> +static bool register_emulation_hooks(struct insn_emulation_ops *ops)
> +{
> + bool success = true;
> + struct undef_hook *hook;
> +
> + if (!ops->hooks) {
> + success = false;
> + goto ret;
> + }
> +
> + for (hook = ops->hooks; hook->instr_mask; hook++)
> + if (register_undef_hook(hook)) {
> + success = false;
> + break;
> + }
> +
> +ret:
> + return success;
> +}
I don't really think this code needs so much checking and error
returning. As I already commented, register_undef_hook() should not
return anything and for the ops->hooks check I think we could just use
BUG_ON(!ops->hooks).
Do we need to add support for instructions that can't be emulated? I
wouldn't go there yet until we see some real use-case.
> +static void remove_emulation_hooks(struct insn_emulation_ops *ops)
> +{
> + struct undef_hook *hook;
> +
> + if (!ops->hooks)
> + return;
Same here.
> +
> + 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:
> + if (!register_emulation_hooks(insn->ops)) {
> + insn->current_mode = INSN_UNDEF;
> + ret = -EINVAL;
> + } else
> + pr_notice("Registered %s emulation handler\n",
> + insn->ops->name);
> + break;
If we assume that register_emulation_hooks() doesn't fail, we could
simplify slightly here.
> + 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 {
> + insn->current_mode = INSN_UNDEF;
> + ret = -EINVAL;
> + }
> + break;
I would rather let it fall back to the previous. So in the caller, we
could re-instate the previous mode rather than undef.
> +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);
After this instruction, something like below to restore the previous
mode:
if (!ret) {
enum insn_emulation mode = insn_mode->current_mode;
insn->current_mode = prev_mode;
update_insn_emulation_mode(insn, prev_mode);
}
--
Catalin
More information about the linux-arm-kernel
mailing list