// SPDX-License-Identifier: GPL-2.0 /* This is a loopback mailbox that can be used to test drivers on * a single system. It uses a global memory so only 2 instances * (one for each side) can be probed. */ #include #include #include #include #include #include #include struct mbox_loopback_chan { struct mbox_chan *self; struct mbox_chan *other; struct irq_work work; struct hrtimer timer; u32 msg; bool pending; }; #define MAILBOX_NUM_CHANS 32 struct mbox_loopback { struct mbox_controller controller[2]; bool probed[2]; struct mbox_loopback_chan lchans[2*MAILBOX_NUM_CHANS]; struct mbox_chan chans[2*MAILBOX_NUM_CHANS]; }; /* A global shared memory for both sides of the mailbox */ static struct mbox_loopback mbox_loopback; static void mbox_loopback_work(struct irq_work *work) { struct mbox_loopback_chan *lchan = container_of(work, struct mbox_loopback_chan, work); mbox_chan_received_data(lchan->other, (void *)(unsigned long)lchan->msg); smp_wmb(); lchan->pending = false; } static bool mbox_loopback_last_tx_done(struct mbox_chan *chan) { struct mbox_loopback_chan *lchan = chan->con_priv; return !lchan->pending; } static int mbox_loopback_send_data(struct mbox_chan *chan, void *data) { struct mbox_loopback_chan *lchan = chan->con_priv; lchan->msg = (u32)(unsigned long)data; lchan->pending = true; smp_wmb(); /* Start a timer that will trigger an IRQ in a short while */ hrtimer_start(&lchan->timer, ns_to_ktime(1000), HRTIMER_MODE_REL); return 0; } static enum hrtimer_restart mbox_loopback_timer_callback(struct hrtimer *hrtimer) { struct mbox_loopback_chan *lchan = container_of(hrtimer, struct mbox_loopback_chan, timer); irq_work_queue(&lchan->work); return HRTIMER_NORESTART; } static void mbox_loopback_shutdown(struct mbox_chan *chan) { struct mbox_loopback_chan *lchan = chan->con_priv; hrtimer_cancel(&lchan->timer); irq_work_sync(&lchan->work); } static const struct mbox_chan_ops mbox_loopback_ops = { .send_data = mbox_loopback_send_data, .shutdown = mbox_loopback_shutdown, .last_tx_done = mbox_loopback_last_tx_done, }; static int mbox_loopback_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int i; unsigned int side; /* Check side (nothing or anything but 1 is primary side) */ of_property_read_u32(dev->of_node, "side", &side); if (side != 1) side = 0; if (mbox_loopback.probed[side]) return -ENOMEM; mbox_loopback.probed[side] = true; mbox_loopback.controller[side].dev = dev; mbox_loopback.controller[side].num_chans = MAILBOX_NUM_CHANS; mbox_loopback.controller[side].txdone_irq = false; mbox_loopback.controller[side].txdone_poll = true; mbox_loopback.controller[side].txpoll_period = 1; mbox_loopback.controller[side].chans = &mbox_loopback.chans[side * MAILBOX_NUM_CHANS]; mbox_loopback.controller[side].ops = &mbox_loopback_ops; BUILD_BUG_ON(ARRAY_SIZE(mbox_loopback.chans) != ARRAY_SIZE(mbox_loopback.lchans)); for (i = 0; i < MAILBOX_NUM_CHANS; i++) { int me = i + side * MAILBOX_NUM_CHANS; int other; if (me >= MAILBOX_NUM_CHANS) { other = me - MAILBOX_NUM_CHANS; } else { other = me + MAILBOX_NUM_CHANS; } mbox_loopback.lchans[me].self = &mbox_loopback.chans[me]; mbox_loopback.lchans[me].other = &mbox_loopback.chans[other]; init_irq_work(&mbox_loopback.lchans[me].work, mbox_loopback_work); hrtimer_init(&mbox_loopback.lchans[me].timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); mbox_loopback.lchans[me].timer.function = mbox_loopback_timer_callback; mbox_loopback.chans[me].con_priv = &mbox_loopback.lchans[me]; } return devm_mbox_controller_register(dev, &mbox_loopback.controller[side]); } static const struct of_device_id mbox_loopback_match[] = { { .compatible = "mailbox-loopback" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mbox_loopback_match); static struct platform_driver mbox_loopback_driver = { .probe = mbox_loopback_probe, .driver = { .name = "mailbox-loopback", .of_match_table = mbox_loopback_match, }, }; module_platform_driver(mbox_loopback_driver); MODULE_DESCRIPTION("Loopback Mailbox"); MODULE_LICENSE("GPL v2");