diff options
Diffstat (limited to 'drivers/vfio/pci/vfio_pci_rdwr.c')
-rw-r--r-- | drivers/vfio/pci/vfio_pci_rdwr.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/drivers/vfio/pci/vfio_pci_rdwr.c b/drivers/vfio/pci/vfio_pci_rdwr.c new file mode 100644 index 000000000000..4362d9e7baa3 --- /dev/null +++ b/drivers/vfio/pci/vfio_pci_rdwr.c @@ -0,0 +1,269 @@ +/* + * VFIO PCI I/O Port & MMIO access + * + * Copyright (C) 2012 Red Hat, Inc. All rights reserved. + * Author: Alex Williamson <alex.williamson@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Derived from original vfio: + * Copyright 2010 Cisco Systems, Inc. All rights reserved. + * Author: Tom Lyon, pugs@cisco.com + */ + +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "vfio_pci_private.h" + +/* I/O Port BAR access */ +ssize_t vfio_pci_io_readwrite(struct vfio_pci_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite) +{ + struct pci_dev *pdev = vdev->pdev; + loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; + int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); + void __iomem *io; + size_t done = 0; + + if (!pci_resource_start(pdev, bar)) + return -EINVAL; + + if (pos + count > pci_resource_len(pdev, bar)) + return -EINVAL; + + if (!vdev->barmap[bar]) { + int ret; + + ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); + if (ret) + return ret; + + vdev->barmap[bar] = pci_iomap(pdev, bar, 0); + + if (!vdev->barmap[bar]) { + pci_release_selected_regions(pdev, 1 << bar); + return -EINVAL; + } + } + + io = vdev->barmap[bar]; + + while (count) { + int filled; + + if (count >= 3 && !(pos % 4)) { + __le32 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 4)) + return -EFAULT; + + iowrite32(le32_to_cpu(val), io + pos); + } else { + val = cpu_to_le32(ioread32(io + pos)); + + if (copy_to_user(buf, &val, 4)) + return -EFAULT; + } + + filled = 4; + + } else if ((pos % 2) == 0 && count >= 2) { + __le16 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 2)) + return -EFAULT; + + iowrite16(le16_to_cpu(val), io + pos); + } else { + val = cpu_to_le16(ioread16(io + pos)); + + if (copy_to_user(buf, &val, 2)) + return -EFAULT; + } + + filled = 2; + } else { + u8 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 1)) + return -EFAULT; + + iowrite8(val, io + pos); + } else { + val = ioread8(io + pos); + + if (copy_to_user(buf, &val, 1)) + return -EFAULT; + } + + filled = 1; + } + + count -= filled; + done += filled; + buf += filled; + pos += filled; + } + + *ppos += done; + + return done; +} + +/* + * MMIO BAR access + * We handle two excluded ranges here as well, if the user tries to read + * the ROM beyond what PCI tells us is available or the MSI-X table region, + * we return 0xFF and writes are dropped. + */ +ssize_t vfio_pci_mem_readwrite(struct vfio_pci_device *vdev, char __user *buf, + size_t count, loff_t *ppos, bool iswrite) +{ + struct pci_dev *pdev = vdev->pdev; + loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; + int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); + void __iomem *io; + resource_size_t end; + size_t done = 0; + size_t x_start = 0, x_end = 0; /* excluded range */ + + if (!pci_resource_start(pdev, bar)) + return -EINVAL; + + end = pci_resource_len(pdev, bar); + + if (pos > end) + return -EINVAL; + + if (pos == end) + return 0; + + if (pos + count > end) + count = end - pos; + + if (bar == PCI_ROM_RESOURCE) { + io = pci_map_rom(pdev, &x_start); + x_end = end; + } else { + if (!vdev->barmap[bar]) { + int ret; + + ret = pci_request_selected_regions(pdev, 1 << bar, + "vfio"); + if (ret) + return ret; + + vdev->barmap[bar] = pci_iomap(pdev, bar, 0); + + if (!vdev->barmap[bar]) { + pci_release_selected_regions(pdev, 1 << bar); + return -EINVAL; + } + } + + io = vdev->barmap[bar]; + + if (bar == vdev->msix_bar) { + x_start = vdev->msix_offset; + x_end = vdev->msix_offset + vdev->msix_size; + } + } + + if (!io) + return -EINVAL; + + while (count) { + size_t fillable, filled; + + if (pos < x_start) + fillable = x_start - pos; + else if (pos >= x_end) + fillable = end - pos; + else + fillable = 0; + + if (fillable >= 4 && !(pos % 4) && (count >= 4)) { + __le32 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 4)) + goto out; + + iowrite32(le32_to_cpu(val), io + pos); + } else { + val = cpu_to_le32(ioread32(io + pos)); + + if (copy_to_user(buf, &val, 4)) + goto out; + } + + filled = 4; + } else if (fillable >= 2 && !(pos % 2) && (count >= 2)) { + __le16 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 2)) + goto out; + + iowrite16(le16_to_cpu(val), io + pos); + } else { + val = cpu_to_le16(ioread16(io + pos)); + + if (copy_to_user(buf, &val, 2)) + goto out; + } + + filled = 2; + } else if (fillable) { + u8 val; + + if (iswrite) { + if (copy_from_user(&val, buf, 1)) + goto out; + + iowrite8(val, io + pos); + } else { + val = ioread8(io + pos); + + if (copy_to_user(buf, &val, 1)) + goto out; + } + + filled = 1; + } else { + /* Drop writes, fill reads with FF */ + if (!iswrite) { + char val = 0xFF; + size_t i; + + for (i = 0; i < x_end - pos; i++) { + if (put_user(val, buf + i)) + goto out; + } + } + + filled = x_end - pos; + } + + count -= filled; + done += filled; + buf += filled; + pos += filled; + } + + *ppos += done; + +out: + if (bar == PCI_ROM_RESOURCE) + pci_unmap_rom(pdev, io); + + return count ? -EFAULT : done; +} |