diff --git a/drivers/staging/asi/asidrv.c b/drivers/staging/asi/asidrv.c
index c06e4734e0e5..4f0548edb2f9 100644
--- a/drivers/staging/asi/asidrv.c
+++ b/drivers/staging/asi/asidrv.c
@@ -10,15 +10,28 @@
 
 #include <asm/asi.h>
 #include <asm/dpt.h>
+#include <asm/tlbflush.h>
+
+#include "asidrv.h"
 
 struct asidrv_test {
 	struct asi		*asi;	/* ASI for testing */
 	struct dpt		*dpt;	/* ASI decorated page-table */
 };
 
+struct asidrv_sequence {
+	const char *name;
+	enum asidrv_run_error (*setup)(struct asidrv_test *t);
+	enum asidrv_run_error (*run)(struct asidrv_test *t);
+	void (*cleanup)(struct asidrv_test *t);
+};
+
 static struct asidrv_test *asidrv_test;
 
 static void asidrv_test_destroy(struct asidrv_test *test);
+static void asidrv_run_fini(struct asidrv_test *test);
+static void asidrv_run_cleanup(struct asidrv_test *test,
+			       struct asidrv_sequence *sequence);
 
 static struct asidrv_test *asidrv_test_create(void)
 {
@@ -86,8 +99,243 @@ static void asidrv_test_destroy(struct asidrv_test *test)
 	kfree(test);
 }
 
