diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index e55e8cef8def..118123758b93 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -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, }; diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c index e03f9b532a96..cff8454f9e61 100644 --- a/drivers/uio/uio_pci_generic.c +++ b/drivers/uio/uio_pci_generic.c @@ -23,16 +23,129 @@ #include #include #include +#ifdef CONFIG_PCI_MSI +#include +#include +#include +#endif #define DRIVER_VERSION "0.01.0" #define DRIVER_AUTHOR "Michael S. Tsirkin " #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); diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 47c5962b876b..44bcbe6468c1 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -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