diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig
index ac3c1dd3edef..fd3716d10ded 100644
--- a/drivers/vfio/pci/Kconfig
+++ b/drivers/vfio/pci/Kconfig
@@ -45,3 +45,10 @@ config VFIO_PCI_NVLINK2
 	depends on VFIO_PCI && PPC_POWERNV
 	help
 	  VFIO PCI support for P9 Witherspoon machine with NVIDIA V100 GPUs
+
+config VFIO_PCI_OCXL
+	depends on VFIO_PCI
+	def_bool y if OCXL_BASE
+	help
+	  VFIO PCI support for devices which handle the Open Coherent
+	  Accelerator Processor Interface.
diff --git a/drivers/vfio/pci/Makefile b/drivers/vfio/pci/Makefile
index f027f8a0e89c..6d55a5fee4b0 100644
--- a/drivers/vfio/pci/Makefile
+++ b/drivers/vfio/pci/Makefile
@@ -3,5 +3,6 @@
 vfio-pci-y := vfio_pci.o vfio_pci_intrs.o vfio_pci_rdwr.o vfio_pci_config.o
 vfio-pci-$(CONFIG_VFIO_PCI_IGD) += vfio_pci_igd.o
 vfio-pci-$(CONFIG_VFIO_PCI_NVLINK2) += vfio_pci_nvlink2.o
+vfio-pci-$(CONFIG_VFIO_PCI_OCXL) += vfio_pci_ocxl.o
 
 obj-$(CONFIG_VFIO_PCI) += vfio-pci.o
diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 703948c9fbe1..4f9741bbe790 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -1128,6 +1128,25 @@ static long vfio_pci_ioctl(void *device_data,
 
 		return vfio_pci_ioeventfd(vdev, ioeventfd.offset,
 					  ioeventfd.data, count, ioeventfd.fd);
+	} else if (cmd == VFIO_DEVICE_OCXL_OP) {
+		struct vfio_device_ocxl_op ocxl_op;
+		int ret = 0;
+
+		minsz = offsetofend(struct vfio_device_ocxl_op, data);
+
+		if (copy_from_user(&ocxl_op, (void __user *)arg, minsz))
+			return -EFAULT;
+
+		if (ocxl_op.argsz < minsz)
+			return -EINVAL;
+
+		ret = vfio_pci_ocxl_ioctl(vdev->pdev, &ocxl_op);
+
+		if (!ret) {
+			if (copy_to_user((void __user *)arg, &ocxl_op, minsz))
+				ret = -EFAULT;
+		}
+		return ret;
 	}
 
 	return -ENOTTY;
