189e1f7d4SAlex Williamson /* 289e1f7d4SAlex Williamson * VFIO PCI I/O Port & MMIO access 389e1f7d4SAlex Williamson * 489e1f7d4SAlex Williamson * Copyright (C) 2012 Red Hat, Inc. All rights reserved. 589e1f7d4SAlex Williamson * Author: Alex Williamson <alex.williamson@redhat.com> 689e1f7d4SAlex Williamson * 789e1f7d4SAlex Williamson * This program is free software; you can redistribute it and/or modify 889e1f7d4SAlex Williamson * it under the terms of the GNU General Public License version 2 as 989e1f7d4SAlex Williamson * published by the Free Software Foundation. 1089e1f7d4SAlex Williamson * 1189e1f7d4SAlex Williamson * Derived from original vfio: 1289e1f7d4SAlex Williamson * Copyright 2010 Cisco Systems, Inc. All rights reserved. 1389e1f7d4SAlex Williamson * Author: Tom Lyon, pugs@cisco.com 1489e1f7d4SAlex Williamson */ 1589e1f7d4SAlex Williamson 1689e1f7d4SAlex Williamson #include <linux/fs.h> 1789e1f7d4SAlex Williamson #include <linux/pci.h> 1889e1f7d4SAlex Williamson #include <linux/uaccess.h> 1989e1f7d4SAlex Williamson #include <linux/io.h> 2089e1f7d4SAlex Williamson 2189e1f7d4SAlex Williamson #include "vfio_pci_private.h" 2289e1f7d4SAlex Williamson 2389e1f7d4SAlex Williamson /* 24*906ee99dSAlex Williamson * Read or write from an __iomem region (MMIO or I/O port) with an excluded 25*906ee99dSAlex Williamson * range which is inaccessible. The excluded range drops writes and fills 26*906ee99dSAlex Williamson * reads with -1. This is intended for handling MSI-X vector tables and 27*906ee99dSAlex Williamson * leftover space for ROM BARs. 2889e1f7d4SAlex Williamson */ 29*906ee99dSAlex Williamson static ssize_t do_io_rw(void __iomem *io, char __user *buf, 30*906ee99dSAlex Williamson loff_t off, size_t count, size_t x_start, 31*906ee99dSAlex Williamson size_t x_end, bool iswrite) 3289e1f7d4SAlex Williamson { 33*906ee99dSAlex Williamson ssize_t done = 0; 3489e1f7d4SAlex Williamson 3589e1f7d4SAlex Williamson while (count) { 3689e1f7d4SAlex Williamson size_t fillable, filled; 3789e1f7d4SAlex Williamson 38*906ee99dSAlex Williamson if (off < x_start) 39*906ee99dSAlex Williamson fillable = min(count, (size_t)(x_start - off)); 40*906ee99dSAlex Williamson else if (off >= x_end) 41*906ee99dSAlex Williamson fillable = count; 4289e1f7d4SAlex Williamson else 4389e1f7d4SAlex Williamson fillable = 0; 4489e1f7d4SAlex Williamson 45*906ee99dSAlex Williamson if (fillable >= 4 && !(off % 4)) { 4689e1f7d4SAlex Williamson __le32 val; 4789e1f7d4SAlex Williamson 4889e1f7d4SAlex Williamson if (iswrite) { 4989e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 4)) 50*906ee99dSAlex Williamson return -EFAULT; 5189e1f7d4SAlex Williamson 52*906ee99dSAlex Williamson iowrite32(le32_to_cpu(val), io + off); 5389e1f7d4SAlex Williamson } else { 54*906ee99dSAlex Williamson val = cpu_to_le32(ioread32(io + off)); 5589e1f7d4SAlex Williamson 5689e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 4)) 57*906ee99dSAlex Williamson return -EFAULT; 5889e1f7d4SAlex Williamson } 5989e1f7d4SAlex Williamson 6089e1f7d4SAlex Williamson filled = 4; 61*906ee99dSAlex Williamson } else if (fillable >= 2 && !(off % 2)) { 6289e1f7d4SAlex Williamson __le16 val; 6389e1f7d4SAlex Williamson 6489e1f7d4SAlex Williamson if (iswrite) { 6589e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 2)) 66*906ee99dSAlex Williamson return -EFAULT; 6789e1f7d4SAlex Williamson 68*906ee99dSAlex Williamson iowrite16(le16_to_cpu(val), io + off); 6989e1f7d4SAlex Williamson } else { 70*906ee99dSAlex Williamson val = cpu_to_le16(ioread16(io + off)); 7189e1f7d4SAlex Williamson 7289e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 2)) 73*906ee99dSAlex Williamson return -EFAULT; 7489e1f7d4SAlex Williamson } 7589e1f7d4SAlex Williamson 7689e1f7d4SAlex Williamson filled = 2; 7789e1f7d4SAlex Williamson } else if (fillable) { 7889e1f7d4SAlex Williamson u8 val; 7989e1f7d4SAlex Williamson 8089e1f7d4SAlex Williamson if (iswrite) { 8189e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 1)) 82*906ee99dSAlex Williamson return -EFAULT; 8389e1f7d4SAlex Williamson 84*906ee99dSAlex Williamson iowrite8(val, io + off); 8589e1f7d4SAlex Williamson } else { 86*906ee99dSAlex Williamson val = ioread8(io + off); 8789e1f7d4SAlex Williamson 8889e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 1)) 89*906ee99dSAlex Williamson return -EFAULT; 9089e1f7d4SAlex Williamson } 9189e1f7d4SAlex Williamson 9289e1f7d4SAlex Williamson filled = 1; 9389e1f7d4SAlex Williamson } else { 94*906ee99dSAlex Williamson /* Fill reads with -1, drop writes */ 95*906ee99dSAlex Williamson filled = min(count, (size_t)(x_end - off)); 9689e1f7d4SAlex Williamson if (!iswrite) { 97*906ee99dSAlex Williamson u8 val = 0xFF; 9889e1f7d4SAlex Williamson size_t i; 9989e1f7d4SAlex Williamson 100*906ee99dSAlex Williamson for (i = 0; i < filled; i++) 101*906ee99dSAlex Williamson if (copy_to_user(buf + i, &val, 1)) 102*906ee99dSAlex Williamson return -EFAULT; 10389e1f7d4SAlex Williamson } 10489e1f7d4SAlex Williamson } 10589e1f7d4SAlex Williamson 10689e1f7d4SAlex Williamson count -= filled; 10789e1f7d4SAlex Williamson done += filled; 108*906ee99dSAlex Williamson off += filled; 10989e1f7d4SAlex Williamson buf += filled; 11089e1f7d4SAlex Williamson } 11189e1f7d4SAlex Williamson 112*906ee99dSAlex Williamson return done; 113*906ee99dSAlex Williamson } 114*906ee99dSAlex Williamson 115*906ee99dSAlex Williamson ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, 116*906ee99dSAlex Williamson size_t count, loff_t *ppos, bool iswrite) 117*906ee99dSAlex Williamson { 118*906ee99dSAlex Williamson struct pci_dev *pdev = vdev->pdev; 119*906ee99dSAlex Williamson loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; 120*906ee99dSAlex Williamson int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); 121*906ee99dSAlex Williamson size_t x_start = 0, x_end = 0; 122*906ee99dSAlex Williamson resource_size_t end; 123*906ee99dSAlex Williamson void __iomem *io; 124*906ee99dSAlex Williamson ssize_t done; 125*906ee99dSAlex Williamson 126*906ee99dSAlex Williamson if (!pci_resource_start(pdev, bar)) 127*906ee99dSAlex Williamson return -EINVAL; 128*906ee99dSAlex Williamson 129*906ee99dSAlex Williamson end = pci_resource_len(pdev, bar); 130*906ee99dSAlex Williamson 131*906ee99dSAlex Williamson if (pos >= end) 132*906ee99dSAlex Williamson return -EINVAL; 133*906ee99dSAlex Williamson 134*906ee99dSAlex Williamson count = min(count, (size_t)(end - pos)); 135*906ee99dSAlex Williamson 136*906ee99dSAlex Williamson if (bar == PCI_ROM_RESOURCE) { 137*906ee99dSAlex Williamson /* 138*906ee99dSAlex Williamson * The ROM can fill less space than the BAR, so we start the 139*906ee99dSAlex Williamson * excluded range at the end of the actual ROM. This makes 140*906ee99dSAlex Williamson * filling large ROM BARs much faster. 141*906ee99dSAlex Williamson */ 142*906ee99dSAlex Williamson io = pci_map_rom(pdev, &x_start); 143*906ee99dSAlex Williamson if (!io) 144*906ee99dSAlex Williamson return -ENOMEM; 145*906ee99dSAlex Williamson x_end = end; 146*906ee99dSAlex Williamson } else if (!vdev->barmap[bar]) { 147*906ee99dSAlex Williamson int ret; 148*906ee99dSAlex Williamson 149*906ee99dSAlex Williamson ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); 150*906ee99dSAlex Williamson if (ret) 151*906ee99dSAlex Williamson return ret; 152*906ee99dSAlex Williamson 153*906ee99dSAlex Williamson io = pci_iomap(pdev, bar, 0); 154*906ee99dSAlex Williamson if (!io) { 155*906ee99dSAlex Williamson pci_release_selected_regions(pdev, 1 << bar); 156*906ee99dSAlex Williamson return -ENOMEM; 157*906ee99dSAlex Williamson } 158*906ee99dSAlex Williamson 159*906ee99dSAlex Williamson vdev->barmap[bar] = io; 160*906ee99dSAlex Williamson } else 161*906ee99dSAlex Williamson io = vdev->barmap[bar]; 162*906ee99dSAlex Williamson 163*906ee99dSAlex Williamson if (bar == vdev->msix_bar) { 164*906ee99dSAlex Williamson x_start = vdev->msix_offset; 165*906ee99dSAlex Williamson x_end = vdev->msix_offset + vdev->msix_size; 166*906ee99dSAlex Williamson } 167*906ee99dSAlex Williamson 168*906ee99dSAlex Williamson done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); 169*906ee99dSAlex Williamson 170*906ee99dSAlex Williamson if (done >= 0) 17189e1f7d4SAlex Williamson *ppos += done; 17289e1f7d4SAlex Williamson 17389e1f7d4SAlex Williamson if (bar == PCI_ROM_RESOURCE) 17489e1f7d4SAlex Williamson pci_unmap_rom(pdev, io); 17589e1f7d4SAlex Williamson 176*906ee99dSAlex Williamson return done; 17789e1f7d4SAlex Williamson } 178