From 2b70f3a6cc0baee8f43c25f92ca007cc206dafef Mon Sep 17 00:00:00 2001
From: Nikita Shubin <nikita.shubin@maquefel.me>
Date: Fri, 31 May 2024 10:16:46 +0300
Subject: [PATCH] [stage 0] mailbox: Add QEMU PCI mailboxes

Add a simple PCI mailbox driver dummy for testing purpose.

Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
---
 drivers/mailbox/Kconfig        |   6 ++
 drivers/mailbox/Makefile       |   2 +
 drivers/mailbox/qemu-mailbox.c | 162 +++++++++++++++++++++++++++++++++
 3 files changed, 170 insertions(+)
 create mode 100644 drivers/mailbox/qemu-mailbox.c

diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 3b8842c4a3401..020f32e85aa49 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -286,4 +286,10 @@ config QCOM_IPCC
 	  acts as an interrupt controller for receiving interrupts from clients.
 	  Say Y here if you want to build this driver.
 
+config QEMU_MBOX
+	tristate "QEMU PCIe Mailbox"
+	select REGMAP_MMIO
+	help
+	  QEMU Mailbox driver for PCIe MBOX model.
+
 endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 5cf2f54debaf4..d99cc632ff459 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -62,3 +62,5 @@ obj-$(CONFIG_SUN6I_MSGBOX)	+= sun6i-msgbox.o
 obj-$(CONFIG_SPRD_MBOX)		+= sprd-mailbox.o
 
 obj-$(CONFIG_QCOM_IPCC)		+= qcom-ipcc.o
+
+obj-$(CONFIG_QEMU_MBOX)		+= qemu-mailbox.o
diff --git a/drivers/mailbox/qemu-mailbox.c b/drivers/mailbox/qemu-mailbox.c
new file mode 100644
index 0000000000000..2f6ade55126ff
--- /dev/null
+++ b/drivers/mailbox/qemu-mailbox.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mailbox driver for QEMU Virtual PCI
+ *
+ * Author: Nikita Shubin <nikita.shubin@maquefel.me>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define QEMU_MBOX_MAX_CHAN_CNT		8
+
+#define QEMU_MBOX_CFG			0x0
+#define QEMU_MBOX_ISTATUS		0x4
+
+#define QEMU_MBOX_CHAN_STRIDE		0x20
+#define QEMU_MBOX_CHAN_ADDR(chan)	(QEMU_MBOX_CHAN_STRIDE + (chan * QEMU_MBOX_CHAN_STRIDE))
+
+struct qemu_mbox {
+	struct device *dev;
+	struct regmap *map;
+	struct mbox_controller mbox;
+};
+
+static inline struct qemu_mbox *get_qemu_mbox(struct mbox_controller *mbox)
+{
+	return container_of(mbox, struct qemu_mbox, mbox);
+}
+
+static irqreturn_t qemu_mbox_isr(int virq, void *data)
+{
+	struct qemu_mbox *mbox = data;
+	struct mbox_chan *chan;
+	unsigned int stat = 0;
+	int offset;
+	u32 msg;
+
+	regmap_read(mbox->map, QEMU_MBOX_ISTATUS, &stat);
+
+	for_each_set_bit(offset, (unsigned long *)&stat, QEMU_MBOX_MAX_CHAN_CNT) {
+		regmap_read(mbox->map, QEMU_MBOX_CHAN_ADDR(offset), &msg);
+		chan = &mbox->mbox.chans[offset];
+		if (chan->cl)
+			mbox_chan_received_data(chan, &msg);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int qemu_mbox_send_data(struct mbox_chan *link, void *data)
+{
+	unsigned long chan = (unsigned long)link->con_priv;
+	struct qemu_mbox *mbox = get_qemu_mbox(link->mbox);
+	u32 *msg = data;
+
+	return regmap_write(mbox->map, QEMU_MBOX_CHAN_ADDR(chan), *msg);
+}
+
+static const struct regmap_config qemu_mbox_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.use_raw_spinlock = true,
+};
+
+static const struct mbox_chan_ops qemu_mbox_chan_ops = {
+	.send_data	= qemu_mbox_send_data,
+};
+
+static int qemu_mbox_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	const char *const name = pci_name(pdev);
+	struct device *const dev = &pdev->dev;
+	void __iomem *qemu_mbox_regs;
+	struct mbox_controller *mbox;
+	struct qemu_mbox *qemu_mbox;
+	unsigned long i;
+	int err;
+
+	err = pcim_enable_device(pdev);
+	if (err)
+		return dev_err_probe(dev, err, "Failed to enable PCI device");
+
+	err = pcim_iomap_regions(pdev, BIT(0), name);
+	if (err)
+		return dev_err_probe(dev, err, "Unable to map PCI I/O addresses");
+
+	qemu_mbox_regs = pcim_iomap_table(pdev)[0];
+
+	qemu_mbox = devm_kzalloc(dev, sizeof(*qemu_mbox), GFP_KERNEL);
+	if (!qemu_mbox)
+		return -ENOMEM;
+
+	qemu_mbox->dev = dev;
+	qemu_mbox->map = devm_regmap_init_mmio(dev, qemu_mbox_regs, &qemu_mbox_regmap_config);
+	if (IS_ERR(qemu_mbox->map))
+		return dev_err_probe(dev, PTR_ERR(qemu_mbox->map),
+				     "Unable to initialize register map\n");
+
+	err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX);
+	if (err < 0)
+		return dev_err_probe(dev, err,
+				     "Unable to allocate irqs\n");
+
+	err =  devm_request_irq(&pdev->dev, pci_irq_vector(pdev, 0),
+				qemu_mbox_isr, 0,
+				"qemu_mbox_isr", qemu_mbox);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "Can't claim irq\n");
+
+	pci_set_master(pdev);
+
+	mbox = &qemu_mbox->mbox;
+	mbox->dev = dev;
+	mbox->ops = &qemu_mbox_chan_ops;
+	mbox->txdone_irq = true;
+	mbox->txdone_poll = false;
+	mbox->num_chans = QEMU_MBOX_MAX_CHAN_CNT;
+	mbox->chans = devm_kcalloc(dev, QEMU_MBOX_MAX_CHAN_CNT,
+				   sizeof(*mbox->chans), GFP_KERNEL);
+	if (!mbox->chans)
+		return -ENOMEM;
+
+	for (i = 0; i < mbox->num_chans; i++)
+		mbox->chans[i].con_priv = (void*)i;
+
+	err = devm_mbox_controller_register(dev, &qemu_mbox->mbox);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "Can't claim irq\n");
+
+	/* TODO: make a list */
+	__mbox = qemu_mbox;
+
+	return 0;
+}
+
+static const struct pci_device_id qemu_mbox_pci_dev_id[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QUMRANET, 0x1111) },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, qemu_mbox_pci_dev_id);
+
+static struct pci_driver qemu_mbox_driver = {
+	.name = "qemu-mailbox",
+	.id_table = qemu_mbox_pci_dev_id,
+	.probe = qemu_mbox_probe
+};
+
+module_pci_driver(qemu_mbox_driver);
+
+MODULE_AUTHOR("Nikita Shubin <nikita.shubin@maquefel.me>");
+MODULE_DESCRIPTION("Mailbox driver for QEMU Virtual PCI");
+MODULE_LICENSE("GPL");
-- 
2.30.2