diff --git a/drivers/vfio/pci/vfio_pci_ocxl.c b/drivers/vfio/pci/vfio_pci_ocxl.c
new file mode 100644
index 000000000000..cb5cd4fb416d
--- /dev/null
+++ b/drivers/vfio/pci/vfio_pci_ocxl.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright 2019 IBM Corp.
+
+#include <asm/kvm_ppc.h>
+#include <asm/pnv-ocxl.h>
+#include <linux/vfio.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/kvm_host.h>
+
+struct vfio_device_ocxl_link {
+	struct list_head list;
+	int domain;
+	int bus;
+	int slot;
+	void *platform_data;
+};
+static struct list_head links_list = LIST_HEAD_INIT(links_list);
+static DEFINE_MUTEX(links_list_lock);
+
+#define VFIO_DEVICE_OCXL_CONFIG_ADAPTER		1
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_SETUP		1
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_RELEASE	2
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_GET_ACTAG	3
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_GET_PASID	4
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_SET_TL	5
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_ALLOC_IRQ	6
+#define   VFIO_DEVICE_OCXL_CONFIG_ADAPTER_FREE_IRQ	7
+
+#define VFIO_DEVICE_OCXL_CONFIG_SPA		2
+#define   VFIO_DEVICE_OCXL_CONFIG_SPA_SET		1
+#define   VFIO_DEVICE_OCXL_CONFIG_SPA_UPDATE		2
+#define   VFIO_DEVICE_OCXL_CONFIG_SPA_REMOVE		3
+
+#define VFIO_DEVICE_OCXL_GET_FAULT_STATE	3
+#define VFIO_DEVICE_OCXL_HANDLE_FAULT		4
+
+static struct vfio_device_ocxl_link *find_link(struct pci_dev *pdev)
+{
+	struct vfio_device_ocxl_link *link;
+
+	list_for_each_entry(link, &links_list, list) {
+		/* The functions of a device all share the same link */
+		if (link->domain == pci_domain_nr(pdev->bus) &&
+			link->bus == pdev->bus->number &&
+			link->slot == PCI_SLOT(pdev->devfn)) {
+			return link;
+		}
+	}
+
+	/* link doesn't exist yet. Allocate one */
+	link = kzalloc(sizeof(struct vfio_device_ocxl_link), GFP_KERNEL);
+	if (!link)
+		return NULL;
+	link->domain = pci_domain_nr(pdev->bus);
+	link->bus = pdev->bus->number;
+	link->slot = PCI_SLOT(pdev->devfn);
+	list_add(&link->list, &links_list);
+	return link;
+}
+
+static long irq_mapped(struct pci_dev *pdev,
+		       int host_irq, int guest_irq, bool set)
+{
+	struct irq_desc *desc;
+	struct kvm *kvm;
+	int ret = 0, virq;
+
+	virq = irq_create_mapping(NULL, host_irq);
+	if (!virq) {
+		dev_err(&pdev->dev,
+			"irq_create_mapping failed for translation interrupt\n");
+		return -EINVAL;
+	}
+
+	desc = irq_to_desc(virq);
+	if (!desc) {
+		dev_err(&pdev->dev,
+			"irq_to_desc failed (host_irq: %d, virq: %d)\n",
+			host_irq, virq);
+		return -EIO;
+	}
+
+	kvm = vfio_dev_get_kvm(&pdev->dev);
+	if (!kvm)
+		return -ENODEV;
+
+	mutex_lock(&kvm->lock);
+	if (xics_on_xive()) {
+		if (set)
+			ret = kvmppc_xive_set_mapped(kvm, guest_irq, desc);
+		else
+			ret = kvmppc_xive_clr_mapped(kvm, guest_irq, desc);
+	} else {
+		if (set)
+			kvmppc_xics_set_mapped(kvm, guest_irq, host_irq);
+		else
+			kvmppc_xics_clr_mapped(kvm, guest_irq, host_irq);
+	}
+	mutex_unlock(&kvm->lock);
+	kvm_put_kvm(kvm);
+
+	return ret;
+}
+
+static long config_adapter(struct pci_dev *pdev,
+			   struct vfio_device_ocxl_op *ocxl_op,
+			   struct vfio_device_ocxl_link *link)
+{
+	int PE_mask, host_irq, guest_irq, count, tl_dvsec;
+	u16 base, enabled, supported;
+	u64 cmd;
+	int ret = 0;
+
+	cmd = ocxl_op->data[2];
+	switch (cmd) {
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER_SETUP:
+		PE_mask = ocxl_op->data[3];
+		ret = pnv_ocxl_platform_setup(pdev, PE_mask,
+					      &host_irq,
+					      &link->platform_data);
+		if (!ret) {
+			guest_irq = ocxl_op->data[4];
+			ret = irq_mapped(pdev, host_irq, guest_irq, true);
+			if (!ret)
+				ocxl_op->data[0] = host_irq;
+		}
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER_RELEASE:
+		pnv_ocxl_platform_release(link->platform_data);
+
+		host_irq = ocxl_op->data[3];
+		guest_irq = ocxl_op->data[4];
+		if (host_irq && guest_irq)
+			ret = irq_mapped(pdev, host_irq, guest_irq, false);
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER_GET_ACTAG:
+		ret = pnv_ocxl_get_actag(pdev, &base, &enabled,
+					 &supported);
+		if (!ret) {
+			ocxl_op->data[0] = base;
+			ocxl_op->data[1] = enabled;
+			ocxl_op->data[2] = supported;
+		}
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER_GET_PASID:
+		ret = pnv_ocxl_get_pasid_count(pdev, &count);
+		if (!ret)
+			ocxl_op->data[0] = count;
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER_SET_TL:
+		tl_dvsec = ocxl_op->data[3];
+		ret = pnv_ocxl_set_TL(pdev, tl_dvsec);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret)
+		dev_err(&pdev->dev, "Failed to configure the adapter "
+				    "(cmd: %#llx, ret: %d)\n",
+				    cmd, ret);
+
+	return ret;
+}
+
+static long config_spa(struct pci_dev *pdev,
+		       struct vfio_device_ocxl_op *ocxl_op,
+		       struct vfio_device_ocxl_link *link)
+{
+	int lpid, pid, tid, pasid;
+	int pe_handle, ret = 0;
+	u32 pidr, tidr, amr;
+	struct kvm *kvm;
+	u64 cmd;
+
+	cmd = ocxl_op->data[2];
+	switch (cmd) {
+	case VFIO_DEVICE_OCXL_CONFIG_SPA_SET:
+		kvm = vfio_dev_get_kvm(&pdev->dev);
+		if (!kvm)
+			return -ENODEV;
+		lpid = kvm->arch.lpid;
+		kvm_put_kvm(kvm);
+
+		pasid = ocxl_op->data[3];
+		pidr  = ocxl_op->data[4];
+		tidr  = ocxl_op->data[5];
+		amr   = ocxl_op->data[6];
+
+		ret = pnv_ocxl_set_pe(link->platform_data, lpid, pasid,
+				      pidr, tidr, amr, &pe_handle);
+		if (!ret)
+			ocxl_op->data[0] = pe_handle;
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_SPA_UPDATE:
+		pasid = ocxl_op->data[3];
+		tid   = ocxl_op->data[4];
+
+		pnv_ocxl_update_pe(link->platform_data, pasid, tid);
+		break;
+
+	case VFIO_DEVICE_OCXL_CONFIG_SPA_REMOVE:
+		pasid = ocxl_op->data[3];
+
+		ret = pnv_ocxl_remove_pe(link->platform_data, pasid,
+					 &pid, &tid, &pe_handle);
+		if (!ret) {
+			ocxl_op->data[0] = pid;
+			ocxl_op->data[1] = tid;
+			ocxl_op->data[2] = pe_handle;
+		}
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret)
+		dev_err(&pdev->dev, "Failed to configure the SPA "
+				    "(cmd: %#llx, ret: %d)\n",
+				    cmd, ret);
+
+	return ret;
+}
+
+static void get_fault_state(struct vfio_device_ocxl_op *ocxl_op,
+			    struct vfio_device_ocxl_link *link)
+{
+	u64 dsisr, dar, pe_handle;
+	int pid;
+
+	pnv_ocxl_get_fault_state(link->platform_data, &dsisr, &dar,
+				 &pe_handle, &pid);
+
+	ocxl_op->data[0] = dsisr;
+	ocxl_op->data[1] = dar;
+	ocxl_op->data[2] = pe_handle;
+	ocxl_op->data[3] = pid;
+}
+
+static void handle_fault(struct vfio_device_ocxl_op *ocxl_op,
+			 struct vfio_device_ocxl_link *link)
+{
+	u64 tfc;
+
+	tfc = ocxl_op->data[2];
+	pnv_ocxl_handle_fault(link->platform_data, tfc);
+}
+
+long vfio_pci_ocxl_ioctl(struct pci_dev *pdev,
+			 struct vfio_device_ocxl_op *ocxl_op)
+{
+	struct vfio_device_ocxl_link *link;
+	int ret = 0;
+
+	/* The functions of a device all share the same link */
+	mutex_lock(&links_list_lock);
+	link = find_link(pdev);
+
+	switch (ocxl_op->op) {
+	case VFIO_DEVICE_OCXL_CONFIG_ADAPTER:
+		ret = config_adapter(pdev, ocxl_op, link);
+		break;
+	case VFIO_DEVICE_OCXL_CONFIG_SPA:
+		ret = config_spa(pdev, ocxl_op, link);
+		break;
+	case VFIO_DEVICE_OCXL_GET_FAULT_STATE:
+		get_fault_state(ocxl_op, link);
+		break;
+	case VFIO_DEVICE_OCXL_HANDLE_FAULT:
+		handle_fault(ocxl_op, link);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&links_list_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(vfio_pci_ocxl_ioctl);
diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c
index 388597930b64..31d64ecac690 100644
--- a/drivers/vfio/vfio.c
+++ b/drivers/vfio/vfio.c
@@ -18,6 +18,7 @@
 #include <linux/fs.h>
 #include <linux/idr.h>
 #include <linux/iommu.h>
+#include <linux/kvm_host.h>
 #include <linux/list.h>
 #include <linux/miscdevice.h>
 #include <linux/module.h>
@@ -2051,6 +2052,30 @@ void vfio_group_set_kvm(struct vfio_group *group, struct kvm *kvm)
 }
 EXPORT_SYMBOL_GPL(vfio_group_set_kvm);
 
+struct kvm *vfio_dev_get_kvm(struct device *dev)
+{
+	struct iommu_group *iommu_group;
+	struct vfio_group *group;
+	struct kvm *kvm;
+
+	iommu_group = iommu_group_get(dev);
+	if (!iommu_group)
+		return NULL;
+
+	group = vfio_group_get_from_iommu(iommu_group);
+	if (!group) {
+		iommu_group_put(iommu_group);
+		return NULL;
+	}
+
+	kvm_get_kvm(kvm = group->kvm);
+	iommu_group_put(iommu_group);
+	vfio_group_put(group);
+
+	return kvm;
+}
+EXPORT_SYMBOL_GPL(vfio_dev_get_kvm);
+
 static int vfio_register_group_notifier(struct vfio_group *group,
 					unsigned long *events,
 					struct notifier_block *nb)
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index e42a711a2800..22ee8d007353 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -129,6 +129,7 @@ extern int vfio_unregister_notifier(struct device *dev,
 
 struct kvm;
 extern void vfio_group_set_kvm(struct vfio_group *group, struct kvm *kvm);
+extern struct kvm *vfio_dev_get_kvm(struct device *dev);
 
 /*
  * Sub-module helpers
@@ -195,4 +196,16 @@ extern int vfio_virqfd_enable(void *opaque,
 			      void *data, struct virqfd **pvirqfd, int fd);
 extern void vfio_virqfd_disable(struct virqfd **pvirqfd);
 
+/* OpenCAPI */
+#if IS_ENABLED(CONFIG_OCXL_BASE)
+extern long vfio_pci_ocxl_ioctl(struct pci_dev *pdev,
+				struct vfio_device_ocxl_op *ocxl_op);
+#else
+static inline long vfio_pci_ocxl_ioctl(struct pci_dev *pdev,
+				       struct vfio_device_ocxl_op *ocxl_op)
+{
+	return -ENOTTY;
+}
+#endif /* CONFIG_OCXL_BASE */
+
 #endif /* VFIO_H */
diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
index 8f10748dac79..4432593c2e65 100644
--- a/include/uapi/linux/vfio.h
+++ b/include/uapi/linux/vfio.h
@@ -912,6 +912,28 @@ struct vfio_iommu_spapr_tce_remove {
 };
 #define VFIO_IOMMU_SPAPR_TCE_REMOVE	_IO(VFIO_TYPE, VFIO_BASE + 20)
 
+/**
+ * VFIO_DEVICE_OCXL_OP - _IOW(VFIO_TYPE, VFIO_BASE + 22, struct vfio_device_ocxl_op)
+ *
+ * Handles devices, which supports the OpenCAPI interface, using the
+ * ocxl pnv_* interface.
+ */
+struct vfio_device_ocxl_op {
+	__u32 argsz;
+	__u32 flags;
+	__u32 op;
+	__u64 data[9];
+		/* data to be read and written
+		 * data[0] = buid
+		 * data[1] = config_addr
+		 * data[2] = cmd or data[2] = p1, data[3] = p2, ...
+		 * data[3] = p1, params[4] = p2, ...
+		 *
+		 * data[x] = outx ...
+		 */
+};
+#define VFIO_DEVICE_OCXL_OP		_IO(VFIO_TYPE, VFIO_BASE + 22)
+
 /* ***************************************************************** */
 
 #endif /* _UAPIVFIO_H */
