Commit | Line | Data |
---|---|---|
89e1f7d4 AW |
1 | /* |
2 | * VFIO PCI I/O Port & MMIO access | |
3 | * | |
4 | * Copyright (C) 2012 Red Hat, Inc. All rights reserved. | |
5 | * Author: Alex Williamson <alex.williamson@redhat.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | * | |
11 | * Derived from original vfio: | |
12 | * Copyright 2010 Cisco Systems, Inc. All rights reserved. | |
13 | * Author: Tom Lyon, pugs@cisco.com | |
14 | */ | |
15 | ||
16 | #include <linux/fs.h> | |
17 | #include <linux/pci.h> | |
18 | #include <linux/uaccess.h> | |
19 | #include <linux/io.h> | |
84237a82 | 20 | #include <linux/vgaarb.h> |
89e1f7d4 AW |
21 | |
22 | #include "vfio_pci_private.h" | |
23 | ||
906ee99d AW |
24 | /* |
25 | * Read or write from an __iomem region (MMIO or I/O port) with an excluded | |
26 | * range which is inaccessible. The excluded range drops writes and fills | |
27 | * reads with -1. This is intended for handling MSI-X vector tables and | |
28 | * leftover space for ROM BARs. | |
29 | */ | |
30 | static ssize_t do_io_rw(void __iomem *io, char __user *buf, | |
31 | loff_t off, size_t count, size_t x_start, | |
32 | size_t x_end, bool iswrite) | |
89e1f7d4 | 33 | { |
906ee99d | 34 | ssize_t done = 0; |
89e1f7d4 AW |
35 | |
36 | while (count) { | |
906ee99d AW |
37 | size_t fillable, filled; |
38 | ||
39 | if (off < x_start) | |
40 | fillable = min(count, (size_t)(x_start - off)); | |
41 | else if (off >= x_end) | |
42 | fillable = count; | |
43 | else | |
44 | fillable = 0; | |
89e1f7d4 | 45 | |
906ee99d | 46 | if (fillable >= 4 && !(off % 4)) { |
89e1f7d4 AW |
47 | __le32 val; |
48 | ||
49 | if (iswrite) { | |
50 | if (copy_from_user(&val, buf, 4)) | |
51 | return -EFAULT; | |
52 | ||
906ee99d | 53 | iowrite32(le32_to_cpu(val), io + off); |
89e1f7d4 | 54 | } else { |
906ee99d | 55 | val = cpu_to_le32(ioread32(io + off)); |
89e1f7d4 AW |
56 | |
57 | if (copy_to_user(buf, &val, 4)) | |
58 | return -EFAULT; | |
59 | } | |
60 | ||
61 | filled = 4; | |
906ee99d | 62 | } else if (fillable >= 2 && !(off % 2)) { |
89e1f7d4 AW |
63 | __le16 val; |
64 | ||
65 | if (iswrite) { | |
66 | if (copy_from_user(&val, buf, 2)) | |
67 | return -EFAULT; | |
68 | ||
906ee99d | 69 | iowrite16(le16_to_cpu(val), io + off); |
89e1f7d4 | 70 | } else { |
906ee99d | 71 | val = cpu_to_le16(ioread16(io + off)); |
89e1f7d4 AW |
72 | |
73 | if (copy_to_user(buf, &val, 2)) | |
74 | return -EFAULT; | |
75 | } | |
76 | ||
77 | filled = 2; | |
906ee99d | 78 | } else if (fillable) { |
89e1f7d4 AW |
79 | u8 val; |
80 | ||
81 | if (iswrite) { | |
82 | if (copy_from_user(&val, buf, 1)) | |
83 | return -EFAULT; | |
84 | ||
906ee99d | 85 | iowrite8(val, io + off); |
89e1f7d4 | 86 | } else { |
906ee99d | 87 | val = ioread8(io + off); |
89e1f7d4 AW |
88 | |
89 | if (copy_to_user(buf, &val, 1)) | |
90 | return -EFAULT; | |
91 | } | |
92 | ||
93 | filled = 1; | |
906ee99d AW |
94 | } else { |
95 | /* Fill reads with -1, drop writes */ | |
96 | filled = min(count, (size_t)(x_end - off)); | |
97 | if (!iswrite) { | |
98 | u8 val = 0xFF; | |
99 | size_t i; | |
100 | ||
101 | for (i = 0; i < filled; i++) | |
102 | if (copy_to_user(buf + i, &val, 1)) | |
103 | return -EFAULT; | |
104 | } | |
89e1f7d4 AW |
105 | } |
106 | ||
107 | count -= filled; | |
108 | done += filled; | |
906ee99d | 109 | off += filled; |
89e1f7d4 | 110 | buf += filled; |
89e1f7d4 AW |
111 | } |
112 | ||
89e1f7d4 AW |
113 | return done; |
114 | } | |
115 | ||
906ee99d AW |
116 | ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, |
117 | size_t count, loff_t *ppos, bool iswrite) | |
89e1f7d4 AW |
118 | { |
119 | struct pci_dev *pdev = vdev->pdev; | |
120 | loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; | |
121 | int bar = VFIO_PCI_OFFSET_TO_INDEX(*ppos); | |
906ee99d | 122 | size_t x_start = 0, x_end = 0; |
89e1f7d4 | 123 | resource_size_t end; |
906ee99d AW |
124 | void __iomem *io; |
125 | ssize_t done; | |
89e1f7d4 | 126 | |
a13b6459 AW |
127 | if (pci_resource_start(pdev, bar)) |
128 | end = pci_resource_len(pdev, bar); | |
129 | else if (bar == PCI_ROM_RESOURCE && | |
130 | pdev->resource[bar].flags & IORESOURCE_ROM_SHADOW) | |
131 | end = 0x20000; | |
132 | else | |
89e1f7d4 AW |
133 | return -EINVAL; |
134 | ||
906ee99d | 135 | if (pos >= end) |
89e1f7d4 AW |
136 | return -EINVAL; |
137 | ||
906ee99d | 138 | count = min(count, (size_t)(end - pos)); |
89e1f7d4 AW |
139 | |
140 | if (bar == PCI_ROM_RESOURCE) { | |
906ee99d AW |
141 | /* |
142 | * The ROM can fill less space than the BAR, so we start the | |
143 | * excluded range at the end of the actual ROM. This makes | |
144 | * filling large ROM BARs much faster. | |
145 | */ | |
89e1f7d4 | 146 | io = pci_map_rom(pdev, &x_start); |
906ee99d AW |
147 | if (!io) |
148 | return -ENOMEM; | |
89e1f7d4 | 149 | x_end = end; |
906ee99d AW |
150 | } else if (!vdev->barmap[bar]) { |
151 | int ret; | |
89e1f7d4 | 152 | |
906ee99d AW |
153 | ret = pci_request_selected_regions(pdev, 1 << bar, "vfio"); |
154 | if (ret) | |
155 | return ret; | |
89e1f7d4 | 156 | |
906ee99d AW |
157 | io = pci_iomap(pdev, bar, 0); |
158 | if (!io) { | |
159 | pci_release_selected_regions(pdev, 1 << bar); | |
160 | return -ENOMEM; | |
89e1f7d4 AW |
161 | } |
162 | ||
906ee99d AW |
163 | vdev->barmap[bar] = io; |
164 | } else | |
89e1f7d4 AW |
165 | io = vdev->barmap[bar]; |
166 | ||
906ee99d AW |
167 | if (bar == vdev->msix_bar) { |
168 | x_start = vdev->msix_offset; | |
169 | x_end = vdev->msix_offset + vdev->msix_size; | |
89e1f7d4 AW |
170 | } |
171 | ||
906ee99d | 172 | done = do_io_rw(io, buf, pos, count, x_start, x_end, iswrite); |
89e1f7d4 | 173 | |
906ee99d AW |
174 | if (done >= 0) |
175 | *ppos += done; | |
89e1f7d4 | 176 | |
89e1f7d4 AW |
177 | if (bar == PCI_ROM_RESOURCE) |
178 | pci_unmap_rom(pdev, io); | |
179 | ||
906ee99d | 180 | return done; |
89e1f7d4 | 181 | } |
84237a82 AW |
182 | |
183 | ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *buf, | |
184 | size_t count, loff_t *ppos, bool iswrite) | |
185 | { | |
186 | int ret; | |
187 | loff_t off, pos = *ppos & VFIO_PCI_OFFSET_MASK; | |
188 | void __iomem *iomem = NULL; | |
189 | unsigned int rsrc; | |
190 | bool is_ioport; | |
191 | ssize_t done; | |
192 | ||
193 | if (!vdev->has_vga) | |
194 | return -EINVAL; | |
195 | ||
196 | switch (pos) { | |
197 | case 0xa0000 ... 0xbffff: | |
198 | count = min(count, (size_t)(0xc0000 - pos)); | |
199 | iomem = ioremap_nocache(0xa0000, 0xbffff - 0xa0000 + 1); | |
200 | off = pos - 0xa0000; | |
201 | rsrc = VGA_RSRC_LEGACY_MEM; | |
202 | is_ioport = false; | |
203 | break; | |
204 | case 0x3b0 ... 0x3bb: | |
205 | count = min(count, (size_t)(0x3bc - pos)); | |
206 | iomem = ioport_map(0x3b0, 0x3bb - 0x3b0 + 1); | |
207 | off = pos - 0x3b0; | |
208 | rsrc = VGA_RSRC_LEGACY_IO; | |
209 | is_ioport = true; | |
210 | break; | |
211 | case 0x3c0 ... 0x3df: | |
212 | count = min(count, (size_t)(0x3e0 - pos)); | |
213 | iomem = ioport_map(0x3c0, 0x3df - 0x3c0 + 1); | |
214 | off = pos - 0x3c0; | |
215 | rsrc = VGA_RSRC_LEGACY_IO; | |
216 | is_ioport = true; | |
217 | break; | |
218 | default: | |
219 | return -EINVAL; | |
220 | } | |
221 | ||
222 | if (!iomem) | |
223 | return -ENOMEM; | |
224 | ||
225 | ret = vga_get_interruptible(vdev->pdev, rsrc); | |
226 | if (ret) { | |
227 | is_ioport ? ioport_unmap(iomem) : iounmap(iomem); | |
228 | return ret; | |
229 | } | |
230 | ||
231 | done = do_io_rw(iomem, buf, off, count, 0, 0, iswrite); | |
232 | ||
233 | vga_put(vdev->pdev, rsrc); | |
234 | ||
235 | is_ioport ? ioport_unmap(iomem) : iounmap(iomem); | |
236 | ||
237 | if (done >= 0) | |
238 | *ppos += done; | |
239 | ||
240 | return done; | |
241 | } |