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;
|
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,
|
static ssize_t uio_read(struct file *filep, char __user *buf,
|
||||||
size_t count, loff_t *ppos)
|
size_t count, loff_t *ppos)
|
||||||
{
|
{
|
||||||
|
@ -823,6 +839,9 @@ static const struct file_operations uio_fops = {
|
||||||
.write = uio_write,
|
.write = uio_write,
|
||||||
.mmap = uio_mmap,
|
.mmap = uio_mmap,
|
||||||
.poll = uio_poll,
|
.poll = uio_poll,
|
||||||
|
#ifdef CONFIG_PCI_MSI
|
||||||
|
.unlocked_ioctl = uio_ioctl,
|
||||||
|
#endif
|
||||||
.fasync = uio_fasync,
|
.fasync = uio_fasync,
|
||||||
.llseek = noop_llseek,
|
.llseek = noop_llseek,
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,16 +23,129 @@
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/uio_driver.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_VERSION "0.01.0"
|
||||||
#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
|
#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
|
||||||
#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices"
|
#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_pci_generic_dev {
|
||||||
struct uio_info info;
|
struct uio_info info;
|
||||||
struct pci_dev *pdev;
|
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 *
|
static inline struct uio_pci_generic_dev *
|
||||||
to_uio_pci_generic_dev(struct uio_info *info)
|
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.name = "uio_pci_generic";
|
||||||
gdev->info.version = DRIVER_VERSION;
|
gdev->info.version = DRIVER_VERSION;
|
||||||
gdev->info.release = release;
|
gdev->info.release = release;
|
||||||
|
#ifdef CONFIG_PCI_MSI
|
||||||
|
gdev->info.ioctl = uio_msi_ioctl;
|
||||||
|
#endif
|
||||||
gdev->pdev = pdev;
|
gdev->pdev = pdev;
|
||||||
if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) {
|
if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) {
|
||||||
gdev->info.irq = pdev->irq;
|
gdev->info.irq = pdev->irq;
|
||||||
gdev->info.irq_flags = IRQF_SHARED;
|
gdev->info.irq_flags = IRQF_SHARED;
|
||||||
gdev->info.handler = irqhandler;
|
gdev->info.handler = irqhandler;
|
||||||
} else {
|
} 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: "
|
dev_warn(&pdev->dev, "No IRQ assigned to device: "
|
||||||
"no support for interrupts?\n");
|
"no support for interrupts?\n");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
uiomem = &gdev->info.mem[0];
|
uiomem = &gdev->info.mem[0];
|
||||||
|
@ -133,13 +255,46 @@ static int probe(struct pci_dev *pdev,
|
||||||
++uiomem;
|
++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 = {
|
static struct pci_driver uio_pci_driver = {
|
||||||
.name = "uio_pci_generic",
|
.name = "uio_pci_generic",
|
||||||
.id_table = NULL, /* only dynamic id's */
|
.id_table = NULL, /* only dynamic id's */
|
||||||
.probe = probe,
|
.probe = probe,
|
||||||
|
#ifdef CONFIG_PCI_MSI
|
||||||
|
.remove = remove,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
module_pci_driver(uio_pci_driver);
|
module_pci_driver(uio_pci_driver);
|
||||||
|
|
|
@ -46,6 +46,15 @@ struct uio_mem {
|
||||||
|
|
||||||
#define MAX_UIO_MAPS 5
|
#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;
|
struct uio_portio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,6 +118,10 @@ struct uio_info {
|
||||||
int (*open)(struct uio_info *info, struct inode *inode);
|
int (*open)(struct uio_info *info, struct inode *inode);
|
||||||
int (*release)(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);
|
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
|
extern int __must_check
|
||||||
|
|
Loading…
Reference in New Issue