mirror of
https://github.com/revyos/thead-kernel.git
synced 2026-07-15 21:07:32 +02:00
553 lines
14 KiB
C
553 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/virtio_mailbox.h>
|
|
#include <linux/virtio_ids.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mailbox_controller.h>
|
|
#include <linux/virtio_light.h>
|
|
#include "../light_vringh.h"
|
|
|
|
static struct light_vdev *vdev;
|
|
static volatile unsigned char __iomem *virtio_config;
|
|
static u32 chan_num;
|
|
static u32 msg_size;
|
|
|
|
#define DRV_NAME "light-virtmbox"
|
|
|
|
struct virtio_mbox {
|
|
struct mbox_controller controller;
|
|
struct light_vdev *vdev;
|
|
unsigned int chan_num;
|
|
struct mbox_chan *chans;
|
|
struct virtio_chan *vchans;
|
|
struct light_vringh *vrh;
|
|
bool ready;
|
|
};
|
|
|
|
struct virtio_chan {
|
|
struct light_vringh *in_vrh;
|
|
struct light_vringh *out_vrh;
|
|
struct virtio_mbox *vmbox;
|
|
struct mbox_chan *chan;
|
|
bool in_use;
|
|
u8 idx;
|
|
spinlock_t lock;
|
|
};
|
|
|
|
static inline volatile unsigned char *offset(u32 index)
|
|
{
|
|
return virtio_config + VIRTIO_LIGHT_CONFIG + offsets[index];
|
|
}
|
|
|
|
static void light_vmbox_notify(struct vringh *vrh)
|
|
{
|
|
#ifdef CONFIG_SOC_INT_SRC7
|
|
writel(1, vdev->frontend_intr_reg);
|
|
#else
|
|
/* Write the interrupt register to signal the other end */
|
|
writel(1, vdev->frontend_intr_reg + 0x10);
|
|
#endif
|
|
}
|
|
|
|
static inline bool light_vmbox_vring_avail(struct light_vringh *lvrh)
|
|
{
|
|
/*
|
|
* vrh.last_avail_idx will be +1 after fetch a new avail idx
|
|
* in vringh_getdesc_kern().
|
|
*/
|
|
return lvrh->vrh.last_avail_idx != lvrh->vring.vr.avail->idx;
|
|
}
|
|
|
|
static int light_vmbox_vring_process(struct light_vringh *lvrh, void *vmsg,
|
|
struct mbox_chan *chan, bool read)
|
|
{
|
|
struct virtio_chan *vchan = chan->con_priv;
|
|
u16 *avail_head = &lvrh->head;
|
|
struct kvec *kiov;
|
|
struct vringh_kiov *riov = &lvrh->riov;
|
|
struct vringh_kiov *wiov = &lvrh->wiov;
|
|
unsigned long vaddr;
|
|
struct vringh *vrh = &lvrh->vrh;
|
|
int ret = 0;
|
|
|
|
/* normally, we can get avail buf in the first while loop */
|
|
while (read && !light_vmbox_vring_avail(lvrh)) {
|
|
cpu_relax();
|
|
pr_debug("we are waiting for one avail buf in in_queue\n");
|
|
}
|
|
|
|
if (riov->i == riov->used && wiov->i == wiov->used) {
|
|
ret = vringh_getdesc_kern(vrh, riov, wiov, avail_head, GFP_ATOMIC);
|
|
/* Check if there are available descriptors */
|
|
if (ret <= 0)
|
|
return ret;
|
|
}
|
|
|
|
if (read)
|
|
kiov = &wiov->iov[wiov->i];
|
|
else
|
|
kiov = &riov->iov[riov->i];
|
|
|
|
vaddr = (unsigned long)phys_to_virt((unsigned long)kiov->iov_base);
|
|
|
|
if (read) {
|
|
memcpy((void *)vaddr, vmsg, msg_size);
|
|
writel(1, offset(MSG_H) + vchan->idx * 4);
|
|
} else {
|
|
mbox_chan_received_data(chan, (void *)vaddr);
|
|
writel(1, offset(ACK_H) + vchan->idx * 4);
|
|
}
|
|
|
|
vringh_complete_kern(vrh, *avail_head, msg_size);
|
|
if (vringh_need_notify_kern(vrh) > 0)
|
|
vringh_notify(vrh); /* send Ack to frontend */
|
|
|
|
vringh_kiov_cleanup(riov);
|
|
vringh_kiov_cleanup(wiov);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void light_vmbox_msg_process(struct light_vdev *vdev)
|
|
{
|
|
struct virtio_mbox *vmbox = vdev->priv;
|
|
struct virtio_chan *vchans = vmbox->vchans;
|
|
struct light_vringh *lvrh;
|
|
unsigned long flags;
|
|
u32 msg;
|
|
int i;
|
|
|
|
for (i = 0; i < vmbox->chan_num; i++) {
|
|
spin_lock_irqsave(&vchans[i].lock, flags);
|
|
|
|
msg = readl(offset(MSG_G) + vchans[i].idx * 4);
|
|
if (vchans[i].in_use && msg) {
|
|
lvrh = vchans[i].out_vrh;
|
|
|
|
/*
|
|
* don't worry, it will be fast.
|
|
* normally, we only has one avail desc to handle
|
|
*/
|
|
while (light_vmbox_vring_avail(lvrh))
|
|
light_vmbox_vring_process(lvrh, NULL,
|
|
vchans[i].chan, false);
|
|
|
|
writel(0, offset(MSG_G) + vchans[i].idx * 4);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&vchans[i].lock, flags);
|
|
}
|
|
}
|
|
|
|
static void light_vmbox_ack_process(struct light_vdev *vdev)
|
|
{
|
|
struct virtio_mbox *vmbox = vdev->priv;
|
|
struct virtio_chan *vchans = vmbox->vchans;
|
|
unsigned long flags;
|
|
u32 ack;
|
|
int i;
|
|
|
|
for (i = 0; i < vmbox->chan_num; i++) {
|
|
spin_lock_irqsave(&vchans[i].lock, flags);
|
|
|
|
ack = readl(offset(ACK_G) + vchans[i].idx * 4);
|
|
if (vchans[i].in_use && ack) {
|
|
mbox_chan_txdone(vchans[i].chan, 0);
|
|
writel(0, offset(ACK_G) + vchans[i].idx * 4);
|
|
}
|
|
|
|
spin_unlock_irqrestore(&vchans[i].lock, flags);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t light_vmbox_interrupt(int irq, void *opaque)
|
|
{
|
|
struct light_vdev *virt_dev;
|
|
|
|
virt_dev = (struct light_vdev *)opaque;
|
|
if (virt_dev->virtio_id != VIRTIO_ID_MAILBOX)
|
|
return IRQ_NONE;
|
|
|
|
#ifdef CONFIG_SOC_INT_SRC7
|
|
writel(0, vdev->backend_intr_reg);
|
|
#else
|
|
/* clear interrupt*/
|
|
writel(vdev->clear_intr, vdev->backend_intr_reg + 0x4);
|
|
#endif
|
|
|
|
/* handle Ack */
|
|
light_vmbox_ack_process(virt_dev);
|
|
|
|
/* handle Msg */
|
|
light_vmbox_msg_process(virt_dev);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int light_vmbox_vring_map(struct light_vdev *vdev)
|
|
{
|
|
int i, ret, vr_size;
|
|
phys_addr_t phys_addr;
|
|
struct virtio_mbox *vmbox = vdev->priv;
|
|
u32 vq_num;
|
|
u32 entry_num;
|
|
u32 align;
|
|
u32 count = 0;
|
|
|
|
/* Wait frontend driver initialization ready, then we can go */
|
|
while (!(readl(virtio_config + VIRTIO_LIGHT_STATUS) & VIRTIO_CONFIG_S_DRIVER_OK)) {
|
|
msleep(200);
|
|
if (count++ == 50) {
|
|
count = 0;
|
|
pr_debug("Vmbox backend: waiting frontend driver ready!!!\n");
|
|
}
|
|
}
|
|
/* We already know frontend is ready, now reset status */
|
|
writel(0, virtio_config + VIRTIO_LIGHT_STATUS);
|
|
|
|
entry_num = readl(virtio_config + VIRTIO_LIGHT_QUEUE_SIZE);
|
|
if (entry_num == 0)
|
|
return -EINVAL;
|
|
|
|
align = readl(virtio_config + VIRTIO_LIGHT_QUEUE_ALIGN);
|
|
if (align < 0)
|
|
return -EINVAL;
|
|
|
|
vq_num = readl(virtio_config + VIRTIO_LIGHT_QUEUE_NUM);
|
|
if (vq_num == 0 || vq_num != 2 * chan_num) {
|
|
pr_info("Vmbox backend: vq num not match(%d, %d)\n",
|
|
vq_num, 2 * chan_num);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < vq_num; i++) {
|
|
struct light_vringh *vvr = &vdev->vvr[i];
|
|
struct light_vring *vr = &vdev->vvr[i].vring;
|
|
|
|
vr_size = PAGE_ALIGN(round_up(vring_size(entry_num, align), 4));
|
|
|
|
/* init vring */
|
|
phys_addr = (phys_addr_t)readl(virtio_config +
|
|
VIRTIO_LIGHT_QUEUE_PFN + i * 4) << PAGE_SHIFT;
|
|
vr->va = (void *) phys_to_virt(phys_addr);
|
|
pr_debug("Vmbox backend: vring(%d) virtual addr %lx, phys addr %llx\n",
|
|
i, (unsigned long)vr->va, phys_addr);
|
|
|
|
vr->len = vr_size;
|
|
|
|
vring_init(&vr->vr, entry_num, vr->va, align);
|
|
ret = vringh_init_kern(&vvr->vrh,
|
|
0, /* don't need features */
|
|
entry_num, false, vr->vr.desc, vr->vr.avail,
|
|
vr->vr.used);
|
|
if (ret) {
|
|
pr_err("%s %d err %d\n", __func__, __LINE__, ret);
|
|
goto err;
|
|
}
|
|
|
|
vringh_kiov_init(&vvr->riov, NULL, 0);
|
|
vringh_kiov_init(&vvr->wiov, NULL, 0);
|
|
vvr->vdev = vdev;
|
|
vvr->vrh.notify = light_vmbox_notify;
|
|
}
|
|
|
|
if (request_irq(vdev->irq, light_vmbox_interrupt, IRQF_SHARED,
|
|
"virtio_mbox", vdev) < 0) {
|
|
pr_err("Vmbox backend: register IRQ %d failed\n", vdev->irq);
|
|
goto err;
|
|
}
|
|
|
|
vmbox->ready = true;
|
|
|
|
pr_info("Vmbox backend added device(%d) with %d vqs %d entries.\n",
|
|
vdev->virtio_id, vq_num, entry_num);
|
|
return 0;
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static struct mbox_chan *light_vmbox_xlate(struct mbox_controller *mbox,
|
|
const struct of_phandle_args *sp)
|
|
{
|
|
struct mbox_chan *chan;
|
|
unsigned int ch = sp->args[0];
|
|
struct virtio_chan *vchan;
|
|
unsigned long flags;
|
|
|
|
if (ch >= mbox->num_chans) {
|
|
dev_err(mbox->dev, "Invalid channel idx %d\n", ch);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
chan = &mbox->chans[ch];
|
|
vchan = chan->con_priv;
|
|
|
|
spin_lock_irqsave(&vchan->lock, flags);
|
|
if (vchan->in_use) {
|
|
dev_err(mbox->dev, "Channel idx %d is in use\n", ch);
|
|
spin_unlock_irqrestore(&vchan->lock, flags);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
spin_unlock_irqrestore(&vchan->lock, flags);
|
|
|
|
return chan;
|
|
}
|
|
|
|
static int light_vmbox_send_data(struct mbox_chan *chan, void *vmsg)
|
|
{
|
|
int ret;
|
|
|
|
struct virtio_chan *vchan = chan->con_priv;
|
|
|
|
if (!vchan->vmbox->ready)
|
|
return 0;
|
|
|
|
ret = light_vmbox_vring_process(vchan->in_vrh, vmsg, chan, true);
|
|
if (ret)
|
|
dev_err(chan->mbox->dev, "message send error %d\n", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int light_vmbox_startup(struct mbox_chan *chan)
|
|
{
|
|
struct virtio_chan *vchan = chan->con_priv;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&vchan->lock, flags);
|
|
vchan->in_use = true;
|
|
spin_unlock_irqrestore(&vchan->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void light_vmbox_shutdown(struct mbox_chan *chan)
|
|
{
|
|
struct virtio_chan *vchan = chan->con_priv;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&vchan->lock, flags);
|
|
vchan->in_use = false;
|
|
spin_unlock_irqrestore(&vchan->lock, flags);
|
|
}
|
|
|
|
static const struct mbox_chan_ops light_vmbox_ops = {
|
|
.send_data = light_vmbox_send_data,
|
|
.startup = light_vmbox_startup,
|
|
.shutdown = light_vmbox_shutdown,
|
|
};
|
|
|
|
static int light_vmbox_setup(struct light_vdev* vdev)
|
|
{
|
|
struct virtio_mbox *vmbox;
|
|
struct virtio_chan *vchans;
|
|
int ret = -ENOMEM;
|
|
int i;
|
|
|
|
vmbox = devm_kzalloc(vdev->device, sizeof(*vmbox), GFP_KERNEL);
|
|
if (!vmbox)
|
|
return ret;
|
|
vdev->priv = vmbox;
|
|
|
|
/*
|
|
* vmbox->ready is set to true after guest ready,
|
|
* then user can use vmbox normally.
|
|
*/
|
|
vmbox->ready = false;
|
|
|
|
vmbox->vdev = vdev;
|
|
vmbox->chan_num = chan_num;
|
|
vmbox->chans = devm_kcalloc(vmbox->vdev->device, vmbox->chan_num,
|
|
sizeof(*vmbox->chans), GFP_KERNEL);
|
|
if (!vmbox->chans)
|
|
goto free_vmbox;
|
|
|
|
vchans = devm_kcalloc(vmbox->vdev->device, vmbox->chan_num,
|
|
sizeof(*vchans), GFP_KERNEL);
|
|
if (!vchans)
|
|
goto free_chans;
|
|
vmbox->vchans = vchans;
|
|
|
|
vmbox->controller.dev = vmbox->vdev->device;
|
|
vmbox->controller.num_chans = vmbox->chan_num;
|
|
vmbox->controller.chans = &vmbox->chans[0];
|
|
vmbox->controller.ops = &light_vmbox_ops;
|
|
vmbox->controller.of_xlate = &light_vmbox_xlate;
|
|
vmbox->controller.txdone_irq = true; /* see comments of mbox_chan_txdone() */
|
|
|
|
/* map chan to in,out vrh pair */
|
|
for (i = 0; i < vmbox->chan_num; i++) {
|
|
vchans[i].vmbox = vmbox;
|
|
vchans[i].chan = &vmbox->chans[i];
|
|
vchans[i].idx = i;
|
|
vchans[i].in_vrh = &vdev->vvr[2 * i];
|
|
vchans[i].out_vrh = &vdev->vvr[2 * i + 1];
|
|
spin_lock_init(&(vchans[i].lock));
|
|
|
|
/* The standard mailbox channel mapped to vq */
|
|
vmbox->chans[i].con_priv = &vchans[i];
|
|
}
|
|
|
|
ret = devm_mbox_controller_register(vmbox->vdev->device,
|
|
&vmbox->controller);
|
|
if (ret) {
|
|
dev_err(vmbox->vdev->device, "Could not register mailbox controller\n");
|
|
goto free_vchans;
|
|
}
|
|
|
|
dev_set_drvdata(vmbox->vdev->device, vmbox);
|
|
|
|
dev_info(vmbox->vdev->device, "registered %d channels\n", vmbox->chan_num);
|
|
|
|
return 0;
|
|
|
|
free_vchans:
|
|
kfree(vchans);
|
|
free_chans:
|
|
kfree(vmbox->chans);
|
|
free_vmbox:
|
|
kfree(vmbox);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void vmbox_work_handler(struct work_struct *work)
|
|
{
|
|
if (light_vmbox_setup(vdev)) {
|
|
pr_err("Mbox backend: vmbox setup failed!!!\n");
|
|
return;
|
|
}
|
|
|
|
if (light_vmbox_vring_map(vdev)) {
|
|
pr_err("Mbox backend: vring map failed!!!\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int light_mbox_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *node = dev->of_node;
|
|
u64 features;
|
|
int i, irq, rc = 0;
|
|
char magic[4] = {'v', 'i', 'r', 't'};
|
|
|
|
/* Alloc light_vdev */
|
|
vdev = devm_kzalloc(dev, sizeof(struct light_vdev), GFP_KERNEL);
|
|
if (!vdev)
|
|
return -ENOMEM;
|
|
vdev->device = dev;
|
|
|
|
INIT_WORK(&vdev->work, vmbox_work_handler);
|
|
|
|
mutex_init(&vdev->mutex);
|
|
|
|
virtio_config = ioremap(0x1fe000, VIRTIO_LIGHT_CONFIG_LEN);
|
|
|
|
/* Reset status, frontend will fill it */
|
|
writel(0, virtio_config + VIRTIO_LIGHT_STATUS);
|
|
|
|
features |= 1ULL << VIRTIO_F_VERSION_1;
|
|
features |= 1ULL << VIRTIO_MAILBOX_F_CHAN_NUM;
|
|
features |= 1ULL << VIRTIO_MAILBOX_F_MSG_SIZE;
|
|
writel(features, virtio_config + VIRTIO_LIGHT_DEVICE_FEATURES_LOW);
|
|
|
|
writel(*(u32 *)((void *)magic), virtio_config +
|
|
VIRTIO_LIGHT_MAGIC_VALUE);
|
|
|
|
writel(1, virtio_config + VIRTIO_LIGHT_VERSION);
|
|
|
|
vdev->virtio_id = VIRTIO_ID_MAILBOX;
|
|
writel(vdev->virtio_id, virtio_config + VIRTIO_LIGHT_DEVICE_ID);
|
|
|
|
writel(64, virtio_config + VIRTIO_LIGHT_QUEUE_SIZE_MAX);
|
|
|
|
for (i = 0; i < chan_num; i++) {
|
|
writel(0, offset(ACK_H) + i * 4);
|
|
writel(0, offset(MSG_H) + i * 4);
|
|
}
|
|
|
|
/* Get vmbox chan_numm & chan_size */
|
|
rc = of_property_read_u32(node, "chan_num", &chan_num);
|
|
if (rc < 0) {
|
|
chan_num = VIRTIO_MAILBOX_CHAN_NUM_DEFAULT;
|
|
pr_err("Vmbox backend: use default chan_num: %d\n", chan_num);
|
|
}
|
|
writel(chan_num, offset(CHAN_NUM));
|
|
|
|
rc = of_property_read_u32(node, "msg_size", &msg_size);
|
|
if (rc < 0) {
|
|
msg_size = VIRTIO_MAILBOX_MSG_SIZE_DEFAULT;
|
|
pr_err("Vmbox backend: use default msg_size: %d\n", msg_size);
|
|
}
|
|
writel(msg_size, offset(MSG_SIZE));
|
|
|
|
#ifdef CONFIG_SOC_INT_SRC7
|
|
vdev->frontend_intr_reg = ioremap(0xFFEF018094, 4);
|
|
vdev->backend_intr_reg = ioremap(0xFFFF019094, 4);
|
|
#else
|
|
/* MPW use mailbox as interrupt */
|
|
vdev->frontend_intr_reg = ioremap(0xffefc50000, 0x100);
|
|
vdev->backend_intr_reg = ioremap(0xffffc3b000, 0x100);
|
|
vdev->enable_intr = 1;
|
|
vdev->clear_intr = 1;
|
|
writel(vdev->enable_intr, vdev->frontend_intr_reg + 0xc);
|
|
writel(vdev->clear_intr, vdev->frontend_intr_reg + 0x4);
|
|
#endif
|
|
|
|
/* Common IRQ request */
|
|
irq = of_irq_get(node, 0);
|
|
if (irq <= 0) {
|
|
pr_err("Cannot get IRQ resource for blk backend\n");
|
|
rc = -EINVAL;
|
|
goto err;
|
|
}
|
|
vdev->irq = irq;
|
|
|
|
schedule_work(&vdev->work);
|
|
|
|
pr_info("light_mbox backend driver init successfully(%d %d)\n",
|
|
chan_num, msg_size);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
kfree(vdev);
|
|
return rc;
|
|
}
|
|
|
|
static int light_mbox_remove(struct platform_device *pdev)
|
|
{
|
|
cancel_work_sync(&vdev->work);
|
|
free_irq(vdev->irq, vdev);
|
|
kfree(vdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id light_of_table[] = {
|
|
{ .compatible = "thead,virtmbox-backend" },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, light_of_table);
|
|
|
|
static struct platform_driver light_mbox_driver = {
|
|
.probe = light_mbox_probe,
|
|
.remove = light_mbox_remove,
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.of_match_table = light_of_table,
|
|
},
|
|
};
|
|
module_platform_driver(light_mbox_driver);
|
|
|
|
MODULE_DESCRIPTION("T-head virtio-mailbox backend driver");
|
|
MODULE_AUTHOR("Xianting Tian <xianting.tian@linux.alibaba.com>");
|
|
MODULE_LICENSE("GPL");
|