18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * VFIO PCI Intel Graphics support 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016 Red Hat, Inc. All rights reserved. 68c2ecf20Sopenharmony_ci * Author: Alex Williamson <alex.williamson@redhat.com> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Register a device specific region through which to provide read-only 98c2ecf20Sopenharmony_ci * access to the Intel IGD opregion. The register defining the opregion 108c2ecf20Sopenharmony_ci * address is also virtualized to prevent user modification. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/pci.h> 158c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 168c2ecf20Sopenharmony_ci#include <linux/vfio.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "vfio_pci_private.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define OPREGION_SIGNATURE "IntelGraphicsMem" 218c2ecf20Sopenharmony_ci#define OPREGION_SIZE (8 * 1024) 228c2ecf20Sopenharmony_ci#define OPREGION_PCI_ADDR 0xfc 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistatic size_t vfio_pci_igd_rw(struct vfio_pci_device *vdev, char __user *buf, 258c2ecf20Sopenharmony_ci size_t count, loff_t *ppos, bool iswrite) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS; 288c2ecf20Sopenharmony_ci void *base = vdev->region[i].data; 298c2ecf20Sopenharmony_ci loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci if (pos >= vdev->region[i].size || iswrite) 328c2ecf20Sopenharmony_ci return -EINVAL; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci count = min(count, (size_t)(vdev->region[i].size - pos)); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci if (copy_to_user(buf, base + pos, count)) 378c2ecf20Sopenharmony_ci return -EFAULT; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci *ppos += count; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci return count; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic void vfio_pci_igd_release(struct vfio_pci_device *vdev, 458c2ecf20Sopenharmony_ci struct vfio_pci_region *region) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci memunmap(region->data); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic const struct vfio_pci_regops vfio_pci_igd_regops = { 518c2ecf20Sopenharmony_ci .rw = vfio_pci_igd_rw, 528c2ecf20Sopenharmony_ci .release = vfio_pci_igd_release, 538c2ecf20Sopenharmony_ci}; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic int vfio_pci_igd_opregion_init(struct vfio_pci_device *vdev) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci __le32 *dwordp = (__le32 *)(vdev->vconfig + OPREGION_PCI_ADDR); 588c2ecf20Sopenharmony_ci u32 addr, size; 598c2ecf20Sopenharmony_ci void *base; 608c2ecf20Sopenharmony_ci int ret; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci ret = pci_read_config_dword(vdev->pdev, OPREGION_PCI_ADDR, &addr); 638c2ecf20Sopenharmony_ci if (ret) 648c2ecf20Sopenharmony_ci return ret; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (!addr || !(~addr)) 678c2ecf20Sopenharmony_ci return -ENODEV; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci base = memremap(addr, OPREGION_SIZE, MEMREMAP_WB); 708c2ecf20Sopenharmony_ci if (!base) 718c2ecf20Sopenharmony_ci return -ENOMEM; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (memcmp(base, OPREGION_SIGNATURE, 16)) { 748c2ecf20Sopenharmony_ci memunmap(base); 758c2ecf20Sopenharmony_ci return -EINVAL; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci size = le32_to_cpu(*(__le32 *)(base + 16)); 798c2ecf20Sopenharmony_ci if (!size) { 808c2ecf20Sopenharmony_ci memunmap(base); 818c2ecf20Sopenharmony_ci return -EINVAL; 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci size *= 1024; /* In KB */ 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (size != OPREGION_SIZE) { 878c2ecf20Sopenharmony_ci memunmap(base); 888c2ecf20Sopenharmony_ci base = memremap(addr, size, MEMREMAP_WB); 898c2ecf20Sopenharmony_ci if (!base) 908c2ecf20Sopenharmony_ci return -ENOMEM; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci ret = vfio_pci_register_dev_region(vdev, 948c2ecf20Sopenharmony_ci PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, 958c2ecf20Sopenharmony_ci VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, 968c2ecf20Sopenharmony_ci &vfio_pci_igd_regops, size, VFIO_REGION_INFO_FLAG_READ, base); 978c2ecf20Sopenharmony_ci if (ret) { 988c2ecf20Sopenharmony_ci memunmap(base); 998c2ecf20Sopenharmony_ci return ret; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Fill vconfig with the hw value and virtualize register */ 1038c2ecf20Sopenharmony_ci *dwordp = cpu_to_le32(addr); 1048c2ecf20Sopenharmony_ci memset(vdev->pci_config_map + OPREGION_PCI_ADDR, 1058c2ecf20Sopenharmony_ci PCI_CAP_ID_INVALID_VIRT, 4); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci return ret; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic size_t vfio_pci_igd_cfg_rw(struct vfio_pci_device *vdev, 1118c2ecf20Sopenharmony_ci char __user *buf, size_t count, loff_t *ppos, 1128c2ecf20Sopenharmony_ci bool iswrite) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - VFIO_PCI_NUM_REGIONS; 1158c2ecf20Sopenharmony_ci struct pci_dev *pdev = vdev->region[i].data; 1168c2ecf20Sopenharmony_ci loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; 1178c2ecf20Sopenharmony_ci size_t size; 1188c2ecf20Sopenharmony_ci int ret; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci if (pos >= vdev->region[i].size || iswrite) 1218c2ecf20Sopenharmony_ci return -EINVAL; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci size = count = min(count, (size_t)(vdev->region[i].size - pos)); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if ((pos & 1) && size) { 1268c2ecf20Sopenharmony_ci u8 val; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci ret = pci_user_read_config_byte(pdev, pos, &val); 1298c2ecf20Sopenharmony_ci if (ret) 1308c2ecf20Sopenharmony_ci return pcibios_err_to_errno(ret); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (copy_to_user(buf + count - size, &val, 1)) 1338c2ecf20Sopenharmony_ci return -EFAULT; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci pos++; 1368c2ecf20Sopenharmony_ci size--; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci if ((pos & 3) && size > 2) { 1408c2ecf20Sopenharmony_ci u16 val; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci ret = pci_user_read_config_word(pdev, pos, &val); 1438c2ecf20Sopenharmony_ci if (ret) 1448c2ecf20Sopenharmony_ci return pcibios_err_to_errno(ret); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci val = cpu_to_le16(val); 1478c2ecf20Sopenharmony_ci if (copy_to_user(buf + count - size, &val, 2)) 1488c2ecf20Sopenharmony_ci return -EFAULT; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci pos += 2; 1518c2ecf20Sopenharmony_ci size -= 2; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci while (size > 3) { 1558c2ecf20Sopenharmony_ci u32 val; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci ret = pci_user_read_config_dword(pdev, pos, &val); 1588c2ecf20Sopenharmony_ci if (ret) 1598c2ecf20Sopenharmony_ci return pcibios_err_to_errno(ret); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci val = cpu_to_le32(val); 1628c2ecf20Sopenharmony_ci if (copy_to_user(buf + count - size, &val, 4)) 1638c2ecf20Sopenharmony_ci return -EFAULT; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci pos += 4; 1668c2ecf20Sopenharmony_ci size -= 4; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci while (size >= 2) { 1708c2ecf20Sopenharmony_ci u16 val; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci ret = pci_user_read_config_word(pdev, pos, &val); 1738c2ecf20Sopenharmony_ci if (ret) 1748c2ecf20Sopenharmony_ci return pcibios_err_to_errno(ret); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci val = cpu_to_le16(val); 1778c2ecf20Sopenharmony_ci if (copy_to_user(buf + count - size, &val, 2)) 1788c2ecf20Sopenharmony_ci return -EFAULT; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci pos += 2; 1818c2ecf20Sopenharmony_ci size -= 2; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci while (size) { 1858c2ecf20Sopenharmony_ci u8 val; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci ret = pci_user_read_config_byte(pdev, pos, &val); 1888c2ecf20Sopenharmony_ci if (ret) 1898c2ecf20Sopenharmony_ci return pcibios_err_to_errno(ret); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (copy_to_user(buf + count - size, &val, 1)) 1928c2ecf20Sopenharmony_ci return -EFAULT; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci pos++; 1958c2ecf20Sopenharmony_ci size--; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci *ppos += count; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return count; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic void vfio_pci_igd_cfg_release(struct vfio_pci_device *vdev, 2048c2ecf20Sopenharmony_ci struct vfio_pci_region *region) 2058c2ecf20Sopenharmony_ci{ 2068c2ecf20Sopenharmony_ci struct pci_dev *pdev = region->data; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci pci_dev_put(pdev); 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic const struct vfio_pci_regops vfio_pci_igd_cfg_regops = { 2128c2ecf20Sopenharmony_ci .rw = vfio_pci_igd_cfg_rw, 2138c2ecf20Sopenharmony_ci .release = vfio_pci_igd_cfg_release, 2148c2ecf20Sopenharmony_ci}; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic int vfio_pci_igd_cfg_init(struct vfio_pci_device *vdev) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci struct pci_dev *host_bridge, *lpc_bridge; 2198c2ecf20Sopenharmony_ci int ret; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci host_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); 2228c2ecf20Sopenharmony_ci if (!host_bridge) 2238c2ecf20Sopenharmony_ci return -ENODEV; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci if (host_bridge->vendor != PCI_VENDOR_ID_INTEL || 2268c2ecf20Sopenharmony_ci host_bridge->class != (PCI_CLASS_BRIDGE_HOST << 8)) { 2278c2ecf20Sopenharmony_ci pci_dev_put(host_bridge); 2288c2ecf20Sopenharmony_ci return -EINVAL; 2298c2ecf20Sopenharmony_ci } 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci ret = vfio_pci_register_dev_region(vdev, 2328c2ecf20Sopenharmony_ci PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, 2338c2ecf20Sopenharmony_ci VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, 2348c2ecf20Sopenharmony_ci &vfio_pci_igd_cfg_regops, host_bridge->cfg_size, 2358c2ecf20Sopenharmony_ci VFIO_REGION_INFO_FLAG_READ, host_bridge); 2368c2ecf20Sopenharmony_ci if (ret) { 2378c2ecf20Sopenharmony_ci pci_dev_put(host_bridge); 2388c2ecf20Sopenharmony_ci return ret; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci lpc_bridge = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x1f, 0)); 2428c2ecf20Sopenharmony_ci if (!lpc_bridge) 2438c2ecf20Sopenharmony_ci return -ENODEV; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci if (lpc_bridge->vendor != PCI_VENDOR_ID_INTEL || 2468c2ecf20Sopenharmony_ci lpc_bridge->class != (PCI_CLASS_BRIDGE_ISA << 8)) { 2478c2ecf20Sopenharmony_ci pci_dev_put(lpc_bridge); 2488c2ecf20Sopenharmony_ci return -EINVAL; 2498c2ecf20Sopenharmony_ci } 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci ret = vfio_pci_register_dev_region(vdev, 2528c2ecf20Sopenharmony_ci PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, 2538c2ecf20Sopenharmony_ci VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, 2548c2ecf20Sopenharmony_ci &vfio_pci_igd_cfg_regops, lpc_bridge->cfg_size, 2558c2ecf20Sopenharmony_ci VFIO_REGION_INFO_FLAG_READ, lpc_bridge); 2568c2ecf20Sopenharmony_ci if (ret) { 2578c2ecf20Sopenharmony_ci pci_dev_put(lpc_bridge); 2588c2ecf20Sopenharmony_ci return ret; 2598c2ecf20Sopenharmony_ci } 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return 0; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ciint vfio_pci_igd_init(struct vfio_pci_device *vdev) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci int ret; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci ret = vfio_pci_igd_opregion_init(vdev); 2698c2ecf20Sopenharmony_ci if (ret) 2708c2ecf20Sopenharmony_ci return ret; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci ret = vfio_pci_igd_cfg_init(vdev); 2738c2ecf20Sopenharmony_ci if (ret) 2748c2ecf20Sopenharmony_ci return ret; 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ci return 0; 2778c2ecf20Sopenharmony_ci} 278