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> 20*84237a82SAlex Williamson #include <linux/vgaarb.h> 2189e1f7d4SAlex Williamson 2289e1f7d4SAlex Williamson #include "vfio_pci_private.h" 2389e1f7d4SAlex Williamson 2489e1f7d4SAlex Williamson /* 25906ee99dSAlex Williamson * Read or write from an __iomem region (MMIO or I/O port) with an excluded 26906ee99dSAlex Williamson * range which is inaccessible. The excluded range drops writes and fills 27906ee99dSAlex Williamson * reads with -1. This is intended for handling MSI-X vector tables and 28906ee99dSAlex Williamson * leftover space for ROM BARs. 2989e1f7d4SAlex Williamson */ 30906ee99dSAlex Williamson static ssize_t do_io_rw(void __iomem *io, char __user *buf, 31906ee99dSAlex Williamson loff_t off, size_t count, size_t x_start, 32906ee99dSAlex Williamson size_t x_end, bool iswrite) 3389e1f7d4SAlex Williamson { 34906ee99dSAlex Williamson ssize_t done = 0; 3589e1f7d4SAlex Williamson 3689e1f7d4SAlex Williamson while (count) { 3789e1f7d4SAlex Williamson size_t fillable, filled; 3889e1f7d4SAlex Williamson 39906ee99dSAlex Williamson if (off < x_start) 40906ee99dSAlex Williamson fillable = min(count, (size_t)(x_start - off)); 41906ee99dSAlex Williamson else if (off >= x_end) 42906ee99dSAlex Williamson fillable = count; 4389e1f7d4SAlex Williamson else 4489e1f7d4SAlex Williamson fillable = 0; 4589e1f7d4SAlex Williamson 46906ee99dSAlex Williamson if (fillable >= 4 && !(off % 4)) { 4789e1f7d4SAlex Williamson __le32 val; 4889e1f7d4SAlex Williamson 4989e1f7d4SAlex Williamson if (iswrite) { 5089e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 4)) 51906ee99dSAlex Williamson return -EFAULT; 5289e1f7d4SAlex Williamson 53906ee99dSAlex Williamson iowrite32(le32_to_cpu(val), io + off); 5489e1f7d4SAlex Williamson } else { 55906ee99dSAlex Williamson val = cpu_to_le32(ioread32(io + off)); 5689e1f7d4SAlex Williamson 5789e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 4)) 58906ee99dSAlex Williamson return -EFAULT; 5989e1f7d4SAlex Williamson } 6089e1f7d4SAlex Williamson 6189e1f7d4SAlex Williamson filled = 4; 62906ee99dSAlex Williamson } else if (fillable >= 2 && !(off % 2)) { 6389e1f7d4SAlex Williamson __le16 val; 6489e1f7d4SAlex Williamson 6589e1f7d4SAlex Williamson if (iswrite) { 6689e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 2)) 67906ee99dSAlex Williamson return -EFAULT; 6889e1f7d4SAlex Williamson 69906ee99dSAlex Williamson iowrite16(le16_to_cpu(val), io + off); 7089e1f7d4SAlex Williamson } else { 71906ee99dSAlex Williamson val = cpu_to_le16(ioread16(io + off)); 7289e1f7d4SAlex Williamson 7389e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 2)) 74906ee99dSAlex Williamson return -EFAULT; 7589e1f7d4SAlex Williamson } 7689e1f7d4SAlex Williamson 7789e1f7d4SAlex Williamson filled = 2; 7889e1f7d4SAlex Williamson } else if (fillable) { 7989e1f7d4SAlex Williamson u8 val; 8089e1f7d4SAlex Williamson 8189e1f7d4SAlex Williamson if (iswrite) { 8289e1f7d4SAlex Williamson if (copy_from_user(&val, buf, 1)) 83906ee99dSAlex Williamson return -EFAULT; 8489e1f7d4SAlex Williamson 85906ee99dSAlex Williamson iowrite8(val, io + off); 8689e1f7d4SAlex Williamson } else { 87906ee99dSAlex Williamson val = ioread8(io + off); 8889e1f7d4SAlex Williamson 8989e1f7d4SAlex Williamson if (copy_to_user(buf, &val, 1)) 90906ee99dSAlex Williamson return -EFAULT; 9189e1f7d4SAlex Williamson } 9289e1f7d4SAlex Williamson 9389e1f7d4SAlex Williamson filled = 1; 9489e1f7d4SAlex Williamson } else { 95906ee99dSAlex Williamson /* Fill reads with -1, drop writes */ 96906ee99dSAlex Williamson filled = min(count, (size_t)(x_end - off)); 9789e1f7d4SAlex Williamson if (!iswrite) { 98906ee99dSAlex Williamson u8 val = 0xFF; 9989e1f7d4SAlex Williamson size_t i; 10089e1f7d4SAlex Williamson 101906ee99dSAlex Williamson for (i = 0; i < filled; i++) 102906ee99dSAlex Williamson if (copy_to_user(buf + i, &val, 1)) 103906ee99dSAlex Williamson return -EFAULT; 10489e1f7d4SAlex Williamson } 10589e1f7d4SAlex Williamson } 10689e1f7d4SAlex Williamson 10789e1f7d4SAlex Williamson count -= filled; 10889e1f7d4SAlex Williamson done += filled; 109906ee99dSAlex Williamson off += filled; 11089e1f7d4SAlex Williamson buf += filled; 11189e1f7d4SAlex Williamson } 11289e1f7d4SAlex Williamson 113906ee99dSAlex Williamson return done; 114906ee99dSAlex Williamson } 115906ee99dSAlex Williamson 116906ee99dSAlex Williamson ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, 117906ee99dSAlex Williamson size_t count, loff_t *ppos, bool iswrite) 118906ee99dSAlex Williamson { 119906ee99dSAlex Williamson struct pci_dev *pdev = vdev->pdev; 120906ee99dSAlex Williamson loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; 121906ee99dSAlex Williamson int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); 122906ee99dSAlex Williamson size_t x_start = 0, x_end = 0; 123906ee99dSAlex Williamson resource_size_t end; 124906ee99dSAlex Williamson void __iomem *io; 125906ee99dSAlex Williamson ssize_t done; 126906ee99dSAlex Williamson 127906ee99dSAlex Williamson if (!pci_resource_start(pdev, bar)) 128906ee99dSAlex Williamson return -EINVAL; 129906ee99dSAlex Williamson 130906ee99dSAlex Williamson end = pci_resource_len(pdev, bar); 131906ee99dSAlex Williamson 132906ee99dSAlex Williamson if (pos >= end) 133906ee99dSAlex Williamson return -EINVAL; 134906ee99dSAlex Williamson 135906ee99dSAlex Williamson count = min(count, (size_t)(end - pos)); 136906ee99dSAlex Williamson 137906ee99dSAlex Williamson if (bar == PCI_ROM_RESOURCE) { 138906ee99dSAlex Williamson /* 139906ee99dSAlex Williamson * The ROM can fill less space than the BAR, so we start the 140906ee99dSAlex Williamson * excluded range at the end of the actual ROM. This makes 141906ee99dSAlex Williamson * filling large ROM BARs much faster. 142906ee99dSAlex Williamson */ 143906ee99dSAlex Williamson io = pci_map_rom(pdev, &x_start); 144906ee99dSAlex Williamson if (!io) 145906ee99dSAlex Williamson return -ENOMEM; 146906ee99dSAlex Williamson x_end = end; 147906ee99dSAlex Williamson } else if (!vdev->barmap[bar]) { 148906ee99dSAlex Williamson int ret; 149906ee99dSAlex Williamson 150906ee99dSAlex Williamson ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); 151906ee99dSAlex Williamson if (ret) 152906ee99dSAlex Williamson return ret; 153906ee99dSAlex Williamson 154906ee99dSAlex Williamson io = pci_iomap(pdev, bar, 0); 155906ee99dSAlex Williamson if (!io) { 156906ee99dSAlex Williamson pci_release_selected_regions(pdev, 1 << bar); 157906ee99dSAlex Williamson return -ENOMEM; 158906ee99dSAlex Williamson } 159906ee99dSAlex Williamson 160906ee99dSAlex Williamson vdev->barmap[bar] = io; 161906ee99dSAlex Williamson } else 162906ee99dSAlex Williamson io = vdev->barmap[bar]; 163906ee99dSAlex Williamson 164906ee99dSAlex Williamson if (bar == vdev->msix_bar) { 165906ee99dSAlex Williamson x_start = vdev->msix_offset; 166906ee99dSAlex Williamson x_end = vdev->msix_offset + vdev->msix_size; 167906ee99dSAlex Williamson } 168906ee99dSAlex Williamson 169906ee99dSAlex Williamson done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); 170906ee99dSAlex Williamson 171906ee99dSAlex Williamson if (done >= 0) 17289e1f7d4SAlex Williamson *ppos += done; 17389e1f7d4SAlex Williamson 17489e1f7d4SAlex Williamson if (bar == PCI_ROM_RESOURCE) 17589e1f7d4SAlex Williamson pci_unmap_rom(pdev, io); 17689e1f7d4SAlex Williamson 177906ee99dSAlex Williamson return done; 17889e1f7d4SAlex Williamson } 179*84237a82SAlex Williamson 180*84237a82SAlex Williamson ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf, 181*84237a82SAlex Williamson size_t count, loff_t *ppos, bool iswrite) 182*84237a82SAlex Williamson { 183*84237a82SAlex Williamson int ret; 184*84237a82SAlex Williamson loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK; 185*84237a82SAlex Williamson void __iomem *iomem = NULL; 186*84237a82SAlex Williamson unsigned int rsrc; 187*84237a82SAlex Williamson bool is_ioport; 188*84237a82SAlex Williamson ssize_t done; 189*84237a82SAlex Williamson 190*84237a82SAlex Williamson if (!vdev->has_vga) 191*84237a82SAlex Williamson return -EINVAL; 192*84237a82SAlex Williamson 193*84237a82SAlex Williamson switch (pos) { 194*84237a82SAlex Williamson case 0xa0000 ... 0xbffff: 195*84237a82SAlex Williamson count = min(count, (size_t)(0xc0000 - pos)); 196*84237a82SAlex Williamson iomem = ioremap_nocache(0xa0000, 0xbffff - 0xa0000 + 1); 197*84237a82SAlex Williamson off = pos - 0xa0000; 198*84237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_MEM; 199*84237a82SAlex Williamson is_ioport = false; 200*84237a82SAlex Williamson break; 201*84237a82SAlex Williamson case 0x3b0 ... 0x3bb: 202*84237a82SAlex Williamson count = min(count, (size_t)(0x3bc - pos)); 203*84237a82SAlex Williamson iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1); 204*84237a82SAlex Williamson off = pos - 0x3b0; 205*84237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 206*84237a82SAlex Williamson is_ioport = true; 207*84237a82SAlex Williamson break; 208*84237a82SAlex Williamson case 0x3c0 ... 0x3df: 209*84237a82SAlex Williamson count = min(count, (size_t)(0x3e0 - pos)); 210*84237a82SAlex Williamson iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1); 211*84237a82SAlex Williamson off = pos - 0x3c0; 212*84237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 213*84237a82SAlex Williamson is_ioport = true; 214*84237a82SAlex Williamson break; 215*84237a82SAlex Williamson default: 216*84237a82SAlex Williamson return -EINVAL; 217*84237a82SAlex Williamson } 218*84237a82SAlex Williamson 219*84237a82SAlex Williamson if (!iomem) 220*84237a82SAlex Williamson return -ENOMEM; 221*84237a82SAlex Williamson 222*84237a82SAlex Williamson ret = vga_get_interruptible(vdev->pdev, rsrc); 223*84237a82SAlex Williamson if (ret) { 224*84237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 225*84237a82SAlex Williamson return ret; 226*84237a82SAlex Williamson } 227*84237a82SAlex Williamson 228*84237a82SAlex Williamson done = do_io_rw(iomem, buf, off, count, 0, 0, iswrite); 229*84237a82SAlex Williamson 230*84237a82SAlex Williamson vga_put(vdev->pdev, rsrc); 231*84237a82SAlex Williamson 232*84237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 233*84237a82SAlex Williamson 234*84237a82SAlex Williamson if (done >= 0) 235*84237a82SAlex Williamson *ppos += done; 236*84237a82SAlex Williamson 237*84237a82SAlex Williamson return done; 238*84237a82SAlex Williamson } 239