extend uio driver to supports msix

This patch extends UIO PCI generic driver to
 support MSI-X interrupt.

Tracked-On:projectacrn/acrn-hypervisor#5407

Signed-off-by: Yonghua Huang <yonghua.huang@intel.com>
Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
This commit is contained in:
Yonghua Huang 2022-11-08 15:47:21 +03:00 committed by wenlingz
parent f7b7b085f0
commit 8843484740
3 changed files with 188 additions and 1 deletions

View File

@ -562,6 +562,22 @@ static __poll_t uio_poll(struct file *filep, poll_table *wait)
return 0;
}
#ifdef CONFIG_PCI_MSI
static long uio_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
if (!idev->info)
return -EIO;
if (!idev->info->ioctl)
return -ENOTTY;
return idev->info->ioctl(idev->info, cmd, arg);
}
#endif
static ssize_t uio_read(struct file *filep, char __user *buf,
size_t count, loff_t *ppos)
{
@ -823,6 +839,9 @@ static const struct file_operations uio_fops = {
.write = uio_write,
.mmap = uio_mmap,
.poll = uio_poll,
#ifdef CONFIG_PCI_MSI
.unlocked_ioctl = uio_ioctl,
#endif
.fasync = uio_fasync,
.llseek = noop_llseek,
};

View File

@ -23,16 +23,129 @@
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#ifdef CONFIG_PCI_MSI
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/eventfd.h>
#endif
#define DRIVER_VERSION "0.01.0"
#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices"
#ifdef CONFIG_PCI_MSI
struct uio_msix_info {
struct msix_entry *entries;
struct eventfd_ctx **evts;
int nvecs;
};
#endif
struct uio_pci_generic_dev {
struct uio_info info;
struct pci_dev *pdev;
#ifdef CONFIG_PCI_MSI
struct uio_msix_info msix_info;
#endif
};
#ifdef CONFIG_PCI_MSI
static irqreturn_t uio_msix_handler(int irq, void *arg)
{
struct eventfd_ctx *evt = arg;
eventfd_signal(evt, 1);
return IRQ_HANDLED;
}
static int map_msix_eventfd(struct uio_pci_generic_dev *gdev,
int fd, int vector)
{
int irq, err;
struct eventfd_ctx *evt;
/* Passing -1 is used to disable interrupt */
if (fd < 0) {
pci_disable_msi(gdev->pdev);
return 0;
}
if (vector >= gdev->msix_info.nvecs)
return -EINVAL;
irq = gdev->msix_info.entries[vector].vector;
evt = gdev->msix_info.evts[vector];
if (evt) {
free_irq(irq, evt);
eventfd_ctx_put(evt);
gdev->msix_info.evts[vector] = NULL;
}
evt = eventfd_ctx_fdget(fd);
if (!evt)
return -EINVAL;
err = request_irq(irq, uio_msix_handler, 0, "UIO IRQ", evt);
if (err) {
eventfd_ctx_put(evt);
return err;
}
gdev->msix_info.evts[vector] = evt;
return 0;
}
static int uio_msi_ioctl(struct uio_info *info, unsigned int cmd,
unsigned long arg)
{
struct uio_pci_generic_dev *gdev;
struct uio_msix_data data;
int err = -EOPNOTSUPP;
gdev = container_of(info, struct uio_pci_generic_dev, info);
switch (cmd) {
case UIO_MSIX_DATA: {
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
return -EFAULT;
err = map_msix_eventfd(gdev, data.fd, data.vector);
break;
}
default:
pr_warn("Not support ioctl cmd: 0x%x\n", cmd);
break;
}
return err;
}
static int pci_generic_init_msix(struct uio_pci_generic_dev *gdev)
{
unsigned char *buffer;
int i, nvecs;
nvecs = pci_msix_vec_count(gdev->pdev);
if (!nvecs)
return -EINVAL;
buffer = devm_kzalloc(&gdev->pdev->dev, nvecs * (sizeof(struct msix_entry) +
sizeof(struct eventfd_ctx *)), GFP_KERNEL);
if (!buffer)
return -ENOMEM;
gdev->msix_info.entries = (struct msix_entry *)buffer;
gdev->msix_info.evts = (struct eventfd_ctx **)
((unsigned char *)buffer + nvecs * sizeof(struct msix_entry));
gdev->msix_info.nvecs = nvecs;
for (i = 0; i < nvecs; ++i)
gdev->msix_info.entries[i].entry = i;
return pci_enable_msix_exact(gdev->pdev, gdev->msix_info.entries, nvecs);
}
#endif
static inline struct uio_pci_generic_dev *
to_uio_pci_generic_dev(struct uio_info *info)
{
@ -93,14 +206,23 @@ static int probe(struct pci_dev *pdev,
gdev->info.name = "uio_pci_generic";
gdev->info.version = DRIVER_VERSION;
gdev->info.release = release;
#ifdef CONFIG_PCI_MSI
gdev->info.ioctl = uio_msi_ioctl;
#endif
gdev->pdev = pdev;
if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) {
gdev->info.irq = pdev->irq;
gdev->info.irq_flags = IRQF_SHARED;
gdev->info.handler = irqhandler;
} else {
#ifdef CONFIG_PCI_MSI
err = pci_generic_init_msix(gdev);
if (!err)
dev_notice(&pdev->dev, "MSIX is enabled for UIO device.\n");
#else
dev_warn(&pdev->dev, "No IRQ assigned to device: "
"no support for interrupts?\n");
#endif
}
uiomem = &gdev->info.mem[0];
@ -133,13 +255,46 @@ static int probe(struct pci_dev *pdev,
++uiomem;
}
return devm_uio_register_device(&pdev->dev, &gdev->info);
err = devm_uio_register_device(&pdev->dev, &gdev->info);
if (err)
return err;
#ifdef CONFIG_PCI_MSI
pci_set_drvdata(pdev, gdev);
#endif
return 0;
}
#ifdef CONFIG_PCI_MSI
static void remove(struct pci_dev *pdev)
{
int i, irq;
struct eventfd_ctx *evt;
struct uio_pci_generic_dev *gdev = pci_get_drvdata(pdev);
if (gdev->msix_info.entries != NULL) {
for (i = 0; i < gdev->msix_info.nvecs; i++) {
irq = gdev->msix_info.entries[i].vector;
evt = gdev->msix_info.evts[i];
if (evt) {
free_irq(irq, evt);
eventfd_ctx_put(evt);
gdev->msix_info.evts[i] = NULL;
}
}
pci_disable_msix(pdev);
}
}
#endif
static struct pci_driver uio_pci_driver = {
.name = "uio_pci_generic",
.id_table = NULL, /* only dynamic id's */
.probe = probe,
#ifdef CONFIG_PCI_MSI
.remove = remove,
#endif
};
module_pci_driver(uio_pci_driver);

View File

@ -46,6 +46,15 @@ struct uio_mem {
#define MAX_UIO_MAPS 5
#ifdef CONFIG_PCI_MSI
struct uio_msix_data {
int fd;
int vector;
};
#define UIO_MSIX_DATA _IOW('u', 100, struct uio_msix_data)
#endif
struct uio_portio;
/**
@ -109,6 +118,10 @@ struct uio_info {
int (*open)(struct uio_info *info, struct inode *inode);
int (*release)(struct uio_info *info, struct inode *inode);
int (*irqcontrol)(struct uio_info *info, s32 irq_on);
#ifdef CONFIG_PCI_MSI
int (*ioctl)(struct uio_info *info, unsigned int cmd,
unsigned long arg);
#endif
};
extern int __must_check