diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index c47f04d..e99efba 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o +obj-y += ivshmem.o diff --git a/drivers/virt/ivshmem.c b/drivers/virt/ivshmem.c new file mode 100644 index 0000000..72b30dc --- /dev/null +++ b/drivers/virt/ivshmem.c @@ -0,0 +1,250 @@ +/* + * + * IVSHMEM driver + * + * Copyright 2014 (C) Red Hat, Inc. + * + * Author: Levente Kurusa + * + * This code is licensed under the conditions of GPLv2. + * + * Contributions after 10-24-2014 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Goal of this driver: + * + * To export ivshmem to userspace without any job-specific tools. + * (Secret reason: Unify ivshmem users so we can update ivshmem more easily) + * + */ + + +/* + * FIXME: wohoo, bad practice, + * these are most likely either already defined or there is no need to define + * them at all... + */ +#define PCI_VENDOR_ID_REDHAT 0x1AF4 +/* taken straight from QEMU:hw/misc/ivshmem.c */ +#define PCI_DEVICE_ID_IVSHMEM 0x1110 +#define DRV_NAME "ivshmem" +#define DEV_NAME "ivshmem" + +/* number of ivshmem devices found so far */ +static int no_ivshmem; + +static dev_t dev_ivshmem; + +struct ivshmem_dev { + int minor; + char *name; + + struct pci_dev *pdev; + /* ivshmem data stored in BAR2 */ + void *shm_data; + int shm_size; + + /* the character device */ + struct cdev cdev; +}; + +static const struct pci_device_id ivshmem_pci_tbl[] = { + { PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_IVSHMEM, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { } +}; + +static ssize_t ivshmem_read(struct file *filp, char *buf, size_t len, loff_t *loff) +{ + struct ivshmem_dev *iv = filp->private_data; + int rc; + + if (*loff >= iv->shm_size) + return 0; + + if (*loff + len >= iv->shm_size) + len = iv->shm_size - *loff; + + rc = copy_to_user(buf, iv->shm_data + *loff, len); + if (rc) + return -EFAULT; + + *loff += len; + return len; +} + +static ssize_t ivshmem_write(struct file *filp, const char *buf, size_t len, + loff_t *loff) +{ + /* + * To avoid the problem mentioned before, we simply put the bytes + * at the location pointed by @loff + */ + struct ivshmem_dev *iv = filp->private_data; + int rc; + + /* + * SHM objects on the host cannot be resized! + */ + if (*loff + len >= iv->shm_size) + return -EINVAL; + + if (*loff >= iv->shm_size) + return -EINVAL; + + rc = copy_from_user(iv->shm_data, buf, len); + if (rc) + return -EFAULT; + + *loff += len; + return len; +} + +static int ivshmem_open(struct inode *i, struct file *filp) +{ + struct ivshmem_dev *iv; + int rc; + + /* find ivshmem_dev structure */ + iv = container_of(i->i_cdev, struct ivshmem_dev, cdev); + filp->private_data = iv; + + /* request access to BAR 2 */ + rc = pci_request_region(iv->pdev, 2, DRV_NAME); + if (rc) { + dev_err(&iv->pdev->dev, "failed to request BAR 2\n"); + return rc; + } + iv->shm_size = pci_resource_len(iv->pdev, 2); + iv->shm_data = pci_iomap(iv->pdev, 2, iv->shm_size); + if (!iv->shm_data || !iv->shm_size) { + dev_err(&iv->pdev->dev, "failed to get a working configuration of BAR 2\n"); + /* FIXME: not sure if correct. */ + return -ENXIO; + } + + dev_info(&iv->pdev->dev, "opened: minor %d ptr: 0x%p size: 0x%x\n", iv->minor, + iv->shm_data, iv->shm_size); + return 0; +} + +static int ivshmem_release(struct inode *i, struct file *filp) +{ + struct ivshmem_dev *iv = filp->private_data; + + pci_release_region(iv->pdev, 2); + pci_iounmap(iv->pdev, iv->shm_data); + iv->shm_size = 0; + iv->shm_data = NULL; + return 0; +} + +static struct file_operations fops = { + .read = ivshmem_read, + .write = ivshmem_write, + .open = ivshmem_open, + .release = ivshmem_release, +}; + +static int ivshmem_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + int rc, devno; + struct ivshmem_dev *iv; + + if (no_ivshmem >= 128) { + dev_warn(&pdev->dev, "device not registered! out of minor numbers\n"); + /* FIXME: not the greatest err code... */ + return -EMFILE; + } + + /* enable the device if not done by BIOS */ + rc = pci_enable_device(pdev); + if (rc) { + dev_warn(&pdev->dev, "failed to enable ivshmem device\n"); + return rc; + } + + /* allocate ivshmem_dev structure */ + iv = kmalloc(sizeof(*iv), GFP_KERNEL); + if (!iv) + return -ENOMEM; + + /* set name */ + iv->name = kasprintf(GFP_KERNEL, DEV_NAME "%d", no_ivshmem); + if (!iv->name) + return -ENOMEM; + iv->pdev = pdev; + iv->minor = no_ivshmem; + dev_set_drvdata(&pdev->dev, iv); + + /* create character device */ + devno = MKDEV(MAJOR(dev_ivshmem), iv->minor); + cdev_init(&iv->cdev, &fops); + iv->cdev.owner = THIS_MODULE; + iv->cdev.ops = &fops; + rc = cdev_add(&iv->cdev, devno, 1); + if (rc) { + dev_err(&pdev->dev, "failed to add chrdev! rc=%d\n", rc); + return rc; + } + + dev_info(&pdev->dev, "device node: /dev/%s (minor: %d)\n", iv->name, + iv->minor); + no_ivshmem++; + return 0; +} + +static struct pci_driver ivshmem_pci_driver = { + .name = DRV_NAME, + .probe = ivshmem_probe, + .id_table = ivshmem_pci_tbl, + /* right now, ivshmem does not support hotplug */ +}; + +static int __init ivshmem_dri_init(void) +{ + int rc; + + + printk(KERN_INFO "ivshmem: driver version 0.1\n"); + + /* allocate a maximum of 128 minor numbers */ + rc = alloc_chrdev_region(&dev_ivshmem, 0, 128, DEV_NAME); + if (rc) { + printk(KERN_WARNING "ivshmem: failed to get a major number!\n"); + return rc; + } + + printk(KERN_INFO "ivshmem: major number: %d\n", MAJOR(dev_ivshmem)); + + rc = pci_register_driver(&ivshmem_pci_driver); + if (rc) { + printk(KERN_WARNING "ivshmem: failed to register PCI driver\n"); + return rc; + } + + return 0; +} + +static void __exit ivshmem_dri_exit(void) +{ + return; +} + +module_init(ivshmem_dri_init); +module_exit(ivshmem_dri_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IVSHMEM - InterVM Shared Memory driver"); +MODULE_AUTHOR("Levente Kurusa ");