+static int asidrv_asi_is_active(struct asi *asi)
+{
+	struct asi *current_asi;
+	unsigned long cr3;
+	bool is_active;
+	int idepth;
+
+	if (!asi)
+		return false;
+
+	current_asi = this_cpu_read(cpu_asi_session.asi);
+	if (current_asi == asi) {
+		idepth = this_cpu_read(cpu_asi_session.idepth);
+		is_active = (idepth == 0);
+	} else {
+		is_active = false;
+		if (current_asi) {
+			/* weird... another ASI is active! */
+			pr_debug("ASI %px is active (testing ASI = %px)\n",
+				 current_asi, asi);
+		}
+	}
+
+	/*
+	 * If the ASI is active check that the CR3 value is consistent with
+	 * this ASI being active. Otherwise, check that CR3 value doesn't
+	 * reference an ASI.
+	 */
+	cr3 = __native_read_cr3();
+	if (is_active) {
+		if ((cr3 ^ asi->base_cr3) >> ASI_PCID_PREFIX_SHIFT == 0)
+			return true;
+
+		pr_warn("ASI %px: active ASI has inconsistent CR3 value (cr3=%lx, ASI base=%lx)\n",
+			asi, cr3, asi->base_cr3);
+
+	} else if (cr3 & ASI_PCID_PREFIX_MASK) {
+		pr_warn("ASI %px: inactive ASI has inconsistent CR3 value (cr3=%lx, ASI base=%lx)\n",
+			asi, cr3, asi->base_cr3);
+	}
+
+	return false;
+}
+
+/*
+ * Printk Test Sequence
+ */
+static enum asidrv_run_error asidrv_printk_run(struct asidrv_test *test)
+{
+	pr_notice("asidrv printk test...\n");
+	return ASIDRV_RUN_ERR_NONE;
+}
+
+struct asidrv_sequence asidrv_sequences[] = {
+	[ASIDRV_SEQ_NOP] = {
+		"nop",
+		NULL, NULL, NULL,
+	},
+	[ASIDRV_SEQ_PRINTK] = {
+		"printk",
+		NULL, asidrv_printk_run, NULL,
+	},
+};
+
+static enum asidrv_run_error asidrv_run_init(struct asidrv_test *test)
+{
+	int err;
+
+	/*
+	 * Map the current stack, we need it to enter ASI.
+	 */
+	err = dpt_map(test->dpt, current->stack,
+		      PAGE_SIZE << THREAD_SIZE_ORDER);
+	if (err) {
+		asidrv_run_fini(test);
+		return ASIDRV_RUN_ERR_MAP_STACK;
+	}
+
+	/*
+	 * Map the current task, schedule() needs it.
+	 */
+	err = dpt_map(test->dpt, current, sizeof(struct task_struct));
+	if (err)
+		return ASIDRV_RUN_ERR_MAP_TASK;
+
+	/*
+	 * The ASI page-table has been updated so bump the generation
+	 * number to have the ASI TLB flushed.
+	 */
+	atomic64_inc(&test->asi->pgtable_gen);
+
+	return ASIDRV_RUN_ERR_NONE;
+}
+
+static void asidrv_run_fini(struct asidrv_test *test)
+{
+	dpt_unmap(test->dpt, current);
+	dpt_unmap(test->dpt, current->stack);
+}
+
+static enum asidrv_run_error asidrv_run_setup(struct asidrv_test *test,
+					      struct asidrv_sequence *sequence)
+{
+	int run_err = ASIDRV_RUN_ERR_NONE;
+
+	if (sequence->setup) {
+		run_err = sequence->setup(test);
+		if (run_err)
+			goto failed;
+	}
+
+	return ASIDRV_RUN_ERR_NONE;
+
+failed:
+	return run_err;
+}
+
+static void asidrv_run_cleanup(struct asidrv_test *test,
+			       struct asidrv_sequence *sequence)
+{
+	if (sequence->cleanup)
+		sequence->cleanup(test);
+}
+
+/*
+ * Run the specified sequence with ASI. Report result back.
+ */
+static enum asidrv_run_error asidrv_run(struct asidrv_test *test,
+					enum asidrv_seqnum seqnum,
+					bool *asi_active)
+{
+	struct asidrv_sequence *sequence = &asidrv_sequences[seqnum];
+	int run_err = ASIDRV_RUN_ERR_NONE;
+	int err = 0;
+
+	if (seqnum >= ARRAY_SIZE(asidrv_sequences)) {
+		pr_debug("Undefined sequence %d\n", seqnum);
+		return ASIDRV_RUN_ERR_SEQUENCE;
+	}
+
+	pr_debug("ASI running sequence %s\n", sequence->name);
+
+	run_err = asidrv_run_setup(test, sequence);
+	if (run_err)
+		return run_err;
+
+	err = asi_enter(test->asi);
+	if (err) {
+		run_err = ASIDRV_RUN_ERR_ENTER;
+		goto failed_noexit;
+	}
+
+	if (!asidrv_asi_is_active(test->asi)) {
+		run_err = ASIDRV_RUN_ERR_ACTIVE;
+		goto failed;
+	}
+
+	if (sequence->run) {
+		run_err = sequence->run(test);
+		if (run_err != ASIDRV_RUN_ERR_NONE)
+			goto failed;
+	}
+
+	*asi_active = asidrv_asi_is_active(test->asi);
+
+failed:
+	asi_exit(test->asi);
+
+failed_noexit:
+	asidrv_run_cleanup(test, sequence);
+
+	return run_err;
+}
+
+static int asidrv_ioctl_run_sequence(struct asidrv_test *test,
+				     unsigned long arg)
+{
+	struct asidrv_run_param __user *urparam;
+	struct asidrv_run_param rparam;
+	enum asidrv_run_error run_err;
+	enum asidrv_seqnum seqnum;
+	bool asi_active = false;
+
+	urparam = (struct asidrv_run_param *)arg;
+	if (copy_from_user(&rparam, urparam, sizeof(rparam)))
+		return -EFAULT;
+
+	seqnum = rparam.sequence;
+
+	pr_debug("ASI sequence %d\n", seqnum);
+
+	run_err = asidrv_run_init(test);
+	if (run_err) {
+		pr_debug("ASI run init error %d\n", run_err);
+		goto failed_nofini;
+	}
+
+	run_err = asidrv_run(test, seqnum, &asi_active);
+	if (run_err) {
+		pr_debug("ASI run error %d\n", run_err);
+	} else {
+		pr_debug("ASI run okay, ASI is %s\n",
+			 asi_active ? "active" : "inactive");
+	}
+
+	asidrv_run_fini(test);
+
+failed_nofini:
+	rparam.run_error = run_err;
+	rparam.asi_active = asi_active;
+
+	if (copy_to_user(urparam, &rparam, sizeof(rparam)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static long asidrv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	struct asidrv_test *test = asidrv_test;
+
+	switch (cmd) {
+
+	/* Test ioctls */
+
+	case ASIDRV_IOCTL_RUN_SEQUENCE:
+		return asidrv_ioctl_run_sequence(test, arg);
+
+	default:
+		return -ENOTTY;
+	};
+}
+
 static const struct file_operations asidrv_fops = {
 	.owner		= THIS_MODULE,
+	.unlocked_ioctl	= asidrv_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
 };
 
 static struct miscdevice asidrv_miscdev = {
diff --git a/drivers/staging/asi/asidrv.h b/drivers/staging/asi/asidrv.h
new file mode 100644
index 000000000000..33acb058c443
--- /dev/null
+++ b/drivers/staging/asi/asidrv.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+
+#ifndef __ASIDRV_H__
+#define __ASIDRV_H__
+
+#include <linux/types.h>
+
+enum asidrv_seqnum {
+	ASIDRV_SEQ_NOP,		/* empty sequence */
+	ASIDRV_SEQ_PRINTK,	/* printk sequence */
+};
+
+enum asidrv_run_error {
+	ASIDRV_RUN_ERR_NONE,	/* no error */
+	ASIDRV_RUN_ERR_SEQUENCE, /* unknown sequence */
+	ASIDRV_RUN_ERR_MAP_STACK, /* failed to map current stack */
+	ASIDRV_RUN_ERR_MAP_TASK, /* failed to map current task */
+	ASIDRV_RUN_ERR_ENTER,	/* failed to enter ASI */
+	ASIDRV_RUN_ERR_ACTIVE,	/* ASI is not active after entering ASI */
+};
+
+#define ASIDRV_IOCTL_RUN_SEQUENCE	_IOWR('a', 1, struct asidrv_run_param)
+
+struct asidrv_run_param {
+	__u32 sequence;		/* sequence to run */
+	__u32 run_error;	/* result error after run */
+	__u32 asi_active;	/* ASI is active after run? */
+};
+#endif
