diff mbox

[5/6] arm64: Port SWP/SWPB emulation support from arm

Message ID 1409048930-21598-6-git-send-email-punit.agrawal@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Punit Agrawal Aug. 26, 2014, 10:28 a.m. UTC
The SWP instruction was deprecated in the ARMv6 architecture,
superseded by the LDREX/STREX family of instructions for
load-linked/store-conditional operations. The ARMv7 multiprocessing
extensions mandate that SWP/SWPB instructions are treated as undefined
from reset, with the ability to enable them through the System Control
Register SW bit. With ARMv8, the option to enable these instructions
through System Control Register was dropped as well.

This patch ports the alternative solution to emulate the SWP and SWPB
instructions using LDXR/STXR sequences from the arm port to
arm64. Additionaly, the patch also proivdes support to log the
emulation statistics via debugfs.

Signed-off-by: Punit Agrawal <punit.agrawal@arm.com>
---
 arch/arm64/Kconfig              |   41 +++++++
 arch/arm64/include/asm/insn.h   |    9 ++
 arch/arm64/kernel/Makefile      |    1 +
 arch/arm64/kernel/insn.c        |   11 ++
 arch/arm64/kernel/v7_obsolete.c |  241 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 303 insertions(+)
 create mode 100644 arch/arm64/kernel/v7_obsolete.c

Comments

Arnd Bergmann Aug. 26, 2014, 11:32 a.m. UTC | #1
On Tuesday 26 August 2014 11:28:49 Punit Agrawal wrote:
> 
> This patch ports the alternative solution to emulate the SWP and SWPB
> instructions using LDXR/STXR sequences from the arm port to
> arm64. Additionaly, the patch also proivdes support to log the
> emulation statistics via debugfs.

I'm not sure that putting this into debugfs is a good idea in this
case: while in general that is considered a good place for this
kind of debugging information, we already have a precedent on arm32
for using procfs, and I see no reason to introduce an incompatible
interface for arm64.

You also add an interface for disabling the feature at runtime,
which we don't have on arm32, but that interface is not available
if debugfs is disabled or not mounted. Maybe a sysctl would be
more appropriate? That one could also be shared with arm32.

