diff --git a/arch/arm64/Kconfig.debug b/arch/arm64/Kconfig.debug
index 265c4461031f..10df6056db3e 100644
--- a/arch/arm64/Kconfig.debug
+++ b/arch/arm64/Kconfig.debug
@@ -20,4 +20,7 @@ config ARM64_RELOC_TEST
 	depends on m
 	tristate "Relocation testing module"
 
+config TEST_INSN
+	tristate "Test functions located in the aarch64 instruction encoder at runtime"
+
 source "drivers/hwtracing/coresight/Kconfig"
diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
index 0bd590605416..4930a2b077b8 100644
--- a/arch/arm64/Makefile
+++ b/arch/arm64/Makefile
@@ -176,7 +176,9 @@ vdso_install:
 
 archprepare:
 	$(Q)$(MAKE) $(build)=arch/arm64/tools kapi
+#ifdef CONFIG_TEST_INSN
 	$(Q)$(MAKE) $(build)=arch/arm64/tools tests
+#endif
 ifeq ($(CONFIG_ARM64_ERRATUM_843419),y)
   ifneq ($(CONFIG_ARM64_LD_HAS_FIX_ERRATUM_843419),y)
 	@echo "warning: ld does not support --fix-cortex-a53-843419; kernel may be susceptible to erratum" >&2
diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile
index 29490be2546b..d180945ecc22 100644
--- a/arch/arm64/lib/Makefile
+++ b/arch/arm64/lib/Makefile
@@ -22,3 +22,5 @@ obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
 obj-$(CONFIG_ARM64_MTE) += mte.o
 
 obj-$(CONFIG_KASAN_SW_TAGS) += kasan_sw_tags.o
+
+obj-$(CONFIG_TEST_INSN) += test_insn.o
diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index fccfe363e567..8888e407032f 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -193,6 +193,7 @@ u64 aarch64_insn_decode_immediate(enum aarch64_insn_imm_type type, u32 insn)
 
 	return (insn >> shift) & mask;
 }
+EXPORT_SYMBOL_GPL(aarch64_insn_decode_immediate);
 
 u32 __kprobes aarch64_insn_encode_immediate(enum aarch64_insn_imm_type type,
 				  u32 insn, u64 imm)
@@ -256,6 +257,7 @@ u32 aarch64_insn_decode_register(enum aarch64_insn_register_type type,
 
 	return (insn >> shift) & GENMASK(4, 0);
 }
+EXPORT_SYMBOL_GPL(aarch64_insn_decode_register);
 
 static u32 aarch64_insn_encode_register(enum aarch64_insn_register_type type,
 					u32 insn,
@@ -1424,6 +1426,7 @@ u32 aarch64_insn_gen_logical_immediate(enum aarch64_insn_logic_type type,
 	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RN, insn, Rn);
 	return aarch64_encode_immediate(imm, variant, insn);
 }
