diff options
Diffstat (limited to 'drivers/uio/uio_ivshmem.c')
-rw-r--r-- | drivers/uio/uio_ivshmem.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/drivers/uio/uio_ivshmem.c b/drivers/uio/uio_ivshmem.c new file mode 100644 index 000000000000..0c16d428c6ed --- /dev/null +++ b/drivers/uio/uio_ivshmem.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UIO driver for Inter-VM shared memory PCI device + * + * Copyright (c) Siemens AG, 2019 + * + * Authors: + * Jan Kiszka <jan.kiszka@siemens.com> + */ + +#include <linux/ivshmem.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/uio_driver.h> + +#define DRV_NAME "uio_ivshmem" + +struct ivshm_dev { + struct uio_info info; + struct pci_dev *pdev; + struct ivshm_regs __iomem *regs; + int vectors; +}; + +static irqreturn_t ivshm_irq_handler(int irq, void *dev_id) +{ + struct ivshm_dev *ivshm_dev = (struct ivshm_dev *)dev_id; + + /* nothing else to do, we configured one-shot interrupt mode */ + uio_event_notify(&ivshm_dev->info); + + return IRQ_HANDLED; +} + +static u64 get_config_qword(struct pci_dev *pdev, unsigned int pos) +{ + u32 lo, hi; + + pci_read_config_dword(pdev, pos, &lo); + pci_read_config_dword(pdev, pos + 4, &hi); + return lo | ((u64)hi << 32); +} + +static int ivshm_release(struct uio_info *info, struct inode *inode) +{ + struct ivshm_dev *ivshm_dev = + container_of(info, struct ivshm_dev, info); + + writel(0, &ivshm_dev->regs->state); + return 0; +} + +static int ivshm_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + resource_size_t rw_section_sz, output_section_sz; + struct ivshm_dev *ivshm_dev; + phys_addr_t section_addr; + int err, vendor_cap, i; + unsigned int cap_pos; + struct uio_mem *mem; + char *device_name; + u32 dword; + + ivshm_dev = devm_kzalloc(&pdev->dev, sizeof(struct ivshm_dev), + GFP_KERNEL); + if (!ivshm_dev) + return -ENOMEM; + + err = pcim_enable_device(pdev); + if (err) + return err; + + device_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s[%s]", DRV_NAME, + dev_name(&pdev->dev)); + if (!device_name) + return -ENOMEM; + + ivshm_dev->info.name = device_name; + ivshm_dev->info.version = "1"; + ivshm_dev->info.release = ivshm_release; + + err = pcim_iomap_regions(pdev, BIT(0), device_name); + if (err) + return err; + ivshm_dev->regs = pcim_iomap_table(pdev)[0]; + + mem = &ivshm_dev->info.mem[0]; + + mem->name = "registers"; + mem->addr = pci_resource_start(pdev, 0); + if (!mem->addr) + return -ENODEV; + mem->size = pci_resource_len(pdev, 0); + mem->memtype = UIO_MEM_PHYS; + + vendor_cap = pci_find_capability(pdev, PCI_CAP_ID_VNDR); + if (vendor_cap < 0) + return -ENODEV; + + if (pci_resource_len(pdev, 2) > 0) { + section_addr = pci_resource_start(pdev, 2); + } else { + cap_pos = vendor_cap + IVSHM_CFG_ADDRESS; + section_addr = get_config_qword(pdev, cap_pos); + } + + mem++; + mem->name = "state_table"; + mem->addr = section_addr; + cap_pos = vendor_cap + IVSHM_CFG_STATE_TAB_SZ; + pci_read_config_dword(pdev, cap_pos, &dword); + mem->size = dword; + mem->memtype = UIO_MEM_IOVA; + mem->readonly = true; + if (!devm_request_mem_region(&pdev->dev, mem->addr, mem->size, + device_name)) + return -EBUSY; + dev_info(&pdev->dev, "%s at %pa, size %pa\n", mem->name, &mem->addr, + &mem->size); + + cap_pos = vendor_cap + IVSHM_CFG_RW_SECTION_SZ; + rw_section_sz = get_config_qword(pdev, cap_pos); + if (rw_section_sz > 0) { + section_addr += mem->size; + + mem++; + mem->name = "rw_section"; + mem->addr = section_addr; + mem->size = rw_section_sz; + mem->memtype = UIO_MEM_IOVA; + if (!devm_request_mem_region(&pdev->dev, mem->addr, mem->size, + device_name)) + return -EBUSY; + dev_info(&pdev->dev, "%s at %pa, size %pa\n", mem->name, + &mem->addr, &mem->size); + } + + cap_pos = vendor_cap + IVSHM_CFG_OUTPUT_SECTION_SZ; + output_section_sz = get_config_qword(pdev, cap_pos); + if (output_section_sz > 0) { + section_addr += mem->size; + + mem++; + mem->name = "input_sections"; + mem->addr = section_addr; + mem->size = + readl(&ivshm_dev->regs->max_peers) * output_section_sz; + mem->memtype = UIO_MEM_IOVA; + mem->readonly = true; + if (!devm_request_mem_region(&pdev->dev, mem->addr, mem->size, + device_name)) + return -EBUSY; + dev_info(&pdev->dev, "%s at %pa, size %pa\n", mem->name, + &mem->addr, &mem->size); + + mem++; + mem->name = "output_section"; + mem->addr = section_addr + + readl(&ivshm_dev->regs->id) * output_section_sz; + mem->size = output_section_sz; + mem->memtype = UIO_MEM_IOVA; + dev_info(&pdev->dev, "%s at %pa, size %pa\n", mem->name, + &mem->addr, &mem->size); + } + + pci_write_config_byte(pdev, vendor_cap + IVSHM_CFG_PRIV_CNTL, + IVSHM_PRIV_CNTL_ONESHOT_INT); + + /* + * Grab all vectors although we can only coalesce them into a single + * notifier. This avoids missing any event. + */ + ivshm_dev->vectors = pci_msix_vec_count(pdev); + if (ivshm_dev->vectors < 0) + ivshm_dev->vectors = 1; + + err = pci_alloc_irq_vectors(pdev, ivshm_dev->vectors, + ivshm_dev->vectors, + PCI_IRQ_LEGACY | PCI_IRQ_MSIX); + if (err < 0) + return err; + + for (i = 0; i < ivshm_dev->vectors; i++) { + err = request_irq(pci_irq_vector(pdev, i), ivshm_irq_handler, + IRQF_SHARED, ivshm_dev->info.name, ivshm_dev); + if (err) + goto error; + } + + ivshm_dev->info.irq = UIO_IRQ_CUSTOM; + + err = uio_register_device(&pdev->dev, &ivshm_dev->info); + if (err) + goto error; + + pci_set_master(pdev); + + pci_set_drvdata(pdev, ivshm_dev); + + return 0; + +error: + while (--i > 0) + free_irq(pci_irq_vector(pdev, i), ivshm_dev); + pci_free_irq_vectors(pdev); + return err; +} + +static void ivshm_remove(struct pci_dev *pdev) +{ + struct ivshm_dev *ivshm_dev = pci_get_drvdata(pdev); + int i; + + writel(0, &ivshm_dev->regs->int_control); + pci_clear_master(pdev); + + uio_unregister_device(&ivshm_dev->info); + + for (i = 0; i < ivshm_dev->vectors; i++) + free_irq(pci_irq_vector(pdev, i), ivshm_dev); + + pci_free_irq_vectors(pdev); +} + +static const struct pci_device_id ivshm_device_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SIEMENS, PCI_DEVICE_ID_IVSHMEM), + (PCI_CLASS_OTHERS << 16) | IVSHM_PROTO_UNDEFINED, 0xffffff }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ivshm_device_id_table); + +static struct pci_driver uio_ivshm_driver = { + .name = DRV_NAME, + .id_table = ivshm_device_id_table, + .probe = ivshm_probe, + .remove = ivshm_remove, +}; +module_pci_driver(uio_ivshm_driver); + +MODULE_AUTHOR("Jan Kiszka <jan.kiszka@siemens.com>"); +MODULE_LICENSE("GPL v2"); |