> +       swp_d = debugfs_create_dir("swp_emulation", arch_debugfs_dir);
> +       if (!IS_ERR_OR_NULL(swp_d)) {

Never use IS_ERR_OR_NULL(). It's enough to check for !swp_d here,
since debugfs_create_dir returns NULL on error. You don't need to
check for debugfs being disabled here because you don't do anything
in the conditional code block if it is disabled. 

	Arnd
Will Deacon Aug. 26, 2014, 12:25 p.m. UTC | #2
On Tue, Aug 26, 2014 at 12:32:23PM +0100, Arnd Bergmann wrote:
> On Tuesday 26 August 2014 11:28:49 Punit Agrawal wrote:
> > 
> > This patch ports the alternative solution to emulate the SWP and SWPB
> > instructions using LDXR/STXR sequences from the arm port to
> > arm64. Additionaly, the patch also proivdes support to log the
> > emulation statistics via debugfs.
> 
> I'm not sure that putting this into debugfs is a good idea in this
> case: while in general that is considered a good place for this
> kind of debugging information, we already have a precedent on arm32
> for using procfs, and I see no reason to introduce an incompatible
> interface for arm64.
> 
> You also add an interface for disabling the feature at runtime,
> which we don't have on arm32, but that interface is not available
> if debugfs is disabled or not mounted. Maybe a sysctl would be
> more appropriate? That one could also be shared with arm32.

One advantage of using debugfs is that it provides a place to keep
controls/statistics for any emulations that we add in the future, as opposed
to littering them around in /proc or (worse) having a mixture of the two.

Will
Arnd Bergmann Aug. 26, 2014, 1:26 p.m. UTC | #3
On Tuesday 26 August 2014 13:25:43 Will Deacon wrote:
> On Tue, Aug 26, 2014 at 12:32:23PM +0100, Arnd Bergmann wrote:
> > On Tuesday 26 August 2014 11:28:49 Punit Agrawal wrote:
> > > 
> > > This patch ports the alternative solution to emulate the SWP and SWPB
> > > instructions using LDXR/STXR sequences from the arm port to
> > > arm64. Additionaly, the patch also proivdes support to log the
> > > emulation statistics via debugfs.
> > 
> > I'm not sure that putting this into debugfs is a good idea in this
> > case: while in general that is considered a good place for this
> > kind of debugging information, we already have a precedent on arm32
> > for using procfs, and I see no reason to introduce an incompatible
> > interface for arm64.
> > 
> > You also add an interface for disabling the feature at runtime,
> > which we don't have on arm32, but that interface is not available
> > if debugfs is disabled or not mounted. Maybe a sysctl would be
> > more appropriate? That one could also be shared with arm32.
> 
> One advantage of using debugfs is that it provides a place to keep
> controls/statistics for any emulations that we add in the future, as opposed
> to littering them around in /proc or (worse) having a mixture of the two.

Yes, I understood that. I just had another idea: would it make sense to
use a tracepoint rather than a simple counter? That way you can actually
see who is using those instructions with ftrace.

You still wouldn't get the files in the same place as the enable switch
though. The easiest way to implement that switch btw would be a
module_param: It can be passed on the command line (using
swp_emulate.enable=0) or at runtime by writing to
/sys/module/swp_emulate/parameters/enable.

If we do both, there is no longer a need to have any debugfs file logic,
which is also a plus.

	Arnd
Will Deacon Aug. 26, 2014, 1:56 p.m. UTC | #4
On Tue, Aug 26, 2014 at 02:26:58PM +0100, Arnd Bergmann wrote:
> On Tuesday 26 August 2014 13:25:43 Will Deacon wrote:
> > On Tue, Aug 26, 2014 at 12:32:23PM +0100, Arnd Bergmann wrote:
> > > On Tuesday 26 August 2014 11:28:49 Punit Agrawal wrote:
> > > > 
> > > > This patch ports the alternative solution to emulate the SWP and SWPB
> > > > instructions using LDXR/STXR sequences from the arm port to
> > > > arm64. Additionaly, the patch also proivdes support to log the
> > > > emulation statistics via debugfs.
> > > 
> > > I'm not sure that putting this into debugfs is a good idea in this
> > > case: while in general that is considered a good place for this
> > > kind of debugging information, we already have a precedent on arm32
> > > for using procfs, and I see no reason to introduce an incompatible
> > > interface for arm64.
> > > 
> > > You also add an interface for disabling the feature at runtime,
> > > which we don't have on arm32, but that interface is not available
> > > if debugfs is disabled or not mounted. Maybe a sysctl would be
> > > more appropriate? That one could also be shared with arm32.
> > 
> > One advantage of using debugfs is that it provides a place to keep
> > controls/statistics for any emulations that we add in the future, as opposed
> > to littering them around in /proc or (worse) having a mixture of the two.
> 
> Yes, I understood that. I just had another idea: would it make sense to
> use a tracepoint rather than a simple counter? That way you can actually
> see who is using those instructions with ftrace.

That would also be useful for perf, where the plain `emulation fault' event
can be a little too broad.

> You still wouldn't get the files in the same place as the enable switch
> though. The easiest way to implement that switch btw would be a
> module_param: It can be passed on the command line (using
> swp_emulate.enable=0) or at runtime by writing to
> /sys/module/swp_emulate/parameters/enable.
> 
> If we do both, there is no longer a need to have any debugfs file logic,
> which is also a plus.

Sounds good to me.

Will
Catalin Marinas Aug. 27, 2014, 5:29 p.m. UTC | #5
On Tue, Aug 26, 2014 at 11:28:49AM +0100, Punit Agrawal wrote:
> The SWP instruction was deprecated in the ARMv6 architecture,
> superseded by the LDREX/STREX family of instructions for
> load-linked/store-conditional operations. The ARMv7 multiprocessing
> extensions mandate that SWP/SWPB instructions are treated as undefined
> from reset, with the ability to enable them through the System Control
> Register SW bit. With ARMv8, the option to enable these instructions
> through System Control Register was dropped as well.
> 
> This patch ports the alternative solution to emulate the SWP and SWPB
> instructions using LDXR/STXR sequences from the arm port to
> arm64. Additionaly, the patch also proivdes support to log the

s/proivdes/provides/

> emulation statistics via debugfs.
> 
> Signed-off-by: Punit Agrawal <punit.agrawal@arm.com>
> ---
>  arch/arm64/Kconfig              |   41 +++++++
>  arch/arm64/include/asm/insn.h   |    9 ++
>  arch/arm64/kernel/Makefile      |    1 +
>  arch/arm64/kernel/insn.c        |   11 ++
>  arch/arm64/kernel/v7_obsolete.c |  241 +++++++++++++++++++++++++++++++++++++++
>  5 files changed, 303 insertions(+)
>  create mode 100644 arch/arm64/kernel/v7_obsolete.c
> 
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index c52894e..ba780b1 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -142,6 +142,47 @@ config ARCH_XGENE
>  	help
>  	  This enables support for AppliedMicro X-Gene SOC Family
>  
> +comment "Processor Features"
> +
> +config V7_OBSOLETE

We could place this under a sub-menu (I haven't checked how it would
look like).

> +	bool "Emulate obsolete ARMv7 instructions"
> +	depends on COMPAT
> +	help
> +	  AArch32 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 N

I think we need to get some terminology right:

deprecated:	no longer recommended, usually with a configuration
		option to be enabled/disabled
obsolete:	no longer present

So what we are adding here is deprecated ARMv7 features that have been
obsolete in ARMv8. You could as well say "Emulate deprecated/obsolete
ARMv8 A32 instructions". "Deprecated ARMv7..." could work but we may
want to add options for features that haven't been deprecated in ARMv7,
just ARMv8 A32.

You could also change the config option (and file/function names) for
consistency to something like a32_obsolete (for config option, maybe
CONFIG_ARM64_A32_OBSOLETE, it gets even more confusing ;)).

> +config SWP_EMULATION
> +	bool "Emulate SWP/SWPB instructions"
> +	help
> +	  ARMv8 obsoletes the use of SWP/SWPB instructions such that

"... the use of A32 SWP/SWPB" to be clearer.

> +	  they are always undefined. Say Y here to enable software
> +	  emulation of these instructions for userspace (not kernel)
> +	  using LDXR/STXR.

No need to say "not kernel" since the kernel uses the A64 ISA.

> diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
> index dc1f73b..2861cc6 100644
> --- a/arch/arm64/include/asm/insn.h
> +++ b/arch/arm64/include/asm/insn.h
> @@ -105,6 +105,15 @@ bool aarch64_insn_hotpatch_safe(u32 old_insn, u32 new_insn);
>  int aarch64_insn_patch_text_nosync(void *addr, u32 insn);
>  int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt);
>  int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
> +
> +#ifdef CONFIG_V7_OBSOLETE
> +#define RN_OFFSET	16
> +#define RT_OFFSET	12
> +#define RT2_OFFSET	 0
> +
> +u32 aarch32_insn_extract_reg_num(u32 insn, int offset);
> +#endif /* CONFIG_V7_OBSOLETE */
> +
>  #endif /* __ASSEMBLY__ */
>  
>  #endif	/* __ASM_INSN_H */

[...]

> --- a/arch/arm64/kernel/insn.c
> +++ b/arch/arm64/kernel/insn.c
> @@ -302,3 +302,14 @@ u32 __kprobes aarch64_insn_gen_nop(void)
>  {
>  	return aarch64_insn_gen_hint(AARCH64_INSN_HINT_NOP);
>  }
> +
> +#ifdef CONFIG_V7_OBSOLETE
> +/*
> + * Macros/defines for extracting register numbers from instruction.
> + */
> +u32 aarch32_insn_extract_reg_num(u32 insn, int offset)
> +{
> +	return (insn & (0xf << offset)) >> offset;
> +}
> +
> +#endif /* CONFIG_V7_OBSOLETE */

I don't think we need to bother with an #ifdef here and in the header.
The code is small enough to affect the compilation time and the linker
should get rid of it if not used.
Punit Agrawal Aug. 27, 2014, 5:35 p.m. UTC | #6
Will Deacon <will.deacon@arm.com> writes:

> On Tue, Aug 26, 2014 at 02:26:58PM +0100, Arnd Bergmann wrote:
>> On Tuesday 26 August 2014 13:25:43 Will Deacon wrote:
>> > On Tue, Aug 26, 2014 at 12:32:23PM +0100, Arnd Bergmann wrote:
>> > > On Tuesday 26 August 2014 11:28:49 Punit Agrawal wrote:
>> > > > 
>> > > > This patch ports the alternative solution to emulate the SWP and SWPB
>> > > > instructions using LDXR/STXR sequences from the arm port to
>> > > > arm64. Additionaly, the patch also proivdes support to log the
>> > > > emulation statistics via debugfs.
>> > > 
>> > > I'm not sure that putting this into debugfs is a good idea in this
>> > > case: while in general that is considered a good place for this
>> > > kind of debugging information, we already have a precedent on arm32
>> > > for using procfs, and I see no reason to introduce an incompatible
>> > > interface for arm64.
>> > > 
>> > > You also add an interface for disabling the feature at runtime,
>> > > which we don't have on arm32, but that interface is not available
>> > > if debugfs is disabled or not mounted. Maybe a sysctl would be
>> > > more appropriate? That one could also be shared with arm32.
>> > 
>> > One advantage of using debugfs is that it provides a place to keep
>> > controls/statistics for any emulations that we add in the future, as opposed
>> > to littering them around in /proc or (worse) having a mixture of the two.
>> 
>> Yes, I understood that. I just had another idea: would it make sense to
>> use a tracepoint rather than a simple counter? That way you can actually
>> see who is using those instructions with ftrace.
>
> That would also be useful for perf, where the plain `emulation fault' event
> can be a little too broad.

I'll replace the counters with trace points.

There is still the pr_warn which informs the user about applications
using legacy instructions. Hopefully, this should encourage updating the
software.

>
>> You still wouldn't get the files in the same place as the enable switch
>> though. The easiest way to implement that switch btw would be a
>> module_param: It can be passed on the command line (using
>> swp_emulate.enable=0) or at runtime by writing to
>> /sys/module/swp_emulate/parameters/enable.
>> 
>> If we do both, there is no longer a need to have any debugfs file logic,
>> which is also a plus.
>
> Sounds good to me.

Just a note: instead of being 'swp_emulate.enable=0' this'll be
'v7_obsolete.swp_emulate=0' and correspondingly for the other features.

I'll include these changes in the next version.

>
> Will
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Arnd Bergmann Aug. 27, 2014, 6:30 p.m. UTC | #7
On Wednesday 27 August 2014, Punit Agrawal wrote:
> I'll replace the counters with trace points.
> 
> There is still the pr_warn which informs the user about applications
> using legacy instructions. Hopefully, this should encourage updating the
> software.

pr_warn_ratelimit() please. There is no point printing this all the time
if nobody reads the messages.

> >> If we do both, there is no longer a need to have any debugfs file logic,
> >> which is also a plus.
> >
> > Sounds good to me.
> 
> Just a note: instead of being 'swp_emulate.enable=0' this'll be
> 'v7_obsolete.swp_emulate=0' and correspondingly for the other features.

It would be nice if the module name could be the same for arm32 and arm64,
and I don't know if we want to rename swp_emulate.c to v7_obsolete.c
on arm32.

Other than that, I have no opinion on the specific name of the module
or the option.

	Arnd
Punit Agrawal Aug. 28, 2014, 10:21 a.m. UTC | #8
Arnd Bergmann <arnd@arndb.de> writes:

> On Wednesday 27 August 2014, Punit Agrawal wrote:
>> I'll replace the counters with trace points.
>> 
>> There is still the pr_warn which informs the user about applications
>> using legacy instructions. Hopefully, this should encourage updating the
>> software.
>
> pr_warn_ratelimit() please. There is no point printing this all the time
> if nobody reads the messages.

Agreed. The patch indeed uses pr_warn_ratelimit().

>
>> >> If we do both, there is no longer a need to have any debugfs file logic,
>> >> which is also a plus.
>> >
>> > Sounds good to me.
>> 
>> Just a note: instead of being 'swp_emulate.enable=0' this'll be
>> 'v7_obsolete.swp_emulate=0' and correspondingly for the other features.
>
> It would be nice if the module name could be the same for arm32 and arm64,
> and I don't know if we want to rename swp_emulate.c to v7_obsolete.c
> on arm32.

Catalin was suggesting renaming v7_obsolete.c, so I'll wait to see what
is the outcome there. Calling it swp_emulate.c feels wrong as in the
subsequent patch we add CP15 barrier emulation to it.

>
> Other than that, I have no opinion on the specific name of the module
> or the option.
>
> 	Arnd
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
diff mbox

Patch

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index c52894e..ba780b1 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -142,6 +142,47 @@  config ARCH_XGENE
 	help
 	  This enables support for AppliedMicro X-Gene SOC Family
 
+comment "Processor Features"
+
+config V7_OBSOLETE
+	bool "Emulate obsolete ARMv7 instructions"
+	depends on COMPAT
+	help
+	  AArch32 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 N
+
+if V7_OBSOLETE
+
+config SWP_EMULATION
+	bool "Emulate SWP/SWPB instructions"
+	help
+	  ARMv8 obsoletes the use of SWP/SWPB instructions such that
+	  they are always undefined. Say Y here to enable software
+	  emulation of these instructions for userspace (not kernel)
+	  using LDXR/STXR.
+
+	  In some older versions of glibc [<=2.8] SWP is used during futex
+	  trylock() operations with the assumption that the code will not
+	  be preempted. This invalid assumption may be more likely to fail
+	  with SWP emulation enabled, leading to deadlock of the user
+	  application.
+
+	  NOTE: when accessing uncached shared regions, LDXR/STXR rely
+	  on an external transaction monitoring block called a global
+	  monitor to maintain update atomicity. If your system does not
+	  implement a global monitor, this option can cause programs that
+	  perform SWP operations to uncached memory to deadlock.
+
+	  If unsure, say N
+
+endif
+
 endmenu
 
 menu "Bus support"
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index dc1f73b..2861cc6 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -105,6 +105,15 @@  bool aarch64_insn_hotpatch_safe(u32 old_insn, u32 new_insn);
 int aarch64_insn_patch_text_nosync(void *addr, u32 insn);
 int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt);
 int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
