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:
parent
f7b7b085f0
commit
8843484740
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue