--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Nikita Shubin <nikita.shubin@maquefel.me>
+ * derived: drivers/dma/dmatest.c
+ */
+#define DEBUG
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/sched/signal.h>
+
+#include <acpi/qemu-mailbox.h>
+
+static unsigned int iterations = 5000;
+module_param(iterations, uint, 0644);
+MODULE_PARM_DESC(iterations,
+ "Iterations before stopping test (default: 5000)");
+
+static int timeout = 3000;
+module_param(timeout, int, 0644);
+MODULE_PARM_DESC(timeout, "Transfer Timeout in msec (default: 3000), "
+ "Pass -1 for infinite timeout");
+
+/* TODO: pass as parameter */
+#define MAX_RX_TIMEOUT (msecs_to_jiffies(3000))
+
+struct mbox_pp_params {
+ unsigned int iterations;
+ int timeout;
+};
+
+enum mbox_pp_error {
+ MBOX_PP_OK,
+ MBOX_PP_TX_DONE_ERR,
+ MBOX_PP_MBOX_SEND_ERR,
+};
+
+static struct mbox_pp_info {
+ /* Test parameters */
+ struct mbox_pp_params params;
+
+ /* Internal state */
+ struct mbox_client client;
+ struct mutex lock;
+ struct mbox_chan *chan;
+ struct task_struct *task;
+ struct completion tx_done;
+ unsigned int iter;
+ ktime_t latency;
+ int last_error;
+ atomic_t running;
+ bool did_init;
+ bool pending;
+} test_info = {
+ .lock = __MUTEX_INITIALIZER(test_info.lock),
+};
+
+static int mbox_pp_run_set(const char *val, const struct kernel_param *kp);
+static int mbox_pp_run_get(char *val, const struct kernel_param *kp);
+static const struct kernel_param_ops run_ops = {
+ .set = mbox_pp_run_set,
+ .get = mbox_pp_run_get,
+};
+static bool mbox_pp_run;
+module_param_cb(run, &run_ops, &mbox_pp_run, 0644);
+MODULE_PARM_DESC(run, "Run the test (default: false)");
+
+static DECLARE_WAIT_QUEUE_HEAD(thread_wait);
+static bool wait;
+
+static bool is_threaded_test_run(struct mbox_pp_info *info)
+{
+ return false;
+}
+
+static bool is_threaded_test_pending(struct mbox_pp_info *info)
+{
+ return false;
+}
+
+static void run_pending_test(struct mbox_pp_info *info)
+{
+ wake_up_process(info->task);
+}
+
+static void stop_threaded_test(struct mbox_pp_info *info)
+{
+ mbox_free_channel(info->chan);
+}
+
+static void start_threaded_tests(struct mbox_pp_info *info)
+{
+ /* we might be called early to set run=, defer running until all
+ * parameters have been evaluated
+ */
+ if (!info->did_init)
+ return;
+
+ run_pending_test(info);
+}
+
+static int mbox_pp_run_get(char *val, const struct kernel_param *kp)
+{
+ struct mbox_pp_info *info = &test_info;
+
+ mutex_lock(&info->lock);
+ if (is_threaded_test_run(info)) {
+ mbox_pp_run = true;
+ } else {
+ if (!is_threaded_test_pending(info))
+ stop_threaded_test(info);
+ mbox_pp_run = false;
+ }
+ mutex_unlock(&info->lock);
+
+ return param_get_bool(val, kp);
+}
+
+static int mbox_pp_func(void *data)
+{
+ struct mbox_pp_info *info = data;
+ u32 message = 1;
+ u64 stop_at, now = 0;
+ int ret;
+
+ info->iter = 0;
+ info->latency = ktime_get();
+ info->last_error = 0;
+ atomic_set(&info->running, 1);
+
+ pr_debug("mbox_pp: started...\n");
+ if (info->params.timeout) {
+ now = ktime_get_real_fast_ns();
+ stop_at = now + info->params.timeout * NSEC_PER_MSEC;
+ } else
+ stop_at = -1;
+
+ pr_debug("mbox_pp: now %llu stop at %llu\n", now, stop_at);
+ while (ktime_get_real_fast_ns() < stop_at) {
+ ret = mbox_send_message(info->chan, &message);
+ if (ret < 0) {
+ pr_err("mbox_pp: started failed on send_message: %d\n", ret);
+ ret = -EIO;
+ goto fail;
+ }
+
+ if (!wait_for_completion_timeout(&info->tx_done,
+ MAX_RX_TIMEOUT)) {
+ pr_err("%s: send msg timeout\n", __func__);
+ ret = -ETIMEDOUT;
+ goto fail;
+ }
+
+ trace_printk("TX_DONE=%llu\n", ktime_get_raw_fast_ns());
+
+ reinit_completion(&info->tx_done);
+ if (info->last_error) {
+ pr_info("mbox_pp: test finished with error: %d\n", info->last_error);
+ ret = -EIO;
+ goto fail;
+ }
+
+ info->iter++;
+ if (info->iter >= info->params.iterations) {
+ info->last_error = MBOX_PP_OK;
+ break;
+ }
+
+ if (!atomic_read(&info->running))
+ break;
+ }
+
+ if (info->last_error)
+ pr_info("mbox_pp: test finished with error: %d\n", info->last_error);
+
+ info->latency = ktime_sub(ktime_get(), info->latency);
+ pr_info("mbox_pp: made %u iterations, lasted %llu usecs\n",
+ info->iter, ktime_to_us(info->latency));
+
+ if (info->iter)
+ info->latency = ns_to_ktime(ktime_divns(info->latency, info->iter));
+
+ pr_info("mbox_pp: latency %llu us (%llu ns)\n",
+ ktime_to_us(info->latency), ktime_to_ns(info->latency));
+
+ return 0;
+
+fail:
+ atomic_set(&info->running, 0);
+ return ret;
+}
+
+static void add_threaded_test(struct mbox_pp_info *info)
+{
+ struct mbox_pp_params *params = &info->params;
+
+ info->task = kthread_create(mbox_pp_func, info, "mbox_pp_thread");
+ if (IS_ERR(info->task)) {
+ pr_warn("Failed to create thread\n");
+ return;
+ }
+
+ params->iterations = iterations;
+ params->timeout = timeout;
+
+ info->pending = true;
+}
+
+static int mbox_pp_run_set(const char *val, const struct kernel_param *kp)
+{
+ struct mbox_pp_info *info = &test_info;
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = param_set_bool(val, kp);
+ if (ret) {
+ mutex_unlock(&info->lock);
+ return ret;
+ } else if (mbox_pp_run) {
+ add_threaded_test(info);
+ start_threaded_tests(info);
+ } else {
+ stop_threaded_test(info);
+ }
+
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+
+static void mbox_pp_tx_done(struct mbox_client *cl, void *msg, int ret)
+{
+ struct mbox_pp_info *info = &test_info;
+ if (unlikely(ret)) {
+ info->last_error = MBOX_PP_TX_DONE_ERR;
+ return;
+ }
+
+ complete(&info->tx_done);
+}
+
+static int __init mbox_pp_init(void)
+{
+ struct mbox_pp_info *info = &test_info;
+ struct mbox_pp_params *params = &info->params;
+ struct mbox_client *cl = &info->client;
+ struct mbox_chan *chan;
+
+ /* Request mailbox channel */
+ cl->tx_done = mbox_pp_tx_done;
+ cl->tx_block = false;
+ cl->tx_tout = test_info.params.timeout;
+ cl->knows_txdone = false;
+
+ /* find channel */
+ chan = qemu_mbox_request_channel(&info->client);
+ if (IS_ERR(chan))
+ pr_err("failed to acquire mailbox channel: %ld\n", PTR_ERR(chan));
+
+ info->chan = chan;
+ init_completion(&info->tx_done);
+
+ if (mbox_pp_run) {
+ mutex_lock(&info->lock);
+ add_threaded_test(info);
+ run_pending_test(info);
+ mutex_unlock(&info->lock);
+ }
+
+ if (params->iterations && wait)
+ wait_event(thread_wait, !is_threaded_test_run(info));
+
+ /* module parameters are stable, inittime tests are started,
+ * let userspace take over 'run' control
+ */
+ info->did_init = true;
+
+ return 0;
+}
+/* when compiled-in wait for drivers to load first */
+late_initcall(mbox_pp_init);
+
+static void __exit mbox_pp_exit(void)
+{
+ struct mbox_pp_info *info = &test_info;
+
+ mutex_lock(&info->lock);
+ stop_threaded_test(info);
+ mutex_unlock(&info->lock);
+}
+module_exit(mbox_pp_exit);
+
+MODULE_DESCRIPTION("Pingpong Mailbox Testing Facility");
+MODULE_AUTHOR("Nikita Shubin <nikita.shubin@maquefel.me>");
+MODULE_LICENSE("GPL v2");