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 116*0d77ed35SAlex Williamson static int vfio_pci_setup_barmap(struct vfio_pci_device *vdev, int bar) 117*0d77ed35SAlex Williamson { 118*0d77ed35SAlex Williamson struct pci_dev *pdev = vdev->pdev; 119*0d77ed35SAlex Williamson int ret; 120*0d77ed35SAlex Williamson void __iomem *io; 121*0d77ed35SAlex Williamson 122*0d77ed35SAlex Williamson if (vdev->barmap[bar]) 123*0d77ed35SAlex Williamson return 0; 124*0d77ed35SAlex Williamson 125*0d77ed35SAlex Williamson ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); 126*0d77ed35SAlex Williamson if (ret) 127*0d77ed35SAlex Williamson return ret; 128*0d77ed35SAlex Williamson 129*0d77ed35SAlex Williamson io = pci_iomap(pdev, bar, 0); 130*0d77ed35SAlex Williamson if (!io) { 131*0d77ed35SAlex Williamson pci_release_selected_regions(pdev, 1 << bar); 132*0d77ed35SAlex Williamson return -ENOMEM; 133*0d77ed35SAlex Williamson } 134*0d77ed35SAlex Williamson 135*0d77ed35SAlex Williamson vdev->barmap[bar] = io; 136*0d77ed35SAlex Williamson 137*0d77ed35SAlex Williamson return 0; 138*0d77ed35SAlex Williamson } 139*0d77ed35SAlex Williamson 140906ee99dSAlex Williamson ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, 141906ee99dSAlex Williamson size_t count, loff_t *ppos, bool iswrite) 142906ee99dSAlex Williamson { 143906ee99dSAlex Williamson struct pci_dev *pdev = vdev->pdev; 144906ee99dSAlex Williamson loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; 145906ee99dSAlex Williamson int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); 146906ee99dSAlex Williamson size_t x_start = 0, x_end = 0; 147906ee99dSAlex Williamson resource_size_t end; 148906ee99dSAlex Williamson void __iomem *io; 149906ee99dSAlex Williamson ssize_t done; 150906ee99dSAlex Williamson 151a13b6459SAlex Williamson if (pci_resource_start(pdev, bar)) 152906ee99dSAlex Williamson end = pci_resource_len(pdev, bar); 153a13b6459SAlex Williamson else if (bar == PCI_ROM_RESOURCE && 154a13b6459SAlex Williamson pdev->resource[bar].flags & IORESOURCE_ROM_SHADOW) 155a13b6459SAlex Williamson end = 0x20000; 156a13b6459SAlex Williamson else 157a13b6459SAlex Williamson return -EINVAL; 158906ee99dSAlex Williamson 159906ee99dSAlex Williamson if (pos >= end) 160906ee99dSAlex Williamson return -EINVAL; 161906ee99dSAlex Williamson 162906ee99dSAlex Williamson count = min(count, (size_t)(end - pos)); 163906ee99dSAlex Williamson 164906ee99dSAlex Williamson if (bar == PCI_ROM_RESOURCE) { 165906ee99dSAlex Williamson /* 166906ee99dSAlex Williamson * The ROM can fill less space than the BAR, so we start the 167906ee99dSAlex Williamson * excluded range at the end of the actual ROM. This makes 168906ee99dSAlex Williamson * filling large ROM BARs much faster. 169906ee99dSAlex Williamson */ 170906ee99dSAlex Williamson io = pci_map_rom(pdev, &x_start); 171906ee99dSAlex Williamson if (!io) 172906ee99dSAlex Williamson return -ENOMEM; 173906ee99dSAlex Williamson x_end = end; 174*0d77ed35SAlex Williamson } else { 175*0d77ed35SAlex Williamson int ret = vfio_pci_setup_barmap(vdev, bar); 176906ee99dSAlex Williamson if (ret) 177906ee99dSAlex Williamson return ret; 178906ee99dSAlex Williamson 179906ee99dSAlex Williamson io = vdev->barmap[bar]; 180*0d77ed35SAlex Williamson } 181906ee99dSAlex Williamson 182906ee99dSAlex Williamson if (bar == vdev->msix_bar) { 183906ee99dSAlex Williamson x_start = vdev->msix_offset; 184906ee99dSAlex Williamson x_end = vdev->msix_offset + vdev->msix_size; 185906ee99dSAlex Williamson } 186906ee99dSAlex Williamson 187906ee99dSAlex Williamson done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); 188906ee99dSAlex Williamson 189906ee99dSAlex Williamson if (done >= 0) 19089e1f7d4SAlex Williamson *ppos += done; 19189e1f7d4SAlex Williamson 19289e1f7d4SAlex Williamson if (bar == PCI_ROM_RESOURCE) 19389e1f7d4SAlex Williamson pci_unmap_rom(pdev, io); 19489e1f7d4SAlex Williamson 195906ee99dSAlex Williamson return done; 19689e1f7d4SAlex Williamson } 19784237a82SAlex Williamson 19884237a82SAlex Williamson ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf, 19984237a82SAlex Williamson size_t count, loff_t *ppos, bool iswrite) 20084237a82SAlex Williamson { 20184237a82SAlex Williamson int ret; 20284237a82SAlex Williamson loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK; 20384237a82SAlex Williamson void __iomem *iomem = NULL; 20484237a82SAlex Williamson unsigned int rsrc; 20584237a82SAlex Williamson bool is_ioport; 20684237a82SAlex Williamson ssize_t done; 20784237a82SAlex Williamson 20884237a82SAlex Williamson if (!vdev->has_vga) 20984237a82SAlex Williamson return -EINVAL; 21084237a82SAlex Williamson 21145e86971SArnd Bergmann if (pos > 0xbfffful) 21245e86971SArnd Bergmann return -EINVAL; 21345e86971SArnd Bergmann 21445e86971SArnd Bergmann switch ((u32)pos) { 21584237a82SAlex Williamson case 0xa0000 ... 0xbffff: 21684237a82SAlex Williamson count = min(count, (size_t)(0xc0000 - pos)); 21784237a82SAlex Williamson iomem = ioremap_nocache(0xa0000, 0xbffff - 0xa0000 + 1); 21884237a82SAlex Williamson off = pos - 0xa0000; 21984237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_MEM; 22084237a82SAlex Williamson is_ioport = false; 22184237a82SAlex Williamson break; 22284237a82SAlex Williamson case 0x3b0 ... 0x3bb: 22384237a82SAlex Williamson count = min(count, (size_t)(0x3bc - pos)); 22484237a82SAlex Williamson iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1); 22584237a82SAlex Williamson off = pos - 0x3b0; 22684237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 22784237a82SAlex Williamson is_ioport = true; 22884237a82SAlex Williamson break; 22984237a82SAlex Williamson case 0x3c0 ... 0x3df: 23084237a82SAlex Williamson count = min(count, (size_t)(0x3e0 - pos)); 23184237a82SAlex Williamson iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1); 23284237a82SAlex Williamson off = pos - 0x3c0; 23384237a82SAlex Williamson rsrc = VGA_RSRC_LEGACY_IO; 23484237a82SAlex Williamson is_ioport = true; 23584237a82SAlex Williamson break; 23684237a82SAlex Williamson default: 23784237a82SAlex Williamson return -EINVAL; 23884237a82SAlex Williamson } 23984237a82SAlex Williamson 24084237a82SAlex Williamson if (!iomem) 24184237a82SAlex Williamson return -ENOMEM; 24284237a82SAlex Williamson 24384237a82SAlex Williamson ret = vga_get_interruptible(vdev->pdev, rsrc); 24484237a82SAlex Williamson if (ret) { 24584237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 24684237a82SAlex Williamson return ret; 24784237a82SAlex Williamson } 24884237a82SAlex Williamson 24984237a82SAlex Williamson done = do_io_rw(iomem, buf, off, count, 0, 0, iswrite); 25084237a82SAlex Williamson 25184237a82SAlex Williamson vga_put(vdev->pdev, rsrc); 25284237a82SAlex Williamson 25384237a82SAlex Williamson is_ioport ? ioport_unmap(iomem) : iounmap(iomem); 25484237a82SAlex Williamson 25584237a82SAlex Williamson if (done >= 0) 25684237a82SAlex Williamson *ppos += done; 25784237a82SAlex Williamson 25884237a82SAlex Williamson return done; 25984237a82SAlex Williamson } 260