--- /dev/null
+// 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");