+EXPORT_SYMBOL_GPL(aarch64_insn_gen_logical_immediate);
 
 u32 aarch64_insn_gen_extr(enum aarch64_insn_variant variant,
 			  enum aarch64_insn_register Rm,
diff --git a/arch/arm64/lib/test_insn.c b/arch/arm64/lib/test_insn.c
new file mode 100644
index 000000000000..41466f61c6c0
--- /dev/null
+++ b/arch/arm64/lib/test_insn.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test cases for the aarch64 insn encoder.
+ *
+ * Copyright (C) 2021 ARM Limited.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+
+#include <asm/debug-monitors.h>
+#include <asm/insn.h>
+
+#include "../../../tools/testing/selftests/kselftest_module.h"
+
+struct bitmask_test_case {
+	/* input */
+	u64 imm;
+
+	/* expected output */
+	u64 n, immr, imms;
+};
+struct bitmask_test_case aarch64_logic_imm_test[] = {
+#include <asm/test_logic_imm_generated.h>
+};
+
+KSTM_MODULE_GLOBALS();
+
+static void __init test_logic_imm(void)
+{
+	int i;
+	u8 rd, rn;
+	u32 insn;
+
+	for (i = 0; i < ARRAY_SIZE(aarch64_logic_imm_test); i++) {
+		total_tests++;
+
+		rd = i % 30;
+		rn = (i + 1) % 30;
+
+		insn = aarch64_insn_gen_logical_immediate(AARCH64_INSN_LOGIC_AND,
+							  AARCH64_INSN_VARIANT_64BIT,
+							  rn, rd, aarch64_logic_imm_test[i].imm);
+
+		if (!aarch64_insn_is_and_imm(insn) ||
+		    rd != aarch64_insn_decode_register(AARCH64_INSN_REGTYPE_RD, insn) ||
+		    rn != aarch64_insn_decode_register(AARCH64_INSN_REGTYPE_RN, insn) ||
+		    aarch64_logic_imm_test[i].imms != aarch64_insn_decode_immediate(AARCH64_INSN_IMM_S, insn) ||
+		    aarch64_logic_imm_test[i].immr != aarch64_insn_decode_immediate(AARCH64_INSN_IMM_R, insn) ||
+		    aarch64_logic_imm_test[i].n != aarch64_insn_decode_immediate(AARCH64_INSN_IMM_N, insn)) {
+			failed_tests++;
+			pr_warn_once("[%s:%u] Failed to encode immediate 0x%llx (got insn 0x%x))\n",
+				     __FILE__, __LINE__, aarch64_logic_imm_test[i].imm, insn);
+			continue;
+		}
+	}
+}
+
+static void __init do_test_bad_logic_imm(u64 imm, enum aarch64_insn_variant var)
+{
+	u32 insn;
+
+	total_tests++;
+	insn = aarch64_insn_gen_logical_immediate(AARCH64_INSN_LOGIC_AND,
+						  var, 0, 0, imm);
+	if (insn != AARCH64_BREAK_FAULT)
+		failed_tests++;
+}
+
+static void __init test_bad_logic_imm(void)
+{
+	do_test_bad_logic_imm(0, AARCH64_INSN_VARIANT_64BIT);
+	do_test_bad_logic_imm(0x1234, AARCH64_INSN_VARIANT_64BIT);
+	do_test_bad_logic_imm(0xffffffffffffffff, AARCH64_INSN_VARIANT_64BIT);
+	do_test_bad_logic_imm((1ULL<<32), AARCH64_INSN_VARIANT_32BIT);
+}
+
+static void __init selftest(void)
+{
+	test_logic_imm();
+	test_bad_logic_imm();
+}
+
+KSTM_MODULE_LOADERS(test_insn);
+MODULE_AUTHOR("James Morse <james.morse@arm.com>");
+MODULE_LICENSE("GPL");
diff --git a/tools/testing/selftests/arm64/Makefile b/tools/testing/selftests/arm64/Makefile
index 1e8d9a8f59df..2c59e7d40524 100644
--- a/tools/testing/selftests/arm64/Makefile
+++ b/tools/testing/selftests/arm64/Makefile
@@ -4,7 +4,7 @@
 ARCH ?= $(shell uname -m 2>/dev/null || echo not)
 
 ifneq (,$(filter $(ARCH),aarch64 arm64))
-ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi
+ARM64_SUBTARGETS ?= tags signal pauth fp mte bti abi lib
 else
 ARM64_SUBTARGETS :=
 endif
diff --git a/tools/testing/selftests/arm64/lib/Makefile b/tools/testing/selftests/arm64/lib/Makefile
new file mode 100644
index 000000000000..5ed92a5135ce
--- /dev/null
+++ b/tools/testing/selftests/arm64/lib/Makefile
@@ -0,0 +1,6 @@
+# Copyright (C) 2022 ARM Limited
+# Makefile for arm64/lib/ function selftests
+
+TEST_PROGS := insn.sh
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/arm64/lib/config b/tools/testing/selftests/arm64/lib/config
new file mode 100644
index 000000000000..cb982478782b
--- /dev/null
+++ b/tools/testing/selftests/arm64/lib/config
@@ -0,0 +1 @@
+CONFIG_TEST_INSN=m
diff --git a/tools/testing/selftests/arm64/lib/insn.sh b/tools/testing/selftests/arm64/lib/insn.sh
new file mode 100755
index 000000000000..3d893b1a0069
--- /dev/null
+++ b/tools/testing/selftests/arm64/lib/insn.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Tests the aarch64 instruction generation infrastructure using test_insn
+# kernel module.
+$(dirname $0)/../../kselftest/module.sh "insn" test_insn
