diff options
| author | Alex Williamson <alex.williamson@redhat.com> | 2016-02-22 18:02:45 -0500 |
|---|---|---|
| committer | Alex Williamson <alex.williamson@redhat.com> | 2016-02-22 18:10:09 -0500 |
| commit | f572a960a15e8bb56599f6d2358a9c18f0808e91 (patch) | |
| tree | 7010cab19cd6604f8d17d3dbb27611635e1870c9 /drivers/vfio | |
| parent | 5846ff54e87d8bab4f1e330af0b5407747a0a57e (diff) | |
vfio/pci: Intel IGD host and LCP bridge config space access
Provide read-only access to PCI config space of the PCI host bridge
and LPC bridge through device specific regions. This may be used to
configure a VM with matching register contents to satisfy driver
requirements. Providing this through the vfio file descriptor removes
an additional userspace requirement for access through pci-sysfs and
removes the CAP_SYS_ADMIN requirement that doesn't appear to apply to
the specific devices we're accessing.
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Diffstat (limited to 'drivers/vfio')
| -rw-r--r-- | drivers/vfio/pci/vfio_pci.c | 15 | ||||
| -rw-r--r-- | drivers/vfio/pci/vfio_pci_igd.c | 171 | ||||
| -rw-r--r-- | drivers/vfio/pci/vfio_pci_private.h | 4 |
3 files changed, 183 insertions, 7 deletions
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index cb2624db37d8..74a375297494 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c | |||
| @@ -111,6 +111,7 @@ static inline bool vfio_pci_is_vga(struct pci_dev *pdev) | |||
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev); | 113 | static void vfio_pci_try_bus_reset(struct vfio_pci_device *vdev); |
| 114 | static void vfio_pci_disable(struct vfio_pci_device *vdev); | ||
| 114 | 115 | ||
| 115 | static int vfio_pci_enable(struct vfio_pci_device *vdev) | 116 | static int vfio_pci_enable(struct vfio_pci_device *vdev) |
| 116 | { | 117 | { |
| @@ -170,10 +171,16 @@ static int vfio_pci_enable(struct vfio_pci_device *vdev) | |||
| 170 | vdev->has_vga = true; | 171 | vdev->has_vga = true; |
| 171 | 172 | ||
| 172 | 173 | ||
| 173 | if (vfio_pci_is_vga(pdev) && pdev->vendor == PCI_VENDOR_ID_INTEL) { | 174 | if (vfio_pci_is_vga(pdev) && |
| 174 | if (vfio_pci_igd_opregion_init(vdev) == 0) | 175 | pdev->vendor == PCI_VENDOR_ID_INTEL && |
| 175 | dev_info(&pdev->dev, | 176 | IS_ENABLED(CONFIG_VFIO_PCI_IGD)) { |
| 176 | "Intel IGD OpRegion support enabled\n"); | 177 | ret = vfio_pci_igd_init(vdev); |
| 178 | if (ret) { | ||
| 179 | dev_warn(&vdev->pdev->dev, | ||
| 180 | "Failed to setup Intel IGD regions\n"); | ||
| 181 | vfio_pci_disable(vdev); | ||
| 182 | return ret; | ||
| 183 | } | ||
| 177 | } | 184 | } |
| 178 | 185 | ||
| 179 | return 0; | 186 | return 0; |
diff --git a/drivers/vfio/pci/vfio_pci_igd.c b/drivers/vfio/pci/vfio_pci_igd.c index 3b6a6f7b367b..6394b168ef29 100644 --- a/drivers/vfio/pci/vfio_pci_igd.c +++ b/drivers/vfio/pci/vfio_pci_igd.c | |||
| @@ -55,7 +55,7 @@ static const struct vfio_pci_regops vfio_pci_igd_regops = { | |||
| 55 | .release = vfio_pci_igd_release, | 55 | .release = vfio_pci_igd_release, |
| 56 | }; | 56 | }; |
| 57 | 57 | ||
| 58 | int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev) | 58 | static int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev) |
| 59 | { | 59 | { |
| 60 | __le32 *dwordp = (__le32 *)(vdev->vconfig + OPREGION_PCI_ADDR); | 60 | __le32 *dwordp = (__le32 *)(vdev->vconfig + OPREGION_PCI_ADDR); |
| 61 | u32 addr, size; | 61 | u32 addr, size; |
| @@ -109,3 +109,172 @@ int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev) | |||
| 109 | 109 | ||
| 110 | return ret; | 110 | return ret; |
| 111 | } | 111 | } |
| 112 | |||
| 113 | static size_t vfio_pci_igd_cfg_rw(struct vfio_pci_device *vdev, | ||
| 114 | char __user *buf, size_t count, loff_t *ppos, | ||
| 115 | bool iswrite) | ||
| 116 | { | ||
| 117 | unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS; | ||
| 118 | struct pci_dev *pdev = vdev->region[i].data; | ||
| 119 | loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; | ||
| 120 | size_t size; | ||
| 121 | int ret; | ||
| 122 | |||
| 123 | if (pos >= vdev->region[i].size || iswrite) | ||
| 124 | return -EINVAL; | ||
| 125 | |||
| 126 | size = count = min(count, (size_t)(vdev->region[i].size - pos)); | ||
| 127 | |||
| 128 | if ((pos & 1) && size) { | ||
| 129 | u8 val; | ||
| 130 | |||
| 131 | ret = pci_user_read_config_byte(pdev, pos, &val); | ||
| 132 | if (ret) | ||
| 133 | return pcibios_err_to_errno(ret); | ||
| 134 | |||
| 135 | if (copy_to_user(buf + count - size, &val, 1)) | ||
| 136 | return -EFAULT; | ||
| 137 | |||
| 138 | pos++; | ||
| 139 | size--; | ||
| 140 | } | ||
| 141 | |||
| 142 | if ((pos & 3) && size > 2) { | ||
| 143 | u16 val; | ||
| 144 | |||
| 145 | ret = pci_user_read_config_word(pdev, pos, &val); | ||
| 146 | if (ret) | ||
| 147 | return pcibios_err_to_errno(ret); | ||
| 148 | |||
| 149 | val = cpu_to_le16(val); | ||
| 150 | if (copy_to_user(buf + count - size, &val, 2)) | ||
| 151 | return -EFAULT; | ||
| 152 | |||
| 153 | pos += 2; | ||
| 154 | size -= 2; | ||
| 155 | } | ||
| 156 | |||
| 157 | while (size > 3) { | ||
| 158 | u32 val; | ||
| 159 | |||
| 160 | ret = pci_user_read_config_dword(pdev, pos, &val); | ||
| 161 | if (ret) | ||
| 162 | return pcibios_err_to_errno(ret); | ||
| 163 | |||
| 164 | val = cpu_to_le32(val); | ||
| 165 | if (copy_to_user(buf + count - size, &val, 4)) | ||
| 166 | return -EFAULT; | ||
| 167 | |||
| 168 | pos += 4; | ||
| 169 | size -= 4; | ||
| 170 | } | ||
| 171 | |||
| 172 | while (size >= 2) { | ||
| 173 | u16 val; | ||
| 174 | |||
| 175 | ret = pci_user_read_config_word(pdev, pos, &val); | ||
| 176 | if (ret) | ||
| 177 | return pcibios_err_to_errno(ret); | ||
| 178 | |||
| 179 | val = cpu_to_le16(val); | ||
| 180 | if (copy_to_user(buf + count - size, &val, 2)) | ||
| 181 | return -EFAULT; | ||
| 182 | |||
| 183 | pos += 2; | ||
| 184 | size -= 2; | ||
| 185 | } | ||
| 186 | |||
| 187 | while (size) { | ||
| 188 | u8 val; | ||
| 189 | |||
| 190 | ret = pci_user_read_config_byte(pdev, pos, &val); | ||
| 191 | if (ret) | ||
| 192 | return pcibios_err_to_errno(ret); | ||
| 193 | |||
| 194 | if (copy_to_user(buf + count - size, &val, 1)) | ||
| 195 | return -EFAULT; | ||
| 196 | |||
| 197 | pos++; | ||
| 198 | size--; | ||
| 199 | } | ||
| 200 | |||
| 201 | *ppos += count; | ||
| 202 | |||
| 203 | return count; | ||
| 204 | } | ||
| 205 | |||
| 206 | static void vfio_pci_igd_cfg_release(struct vfio_pci_device *vdev, | ||
| 207 | struct vfio_pci_region *region) | ||
| 208 | { | ||
| 209 | struct pci_dev *pdev = region->data; | ||
| 210 | |||
| 211 | pci_dev_put(pdev); | ||
| 212 | } | ||
| 213 | |||
| 214 | static const struct vfio_pci_regops vfio_pci_igd_cfg_regops = { | ||
| 215 | .rw = vfio_pci_igd_cfg_rw, | ||
| 216 | .release = vfio_pci_igd_cfg_release, | ||
| 217 | }; | ||
| 218 | |||
| 219 | static int vfio_pci_igd_cfg_init(struct vfio_pci_device *vdev) | ||
| 220 | { | ||
| 221 | struct pci_dev *host_bridge, *lpc_bridge; | ||
| 222 | int ret; | ||
| 223 | |||
| 224 | host_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); | ||
| 225 | if (!host_bridge) | ||
| 226 | return -ENODEV; | ||
| 227 | |||
| 228 | if (host_bridge->vendor != PCI_VENDOR_ID_INTEL || | ||
| 229 | host_bridge->class != (PCI_CLASS_BRIDGE_HOST << 8)) { | ||
| 230 | pci_dev_put(host_bridge); | ||
| 231 | return -EINVAL; | ||
| 232 | } | ||
| 233 | |||
| 234 | ret = vfio_pci_register_dev_region(vdev, | ||
| 235 | PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, | ||
| 236 | VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, | ||
| 237 | &vfio_pci_igd_cfg_regops, host_bridge->cfg_size, | ||
| 238 | VFIO_REGION_INFO_FLAG_READ, host_bridge); | ||
| 239 | if (ret) { | ||
| 240 | pci_dev_put(host_bridge); | ||
| 241 | return ret; | ||
| 242 | } | ||
| 243 | |||
| 244 | lpc_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 0)); | ||
| 245 | if (!lpc_bridge) | ||
| 246 | return -ENODEV; | ||
| 247 | |||
| 248 | if (lpc_bridge->vendor != PCI_VENDOR_ID_INTEL || | ||
| 249 | lpc_bridge->class != (PCI_CLASS_BRIDGE_ISA << 8)) { | ||
| 250 | pci_dev_put(lpc_bridge); | ||
| 251 | return -EINVAL; | ||
| 252 | } | ||
| 253 | |||
| 254 | ret = vfio_pci_register_dev_region(vdev, | ||
| 255 | PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, | ||
| 256 | VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, | ||
| 257 | &vfio_pci_igd_cfg_regops, lpc_bridge->cfg_size, | ||
| 258 | VFIO_REGION_INFO_FLAG_READ, lpc_bridge); | ||
| 259 | if (ret) { | ||
| 260 | pci_dev_put(lpc_bridge); | ||
| 261 | return ret; | ||
| 262 | } | ||
| 263 | |||
| 264 | return 0; | ||
| 265 | } | ||
| 266 | |||
| 267 | int vfio_pci_igd_init(struct vfio_pci_device *vdev) | ||
| 268 | { | ||
| 269 | int ret; | ||
| 270 | |||
| 271 | ret = vfio_pci_igd_opregion_init(vdev); | ||
| 272 | if (ret) | ||
| 273 | return ret; | ||
| 274 | |||
| 275 | ret = vfio_pci_igd_cfg_init(vdev); | ||
| 276 | if (ret) | ||
| 277 | return ret; | ||
| 278 | |||
| 279 | return 0; | ||
| 280 | } | ||
diff --git a/drivers/vfio/pci/vfio_pci_private.h b/drivers/vfio/pci/vfio_pci_private.h index 19f7699ac699..8a7d546d18a0 100644 --- a/drivers/vfio/pci/vfio_pci_private.h +++ b/drivers/vfio/pci/vfio_pci_private.h | |||
| @@ -123,9 +123,9 @@ extern int vfio_pci_register_dev_region(struct vfio_pci_device *vdev, | |||
| 123 | const struct vfio_pci_regops *ops, | 123 | const struct vfio_pci_regops *ops, |
| 124 | size_t size, u32 flags, void *data); | 124 | size_t size, u32 flags, void *data); |
| 125 | #ifdef CONFIG_VFIO_PCI_IGD | 125 | #ifdef CONFIG_VFIO_PCI_IGD |
| 126 | extern int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev); | 126 | extern int vfio_pci_igd_init(struct vfio_pci_device *vdev); |
| 127 | #else | 127 | #else |
| 128 | static inline int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev) | 128 | static inline int vfio_pci_igd_init(struct vfio_pci_device *vdev) |
| 129 | { | 129 | { |
| 130 | return -ENODEV; | 130 | return -ENODEV; |
| 131 | } | 131 | } |