+
+#ifdef CONFIG_V7_OBSOLETE
+#define RN_OFFSET	16
+#define RT_OFFSET	12
+#define RT2_OFFSET	 0
+
+u32 aarch32_insn_extract_reg_num(u32 insn, int offset);
+#endif /* CONFIG_V7_OBSOLETE */
+
 #endif /* __ASSEMBLY__ */
 
 #endif	/* __ASM_INSN_H */
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index eee33a0..2a35a73 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -30,6 +30,7 @@  arm64-obj-$(CONFIG_EARLY_PRINTK)	+= early_printk.o
 arm64-obj-$(CONFIG_ARM64_CPU_SUSPEND)	+= sleep.o suspend.o
 arm64-obj-$(CONFIG_JUMP_LABEL)		+= jump_label.o
 arm64-obj-$(CONFIG_KGDB)		+= kgdb.o
+arm64-obj-$(CONFIG_V7_OBSOLETE)		+= v7_obsolete.o
 
 obj-y					+= $(arm64-obj-y) vdso/
 obj-m					+= $(arm64-obj-m)
diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c
index 92f3683..5db9d35 100644
--- a/arch/arm64/kernel/insn.c
+++ b/arch/arm64/kernel/insn.c
@@ -302,3 +302,14 @@  u32 __kprobes aarch64_insn_gen_nop(void)
 {
 	return aarch64_insn_gen_hint(AARCH64_INSN_HINT_NOP);
 }
