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