uio: Add driver for inter-VM shared memory device
This adds a UIO driver the ivshmem device, found in QEMU and the Jailhouse hypervisor. It exposes the MMIO register region and all shared memory section to userspace. Interrupts are configured in one-shot mode so that userspace needs to re-enable them after each event via the Interrupt Control register. The driver registers all possible MSI-X vectors, coalescing them into the single notifier UIO provides. Note: Specification work for the interface is ongoing, so details may still change. Acked-by: Ye Li <ye.li@nxp.com> Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>5.4-rM2-2.2.x-imx-squashed
parent
5d0c0395a6
commit
92de0e9899
|
@ -165,4 +165,11 @@ config UIO_HV_GENERIC
|
|||
to network and storage devices from userspace.
|
||||
|
||||
If you compile this as a module, it will be called uio_hv_generic.
|
||||
|
||||
config UIO_IVSHMEM
|
||||
tristate "Inter-VM Shared Memory driver"
|
||||
depends on PCI
|
||||
help
|
||||
Userspace I/O driver for the inter-VM shared memory PCI device
|
||||
as provided by QEMU and the Jailhouse hypervisor.
|
||||
endif
|
||||
|
|
|
@ -11,3 +11,4 @@ obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o
|
|||
obj-$(CONFIG_UIO_MF624) += uio_mf624.o
|
||||
obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o
|
||||
obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o
|
||||
obj-$(CONFIG_UIO_IVSHMEM) += uio_ivshmem.o
|
||||
|
|
|
@ -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");
|
|
@ -1477,6 +1477,7 @@
|
|||
|
||||
#define PCI_VENDOR_ID_SIEMENS 0x110A
|
||||
#define PCI_DEVICE_ID_SIEMENS_DSCC4 0x2102
|
||||
#define PCI_DEVICE_ID_IVSHMEM 0x4106
|
||||
|
||||
#define PCI_VENDOR_ID_VORTEX 0x1119
|
||||
#define PCI_DEVICE_ID_VORTEX_GDT60x0 0x0000
|
||||
|
|
Loading…
Reference in New Issue