+
+#ifdef CONFIG_V7_OBSOLETE
+/*
+ * Macros/defines for extracting register numbers from instruction.
+ */
+u32 aarch32_insn_extract_reg_num(u32 insn, int offset)
+{
+	return (insn & (0xf << offset)) >> offset;
+}
+
+#endif /* CONFIG_V7_OBSOLETE */
diff --git a/arch/arm64/kernel/v7_obsolete.c b/arch/arm64/kernel/v7_obsolete.c
new file mode 100644
index 0000000..e9427cb
--- /dev/null
+++ b/arch/arm64/kernel/v7_obsolete.c
@@ -0,0 +1,241 @@ 
+/*
+ *  arch/arm64/kernel/v7_obsolete.c
+ *
+ *  Copied from arch/arm/kernel/swp_emulate.c and modified for ARMv8
+ *
+ *  Copyright (C) 2009,2012,2014 ARM Limited
+ *  __user_* functions adapted from include/asm/uaccess.h
+ *
+ * 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/debugfs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/perf_event.h>
+
+#include <asm/insn.h>
+#include <asm/opcodes.h>
+#include <asm/system_misc.h>
+#include <asm/traps.h>
+#include <asm/uaccess.h>
+
+/*
+ *  Implements emulation of the SWP/SWPB instructions using load-exclusive and
+ *  store-exclusive.
+ *
+ *  Syntax of SWP{B} instruction: SWP{B}<c> <Rt>, <Rt2>, [<Rn>]
+ *  Where: Rt  = destination
+ *	   Rt2 = source
+ *	   Rn  = address
+ */
+
+
+/*
+ * Error-checking SWP macros implemented using ldxr{b}/stxr{b}
+ */
+#define __user_swpX_asm(data, addr, res, temp, B)		\
+	__asm__ __volatile__(					\
+	"	mov		%w2, %w1\n"			\
+	"0:	ldxr"B"		%w1, [%3]\n"			\
+	"1:	stxr"B"		%w0, %w2, [%3]\n"		\
+	"	cbz		%w0, 2f\n"			\
+	"	mov		%w0, %w4\n"			\
+	"2:\n"							\
+	"	.pushsection	 .fixup,\"ax\"\n"		\
+	"	.align		2\n"				\
+	"3:	mov		%w0, %w5\n"			\
+	"	b		2b\n"				\
+	"	.popsection"					\
+	"	.pushsection	 __ex_table,\"a\"\n"		\
+	"	.align		3\n"				\
+	"	.quad		0b, 3b\n"			\
+	"	.quad		1b, 3b\n"			\
+	"	.popsection"					\
+	: "=&r" (res), "+r" (data), "=&r" (temp)		\
+	: "r" (addr), "i" (-EAGAIN), "i" (-EFAULT)		\
+	: "memory")
+
+#define __user_swp_asm(data, addr, res, temp) \
+	__user_swpX_asm(data, addr, res, temp, "")
+#define __user_swpb_asm(data, addr, res, temp) \
+	__user_swpX_asm(data, addr, res, temp, "b")
+
+/*
+ * Bit 22 of the instruction encoding distinguishes between
+ * the SWP and SWPB variants (bit set means SWPB).
+ */
+#define TYPE_SWPB (1 << 22)
+
+static atomic_t swp_counter;
+static atomic_t swpb_counter;
+static u32 swp_enabled = 1;
+
+/*
+ * Set up process info to signal segmentation fault - called on access error.
+ */
+static void set_segfault(struct pt_regs *regs, unsigned long addr)
+{
+	siginfo_t info;
+
+	down_read(&current->mm->mmap_sem);
+	if (find_vma(current->mm, addr) == NULL)
+		info.si_code = SEGV_MAPERR;
+	else
+		info.si_code = SEGV_ACCERR;
+	up_read(&current->mm->mmap_sem);
+
+	info.si_signo = SIGSEGV;
+	info.si_errno = 0;
+	info.si_addr  = (void *) instruction_pointer(regs);
+
+	pr_debug("SWP{B} emulation: access caused memory abort!\n");
+	arm64_notify_die("Illegal memory access", regs, &info, 0);
+}
+
+static int emulate_swpX(unsigned int address, unsigned int *data,
+			unsigned int type)
+{
+	unsigned int res = 0;
+
+	if ((type != TYPE_SWPB) && (address & 0x3)) {
+		/* SWP to unaligned address not permitted */
+		pr_debug("SWP instruction on unaligned pointer!\n");
+		return -EFAULT;
+	}
+
+	while (1) {
+		unsigned long temp;
+
+		if (type == TYPE_SWPB)
+			__user_swpb_asm(*data, address, res, temp);
+		else
+			__user_swp_asm(*data, address, res, temp);
+
+		if (likely(res != -EAGAIN) || signal_pending(current))
+			break;
+
+		cond_resched();
+	}
+
+	return res;
+}
+
+/*
+ * swp_handler logs the id of calling process, dissects the instruction, sanity
+ * checks the memory location, calls emulate_swpX for the actual operation and
+ * deals with fixup/error handling before returning
+ */
+static int swp_handler(struct pt_regs *regs, u32 instr)
+{
+	u32 address, destreg, data, type;
+	int rn, rt2, res = 0;
+
+	if (!swp_enabled)
+		return -EFAULT;
+
+	perf_sw_event(PERF_COUNT_SW_EMULATION_FAULTS, 1, regs, regs->pc);
+
+	type = instr & TYPE_SWPB;
+
+	switch (arm_check_condition(instr, regs->pstate)) {
+	case ARM_OPCODE_CONDTEST_PASS:
+		break;
+	case ARM_OPCODE_CONDTEST_FAIL:
+		/* Condition failed - return to next instruction */
+		goto ret;
+	case ARM_OPCODE_CONDTEST_UNCOND:
+		/* If unconditional encoding - not a SWP, undef */
+		return -EFAULT;
+	default:
+		return -EINVAL;
+	}
+
+	rn = aarch32_insn_extract_reg_num(instr, RN_OFFSET);
+	rt2 = aarch32_insn_extract_reg_num(instr, RT2_OFFSET);
+
+	address = (u32)regs->user_regs.regs[rn];
+	data	= (u32)regs->user_regs.regs[rt2];
+	destreg = aarch32_insn_extract_reg_num(instr, RT_OFFSET);
+
+	pr_debug("addr in r%d->0x%08x, dest is r%d, source in r%d->0x%08x)\n",
+		rn, address, destreg,
+		aarch32_insn_extract_reg_num(instr, RT2_OFFSET), data);
+
+	/* Check access in reasonable access range for both SWP and SWPB */
+	if (!access_ok(VERIFY_WRITE, (address & ~3), 4)) {
+		pr_debug("SWP{B} emulation: access to 0x%08x not allowed!\n",
+			address);
+		goto fault;
+	}
+
+	res = emulate_swpX(address, &data, type);
+	if (res == -EFAULT)
+		goto fault;
+	else if (res == 0)
+		regs->user_regs.regs[destreg] = data;
+
+ret:
+	if (type == TYPE_SWPB)
+		atomic_inc(&swpb_counter);
+	else
+		atomic_inc(&swp_counter);
+
+	pr_warn_ratelimited("\"%s\" (%ld) uses obsolete SWP{B} instruction at 0x%llx\n",
+			current->comm, (unsigned long)current->pid, regs->pc);
+
+	regs->pc += 4;
+	return 0;
+
+fault:
+	set_segfault(regs, address);
+
+	return 0;
+}
+
+/*
+ * 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_hook = {
+	.instr_mask	= 0x0fb00ff0,
+	.instr_val	= 0x01000090,
+	.pstate_mask	= COMPAT_PSR_MODE_MASK,
+	.pstate_val	= COMPAT_PSR_MODE_USR,
+	.fn		= swp_handler
+};
+
+static void __init swp_emulation_init(void)
+{
+	struct dentry *swp_d;
+
+	atomic_set(&swp_counter, 0);
+	atomic_set(&swpb_counter, 0);
+
+	swp_d = debugfs_create_dir("swp_emulation", arch_debugfs_dir);
+	if (!IS_ERR_OR_NULL(swp_d)) {
+		debugfs_create_atomic_t("swp_count", S_IRUGO, swp_d, &swp_counter);
+		debugfs_create_atomic_t("swpb_count", S_IRUGO, swp_d, &swpb_counter);
+		debugfs_create_bool("enabled", S_IRUGO | S_IWUSR, swp_d,
+				&swp_enabled);
+	}
+
+	if (register_undef_hook(&swp_hook) == 0)
+		pr_notice("Registered SWP/SWPB emulation handler\n");
+}
+
+/*
+ * Invoked as late_initcall, since not needed before init spawned.
+ */
+static int __init v7_obsolete_init(void)
+{
+	if (IS_ENABLED(CONFIG_SWP_EMULATION))
+		swp_emulation_init();
+
+	return 0;
+}
+
+late_initcall(v7_obsolete_init);