From nobody Fri May 3 13:34:52 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.gnu.org (208.118.235.17 [208.118.235.17]) by mx.zohomail.com with SMTPS id 1517962177974514.472418195087; Tue, 6 Feb 2018 16:09:37 -0800 (PST) Received: from localhost ([::1]:52912 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ejDIm-0008NP-5m for importer@patchew.org; Tue, 06 Feb 2018 19:09:28 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:52470) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ejDHj-0007oc-6Q for qemu-devel@nongnu.org; Tue, 06 Feb 2018 19:08:26 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ejDHf-0000L5-U6 for qemu-devel@nongnu.org; Tue, 06 Feb 2018 19:08:23 -0500 Received: from mx1.redhat.com ([209.132.183.28]:57014) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1ejDHf-0000J7-M1 for qemu-devel@nongnu.org; Tue, 06 Feb 2018 19:08:19 -0500 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id CAE314E4C6; Wed, 7 Feb 2018 00:08:18 +0000 (UTC) Received: from gimli.home (ovpn-117-203.phx2.redhat.com [10.3.117.203]) by smtp.corp.redhat.com (Postfix) with ESMTP id 5D4F95D9CA; Wed, 7 Feb 2018 00:08:15 +0000 (UTC) From: Alex Williamson To: kvm@vger.kernel.org Date: Tue, 06 Feb 2018 17:08:14 -0700 Message-ID: <20180207000731.32764.95992.stgit@gimli.home> User-Agent: StGit/0.18-102-gdf9f MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Wed, 07 Feb 2018 00:08:19 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [RFC PATCH] vfio/pci: Add ioeventfd support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: alex.williamson@redhat.com, linux-kernel@vger.kernel.org, qemu-devel@nongnu.org Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 The ioeventfd here is actually irqfd handling of an ioeventfd such as supported in KVM. A user is able to pre-program a device write to occur when the eventfd triggers. This is yet another instance of eventfd-irqfd triggering between KVM and vfio. The impetus for this is high frequency writes to pages which are virtualized in QEMU. Enabling this near-direct write path for selected registers within the virtualized page can improve performance and reduce overhead. Specifically this is initially targeted at NVIDIA graphics cards where the driver issues a write to an MMIO register within a virtualized region in order to allow the MSI interrupt to re-trigger. Signed-off-by: Alex Williamson --- drivers/vfio/pci/vfio_pci.c | 33 +++++++ drivers/vfio/pci/vfio_pci_private.h | 14 +++ drivers/vfio/pci/vfio_pci_rdwr.c | 165 +++++++++++++++++++++++++++++++= +--- include/uapi/linux/vfio.h | 24 +++++ 4 files changed, 224 insertions(+), 12 deletions(-) diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index f041b1a6cf66..c8e7297a61a3 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -302,6 +302,7 @@ static void vfio_pci_disable(struct vfio_pci_device *vd= ev) { struct pci_dev *pdev =3D vdev->pdev; struct vfio_pci_dummy_resource *dummy_res, *tmp; + struct vfio_pci_ioeventfd *ioeventfd, *ioeventfd_tmp; int i, bar; =20 /* Stop the device from further DMA */ @@ -311,6 +312,14 @@ static void vfio_pci_disable(struct vfio_pci_device *v= dev) VFIO_IRQ_SET_ACTION_TRIGGER, vdev->irq_type, 0, 0, NULL); =20 + /* Device closed, don't need mutex here */ + list_for_each_entry_safe(ioeventfd, ioeventfd_tmp, + &vdev->ioeventfds_list, next) { + vfio_virqfd_disable(&ioeventfd->virqfd); + list_del(&ioeventfd->next); + kfree(ioeventfd); + } + vdev->virq_disabled =3D false; =20 for (i =3D 0; i < vdev->num_regions; i++) @@ -1039,6 +1048,28 @@ static long vfio_pci_ioctl(void *device_data, =20 kfree(groups); return ret; + } else if (cmd =3D=3D VFIO_DEVICE_IOEVENTFD) { + struct vfio_device_ioeventfd ioeventfd; + int count; + + minsz =3D offsetofend(struct vfio_device_ioeventfd, fd); + + if (copy_from_user(&ioeventfd, (void __user*)arg, minsz)) + return -EFAULT; + + if (ioeventfd.argsz < minsz) + return -EINVAL; + + if (ioeventfd.flags & ~VFIO_DEVICE_IOEVENTFD_SIZE_MASK) + return -EINVAL; + + count =3D ioeventfd.flags & VFIO_DEVICE_IOEVENTFD_SIZE_MASK; + + if (hweight8(count) !=3D 1 || ioeventfd.fd < -1) + return -EINVAL; + + return vfio_pci_ioeventfd(vdev, ioeventfd.offset, + ioeventfd.data, count, ioeventfd.fd); } =20 return -ENOTTY; @@ -1217,6 +1248,8 @@ static int vfio_pci_probe(struct pci_dev *pdev, const= struct pci_device_id *id) vdev->irq_type =3D VFIO_PCI_NUM_IRQS; mutex_init(&vdev->igate); spin_lock_init(&vdev->irqlock); + mutex_init(&vdev->ioeventfds_lock); + INIT_LIST_HEAD(&vdev->ioeventfds_list); =20 ret =3D vfio_add_group_dev(&pdev->dev, &vfio_pci_ops, vdev); if (ret) { diff --git a/drivers/vfio/pci/vfio_pci_private.h b/drivers/vfio/pci/vfio_pc= i_private.h index f561ac1c78a0..23797622396e 100644 --- a/drivers/vfio/pci/vfio_pci_private.h +++ b/drivers/vfio/pci/vfio_pci_private.h @@ -29,6 +29,15 @@ #define PCI_CAP_ID_INVALID 0xFF /* default raw access */ #define PCI_CAP_ID_INVALID_VIRT 0xFE /* default virt access */ =20 +struct vfio_pci_ioeventfd { + struct list_head next; + struct virqfd *virqfd; + loff_t pos; + uint64_t data; + int bar; + int count; +}; + struct vfio_pci_irq_ctx { struct eventfd_ctx *trigger; struct virqfd *unmask; @@ -95,6 +104,8 @@ struct vfio_pci_device { struct eventfd_ctx *err_trigger; struct eventfd_ctx *req_trigger; struct list_head dummy_resources_list; + struct mutex ioeventfds_lock; + struct list_head ioeventfds_list; }; =20 #define is_intx(vdev) (vdev->irq_type =3D=3D VFIO_PCI_INTX_IRQ_INDEX) @@ -120,6 +131,9 @@ extern ssize_t vfio_pci_bar_rw(struct vfio_pci_device *= vdev, char __user *buf, extern ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev, char __user *= buf, size_t count, loff_t *ppos, bool iswrite); =20 +extern long vfio_pci_ioeventfd(struct vfio_pci_device *vdev, loff_t offset, + uint64_t data, int count, int fd); + extern int vfio_pci_init_perm_bits(void); extern void vfio_pci_uninit_perm_bits(void); =20 diff --git a/drivers/vfio/pci/vfio_pci_rdwr.c b/drivers/vfio/pci/vfio_pci_r= dwr.c index 357243d76f10..55bb4517d4ba 100644 --- a/drivers/vfio/pci/vfio_pci_rdwr.c +++ b/drivers/vfio/pci/vfio_pci_rdwr.c @@ -17,6 +17,7 @@ #include #include #include +#include #include =20 #include "vfio_pci_private.h" @@ -113,6 +114,30 @@ static ssize_t do_io_rw(void __iomem *io, char __user = *buf, return done; } =20 +static int vfio_pci_setup_barmap(struct vfio_pci_device *vdev, int bar) +{ + struct pci_dev *pdev =3D vdev->pdev; + int ret; + void __iomem *io; + + if (vdev->barmap[bar]) + return 0; + + ret =3D pci_request_selected_regions(pdev, 1 << bar, "vfio"); + if (ret) + return ret; + + io =3D pci_iomap(pdev, bar, 0); + if (!io) { + pci_release_selected_regions(pdev, 1 << bar); + return -ENOMEM; + } + + vdev->barmap[bar] =3D io; + + return 0; +} + ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev, char __user *buf, size_t count, loff_t *ppos, bool iswrite) { @@ -147,22 +172,13 @@ ssize_t vfio_pci_bar_rw(struct vfio_pci_device *vdev,= char __user *buf, if (!io) return -ENOMEM; x_end =3D end; - } else if (!vdev->barmap[bar]) { - int ret; - - ret =3D pci_request_selected_regions(pdev, 1 << bar, "vfio"); + } else { + int ret =3D vfio_pci_setup_barmap(vdev, bar); if (ret) return ret; =20 - io =3D pci_iomap(pdev, bar, 0); - if (!io) { - pci_release_selected_regions(pdev, 1 << bar); - return -ENOMEM; - } - - vdev->barmap[bar] =3D io; - } else io =3D vdev->barmap[bar]; + } =20 if (bar =3D=3D vdev->msix_bar) { x_start =3D vdev->msix_offset; @@ -242,3 +258,128 @@ ssize_t vfio_pci_vga_rw(struct vfio_pci_device *vdev,= char __user *buf, =20 return done; } + +static int vfio_pci_ioeventfd_handler8(void *opaque, void *data) +{ + iowrite8((unsigned long)data, opaque); + return 0; +} + +static int vfio_pci_ioeventfd_handler16(void *opaque, void *data) +{ + iowrite16((unsigned long)data, opaque); + return 0; +} + +static int vfio_pci_ioeventfd_handler32(void *opaque, void *data) +{ + iowrite32((unsigned long)data, opaque); + return 0; +} + +#ifdef iowrite64 +static int vfio_pci_ioeventfd_handler64(void *opaque, void *data) +{ + iowrite64((unsigned long)data, opaque); + return 0; +} +#endif + +long vfio_pci_ioeventfd(struct vfio_pci_device *vdev, loff_t offset, + uint64_t data, int count, int fd) +{ + struct pci_dev *pdev =3D vdev->pdev; + loff_t pos =3D offset & VFIO_PCI_OFFSET_MASK; + int ret, bar =3D VFIO_PCI_OFFSET_TO_INDEX(offset); + struct vfio_pci_ioeventfd *ioeventfd; + int (*handler)(void *, void *); + unsigned long val; + + /* Only support ioeventfds into BARs */ + if (bar > VFIO_PCI_BAR5_REGION_INDEX) + return -EINVAL; + + if (pos + count > pci_resource_len(pdev, bar)) + return -EINVAL; + + /* Disallow ioeventfds working around MSI-X table writes */ + if (bar =3D=3D vdev->msix_bar && + !(pos + count <=3D vdev->msix_offset || + pos >=3D vdev->msix_offset + vdev->msix_size)) + return -EINVAL; + + switch (count) { + case 1: + handler =3D &vfio_pci_ioeventfd_handler8; + val =3D data; + break; + case 2: + handler =3D &vfio_pci_ioeventfd_handler16; + val =3D le16_to_cpu(data); + break; + case 4: + handler =3D &vfio_pci_ioeventfd_handler32; + val =3D le32_to_cpu(data); + break; +#ifdef iowrite64 + case 8: + handler =3D &vfio_pci_ioeventfd_handler64; + val =3D le64_to_cpu(data); + break; +#endif + default: + return -EINVAL; + } + + ret =3D vfio_pci_setup_barmap(vdev, bar); + if (ret) + return ret; + + mutex_lock(&vdev->ioeventfds_lock); + + list_for_each_entry(ioeventfd, &vdev->ioeventfds_list, next) { + if (ioeventfd->pos =3D=3D pos && ioeventfd->bar =3D=3D bar && + ioeventfd->data =3D=3D data && ioeventfd->count =3D=3D count) { + if (fd =3D=3D -1) { + vfio_virqfd_disable(&ioeventfd->virqfd); + list_del(&ioeventfd->next); + kfree(ioeventfd); + ret =3D 0; + } else + ret =3D -EEXIST; + + goto out_unlock; + } + } + + if (fd < 0) { + ret =3D -ENODEV; + goto out_unlock; + } + + ioeventfd =3D kzalloc(sizeof(*ioeventfd), GFP_KERNEL); + if (!ioeventfd) { + ret =3D -ENOMEM; + goto out_unlock; + } + + ioeventfd->pos =3D pos; + ioeventfd->bar =3D bar; + ioeventfd->data =3D data; + ioeventfd->count =3D count; + + ret =3D vfio_virqfd_enable(vdev->barmap[ioeventfd->bar] + ioeventfd->pos, + handler, NULL, (void *)val, + &ioeventfd->virqfd, fd); + if (ret) { + kfree(ioeventfd); + goto out_unlock; + } + + list_add(&ioeventfd->next, &vdev->ioeventfds_list); + +out_unlock: + mutex_unlock(&vdev->ioeventfds_lock); + + return ret; +} diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index e3301dbd27d4..07966a5f0832 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -503,6 +503,30 @@ struct vfio_pci_hot_reset { =20 #define VFIO_DEVICE_PCI_HOT_RESET _IO(VFIO_TYPE, VFIO_BASE + 13) =20 +/** + * VFIO_DEVICE_IOEVENTFD - _IOW(VFIO_TYPE, VFIO_BASE + 14, + * struct vfio_device_ioeventfd) + * + * Perform a write to the device at the specified device fd offset, with + * the specified data and width when the provided eventfd is triggered. + * + * Return: 0 on success, -errno on failure. + */ +struct vfio_device_ioeventfd { + __u32 argsz; + __u32 flags; +#define VFIO_DEVICE_IOEVENTFD_8 (1 << 0) /* 1-byte write */ +#define VFIO_DEVICE_IOEVENTFD_16 (1 << 1) /* 2-byte write */ +#define VFIO_DEVICE_IOEVENTFD_32 (1 << 2) /* 4-byte write */ +#define VFIO_DEVICE_IOEVENTFD_64 (1 << 3) /* 8-byte write */ +#define VFIO_DEVICE_IOEVENTFD_SIZE_MASK (0xf) + __u64 offset; /* device fd offset of write */ + __u64 data; /* data to be written */ + __s32 fd; /* -1 for de-assignment */ +}; + +#define VFIO_DEVICE_IOEVENTFD _IO(VFIO_TYPE, VFIO_BASE + 14) + /* -------- API for Type1 VFIO IOMMU -------- */ =20 /**