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> 2084237a82SAlex 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 127a13b6459SAlex Williamson if (pci_resource_start(pdev, bar)) 128906ee99dSAlex Williamson end = pci_resource_len(pdev, bar); 129a13b6459SAlex Williamson else if (bar == PCI_ROM_RESOURCE && 130a13b6459SAlex Williamson pdev->resource[bar].flags & IORESOURCE_ROM_SHADOW) 131a13b6459SAlex Williamson end = 0x20000; 132a13b6459SAlex Williamson else 133a13b6459SAlex Williamson return -EINVAL; 134906ee99dSAlex Williamson 135906ee99dSAlex Williamson if (pos >= end) 136906ee99dSAlex Williamson return -EINVAL; 137906ee99dSAlex Williamson 138906ee99dSAlex Williamson count = min(count, (size_t)(end - pos)); 139906ee99dSAlex Williamson 140906ee99dSAlex Williamson if (bar == PCI_ROM_RESOURCE) { 141906ee99dSAlex Williamson /* 142906ee99dSAlex Williamson * The ROM can fill less space than the BAR, so we start the 143906ee99dSAlex Williamson * excluded range at the end of the actual ROM. This makes 144906ee99dSAlex Williamson * filling large ROM BARs much faster. 145906ee99dSAlex Williamson */ 146906ee99dSAlex Williamson io = pci_map_rom(pdev, &x_start); 147906ee99dSAlex Williamson if (!io) 148906ee99dSAlex Williamson return -ENOMEM; 149906ee99dSAlex Williamson x_end = end; 150906ee99dSAlex Williamson } else if (!vdev->barmap[bar]) { 151906ee99dSAlex Williamson int ret; 152906ee99dSAlex Williamson 153906ee99dSAlex Williamson ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); 154906ee99dSAlex Williamson if (ret) 155906ee99dSAlex Williamson return ret; 156906ee99dSAlex Williamson 157906ee99dSAlex Williamson io = pci_iomap(pdev, bar, 0); 158906ee99dSAlex Williamson if (!io) { 159906ee99dSAlex Williamson pci_release_selected_regions(pdev, 1 << bar); 160906ee99dSAlex Williamson return -ENOMEM; 161906ee99dSAlex Williamson } 162906ee99dSAlex Williamson 163906ee99dSAlex Williamson vdev->barmap[bar] = io; 164906ee99dSAlex Williamson } else 165906ee99dSAlex Williamson io = vdev->barmap[bar]; 166906ee99dSAlex Williamson 167906ee99dSAlex Williamson if (bar == vdev->msix_bar) { 168906ee99dSAlex Williamson x_start = vdev->msix_offset; 169906ee99dSAlex Williamson x_end = vdev->msix_offset + vdev->msix_size; 170906ee99dSAlex Williamson } 171906ee99dSAlex Williamson 172906ee99dSAlex Williamson done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); 173906ee99dSAlex Williamson 174906ee99dSAlex Williamson if (done >= 0) 17589e1f7d4SAlex Williamson *ppos += done; 17689e1f7d4SAlex Williamson 17789e1f7d4SAlex Williamson if (bar == PCI_ROM_RESOURCE) 17889e1f7d4SAlex Williamson pci_unmap_rom(pdev, io); 17989e1f7d4SAlex Williamson 180906ee99dSAlex Williamson return done; 18189e1f7d4SAlex Williamson } 18284237a82SAlex Williamson 18384237a82SAlex Williamson ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf, 18484237a82SAlex Williamson size_t count, loff_t *ppos, bool iswrite) 18584237a82SAlex Williamson { 18684237a82SAlex Williamson int ret; 18784237a82SAlex Williamson loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK; 18884237a82SAlex Williamson void __iomem *iomem = NULL; 18984237a82SAlex Williamson unsigned int rsrc; 19084237a82SAlex Williamson bool is_ioport; 19184237a82SAlex Williamson ssize_t done; 19284237a82SAlex Williamson 19384237a82SAlex Williamson if (!vdev->has_vga) 19484237a82SAlex Williamson return -EINVAL; 19584237a82SAlex Williamson 196*45e86971SArnd Bergmann if (pos > 0xbfffful) 197*45e86971SArnd Bergmann return -EINVAL; 198*45e86971SArnd Bergmann 199*45e86971SArnd Bergmann switch ((u32)pos) { 20084237a82SAlex Williamson case 0xa0000 ... 0xbffff: 20184237a82SAlex Williamson count = min(count, (size_t)(0xc0000 - pos)); 20284237a82SAlex Williamson iomem = ioremap_nocache(0xa0000, 0xbffff - 0xa0000 + 1); 20384237a82SAlex Williamson off = pos - 0xa0000; 20484237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_MEM; 20584237a82SAlex Williamson is_ioport = false; 20684237a82SAlex Williamson break; 20784237a82SAlex Williamson case 0x3b0 ... 0x3bb: 20884237a82SAlex Williamson count = min(count, (size_t)(0x3bc - pos)); 20984237a82SAlex Williamson iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1); 21084237a82SAlex Williamson off = pos - 0x3b0; 21184237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 21284237a82SAlex Williamson is_ioport = true; 21384237a82SAlex Williamson break; 21484237a82SAlex Williamson case 0x3c0 ... 0x3df: 21584237a82SAlex Williamson count = min(count, (size_t)(0x3e0 - pos)); 21684237a82SAlex Williamson iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1); 21784237a82SAlex Williamson off = pos - 0x3c0; 21884237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 21984237a82SAlex Williamson is_ioport = true; 22084237a82SAlex Williamson break; 22184237a82SAlex Williamson default: 22284237a82SAlex Williamson return -EINVAL; 22384237a82SAlex Williamson } 22484237a82SAlex Williamson 22584237a82SAlex Williamson if (!iomem) 22684237a82SAlex Williamson return -ENOMEM; 22784237a82SAlex Williamson 22884237a82SAlex Williamson ret = vga_get_interruptible(vdev->pdev, rsrc); 22984237a82SAlex Williamson if (ret) { 23084237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 23184237a82SAlex Williamson return ret; 23284237a82SAlex Williamson } 23384237a82SAlex Williamson 23484237a82SAlex Williamson done = do_io_rw(iomem, buf, off, count, 0, 0, iswrite); 23584237a82SAlex Williamson 23684237a82SAlex Williamson vga_put(vdev->pdev, rsrc); 23784237a82SAlex Williamson 23884237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 23984237a82SAlex Williamson 24084237a82SAlex Williamson if (done >= 0) 24184237a82SAlex Williamson *ppos += done; 24284237a82SAlex Williamson 24384237a82SAlex Williamson return done; 24484237a82SAlex Williamson } 245