| /* |
| * 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 */ |
| filled = min((size_t)(x_end - pos), count); |
| if (!iswrite) { |
| char val = 0xFF; |
| size_t i; |
| |
| for (i = 0; i < filled; i++) { |
| if (put_user(val, buf + i)) |
| goto out; |
| } |
| } |
| |
| } |
| |
| 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; |
| } |