From patchwork Fri Apr 14 12:39:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shunsuke Mie X-Patchwork-Id: 13211423 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 23704C77B6E for ; Fri, 14 Apr 2023 12:39:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229794AbjDNMjV (ORCPT ); Fri, 14 Apr 2023 08:39:21 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57162 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229673AbjDNMjU (ORCPT ); Fri, 14 Apr 2023 08:39:20 -0400 Received: from mail-pl1-x62a.google.com (mail-pl1-x62a.google.com [IPv6:2607:f8b0:4864:20::62a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F1BD5AF29 for ; Fri, 14 Apr 2023 05:39:17 -0700 (PDT) Received: by mail-pl1-x62a.google.com with SMTP id n17so2195381pln.8 for ; Fri, 14 Apr 2023 05:39:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=igel-co-jp.20221208.gappssmtp.com; s=20221208; t=1681475957; x=1684067957; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=r3NQNzImpl7v9ZsQ1lWCxb6S+hYC5oLdUBv6JvMjGuE=; b=EPdWogGf/Az3KsP0yPBGLfyncklpreuAVN4wgKKeyX9jVIyz49Saw3GnDSFwulOlYF pDwEqN0crmcrN2w17a66pt0HZ9SxY7XGBb5nG9hpCr03S+KfMMhUiRVCAEluya6YmUui E/irnFcAfpnutuJSbx1WuQJpcXvOmEBMCtQv38Yfq7YDyI9DU/VQhLJD3vCrIe/optbN k6K34FNgbqJTAHwggn15OLh+2nWXythhirkaq7DR0QvLbJHt+V4SdPPmzJHJ7SkSDJN8 VnU8+NCvdbq0lePOkIxkH6LFI+QC3OyPYZe4X3TqWEVdZhttBSZdd157RddQpGk7U/0q 0ANA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681475957; x=1684067957; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=r3NQNzImpl7v9ZsQ1lWCxb6S+hYC5oLdUBv6JvMjGuE=; b=HHmnqgAsHqsRZ+ms3z44yem2ITBAmC0J+S/c4t/ISJeqxwRd8pWPMZuwbC37pMGbPZ mk4n2tyswQ6NQPU8r5MxDXyZPNq0iCsWmb3V3mWjW5HBqG4LGCxwHVaKUOcqZd0VWVQZ qYrPazbPFwUGyZBiRyTYPv3y81sabuIA5R8jK9w7an3niwqpJlNf3+te5KjRYNCDaxYH 3MV72CO2OGiOPTHcf6wY/ZoKK3OVm/guwsQSWv1o82m0n5AYcg/8gfE3KIc7SZkwX5hx OQdbnSR+ZQyD4/IlJbAYoFr1xJfLLXRw+Y1UOjP3QZpKpQjdPo0AQ4kxYDSxax/dy1+A AEZQ== X-Gm-Message-State: AAQBX9d6Fi/eFlH/jK0ggqH4mkcduzcSDXtleBE3gndIklVK//LeGH7R 1MjNdknbWAdBS5R6mAudCc6HOQ== X-Google-Smtp-Source: AKy350ZowWWUvq7RUMpkAPX+2sPQlC9MpUlP6TgrvPjutz5vDLhMZoKq8l+apqjdfQkRnQe5cfXSxA== X-Received: by 2002:a17:902:db10:b0:1a6:494c:f723 with SMTP id m16-20020a170902db1000b001a6494cf723mr2905237plx.54.1681475957271; Fri, 14 Apr 2023 05:39:17 -0700 (PDT) Received: from tyrell.hq.igel.co.jp (napt.igel.co.jp. [219.106.231.132]) by smtp.gmail.com with ESMTPSA id v21-20020a1709028d9500b001a527761c31sm3015366plo.79.2023.04.14.05.39.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Apr 2023 05:39:16 -0700 (PDT) From: Shunsuke Mie To: Lorenzo Pieralisi Cc: =?utf-8?q?Krzysztof_Wilczy=C5=84ski?= , Manivannan Sadhasivam , Kishon Vijay Abraham I , Bjorn Helgaas , "Michael S. Tsirkin" , Jason Wang , Shunsuke Mie , Frank Li , Jon Mason , Randy Dunlap , Ren Zhijie , linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, virtualization@lists.linux-foundation.org Subject: [RFC PATCH 1/3] PCI: endpoint: introduce a helper to implement pci ep virtio function Date: Fri, 14 Apr 2023 21:39:01 +0900 Message-Id: <20230414123903.896914-2-mie@igel.co.jp> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230414123903.896914-1-mie@igel.co.jp> References: <20230414123903.896914-1-mie@igel.co.jp> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org The Linux PCIe Endpoint framework supports to implement PCIe endpoint functions using a PCIe controller operating in endpoint mode. It is possble to realize the behavior of PCIe device, such as virtio PCI device. This patch introduces a setof helper functions and data structures to implement a PCIe endpoint function that behaves as a virtio device. Those functions enable the implementation PCIe endpoint function that comply with the virtio lecacy specification. Because modern virtio specifications require devices to implement custom PCIe capabilities, which are not currently supported by either PCIe controllers/drivers or the PCIe endpoint framework. The helper functions work with the new struct epf_virtio, which is initialized and finalized using the following functions: - int epf_virtio_init(); - void epf_virtio_final() Once initialized, the PCIe configuration space can be read and written using the following functions: - epf_virtio_cfg_{read,write}#size() - epf_virtio_cfg_{set,clear}#size() - epf_virtio_cfg_memcpy_toio() The size is supported 8, 16 and 32bits. The content of configuration space varies depending on the type of virtio device. After the setup, launch the kernel thread for negotiation with host virtio drivers and detection virtqueue notifications with the function: - epf_virtio_launch_bgtask() Also there are functions to shutdown and reset the kthread. - epf_virtio_terminate_bgtask() - epf_virtio_terminate_reset() Data transfer function is also provide. - epf_virtio_vrh_memcpy() This function copy data indicated descriptor of PCIe host and endpoint virtqueue to each other. The virtqueue manage as vringh and copy direction is specified by enum epf_virtio_copy_dir, which is defined as follows: This function copies data between the PCIe host and endpoint virtqueues. The each virtqueues are managed using vringh, and the copy direction is specified by an enum called epf_virtio_copy_dir, which is defined as follows: enum epf_virtio_copy_dir { EPF_VIRTIO_COPY_DIR_FROM_DEV, EPF_VIRTIO_COPY_DIR_TO_DEV, }; While this patch provides functions for negotiating with host drivers and copying data, each PCIe function driver must impl ement operations that depend on each specific device, such as network, block, etc. Signed-off-by: Shunsuke Mie --- drivers/pci/endpoint/functions/Kconfig | 7 + drivers/pci/endpoint/functions/Makefile | 1 + .../pci/endpoint/functions/pci-epf-virtio.c | 469 ++++++++++++++++++ .../pci/endpoint/functions/pci-epf-virtio.h | 123 +++++ 4 files changed, 600 insertions(+) create mode 100644 drivers/pci/endpoint/functions/pci-epf-virtio.c create mode 100644 drivers/pci/endpoint/functions/pci-epf-virtio.h diff --git a/drivers/pci/endpoint/functions/Kconfig b/drivers/pci/endpoint/functions/Kconfig index 9fd560886871..cf16d33c9585 100644 --- a/drivers/pci/endpoint/functions/Kconfig +++ b/drivers/pci/endpoint/functions/Kconfig @@ -37,3 +37,10 @@ config PCI_EPF_VNTB between PCI Root Port and PCIe Endpoint. If in doubt, say "N" to disable Endpoint NTB driver. + +config PCI_EPF_VIRTIO + tristate + depends on PCI_ENDPOINT + select VHOST_IOMEM + help + Helpers to implement PCI virtio Endpoint function diff --git a/drivers/pci/endpoint/functions/Makefile b/drivers/pci/endpoint/functions/Makefile index 5c13001deaba..a96f127ce900 100644 --- a/drivers/pci/endpoint/functions/Makefile +++ b/drivers/pci/endpoint/functions/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o obj-$(CONFIG_PCI_EPF_NTB) += pci-epf-ntb.o obj-$(CONFIG_PCI_EPF_VNTB) += pci-epf-vntb.o +obj-$(CONFIG_PCI_EPF_VIRTIO) += pci-epf-virtio.o diff --git a/drivers/pci/endpoint/functions/pci-epf-virtio.c b/drivers/pci/endpoint/functions/pci-epf-virtio.c new file mode 100644 index 000000000000..bb3de01563de --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-virtio.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Helpers to implement PCIe virtio EP function. + */ +#include +#include +#include + +#include "pci-epf-virtio.h" + +static void epf_virtio_unmap_vq(struct pci_epf *epf, void __iomem *vq_virt, + phys_addr_t vq_phys, unsigned int num) +{ + size_t vq_size = vring_size(num, VIRTIO_PCI_VRING_ALIGN); + + pci_epc_unmap_addr(epf->epc, epf->func_no, epf->vfunc_no, vq_phys, + vq_virt, vq_size); + pci_epc_mem_free_addr(epf->epc, vq_phys, vq_virt, vq_size); +} + +static void __iomem *epf_virtio_map_vq(struct pci_epf *epf, + phys_addr_t vq_pci_addr, + unsigned int num, phys_addr_t *vq_phys) +{ + int err; + size_t vq_size; + void __iomem *vq_virt; + + vq_size = vring_size(num, VIRTIO_PCI_VRING_ALIGN); + + vq_virt = pci_epc_map_addr(epf->epc, epf->func_no, epf->vfunc_no, + vq_pci_addr, vq_phys, vq_size); + if (IS_ERR(vq_virt)) { + pr_err("Failed to map virtuqueue to local"); + goto err_free; + } + + return vq_virt; + +err_free: + pci_epc_mem_free_addr(epf->epc, *vq_phys, vq_virt, vq_size); + + return ERR_PTR(err); +} + +static void epf_virtio_free_vringh(struct pci_epf *epf, struct epf_vringh *evrh) +{ + epf_virtio_unmap_vq(epf, evrh->virt, evrh->phys, evrh->num); + kfree(evrh); +} + +static struct epf_vringh *epf_virtio_alloc_vringh(struct pci_epf *epf, + u64 features, + phys_addr_t pci_addr, + unsigned int num) +{ + int err; + struct vring vring; + struct epf_vringh *evrh; + + evrh = kmalloc(sizeof(*evrh), GFP_KERNEL); + if (!evrh) { + err = -ENOMEM; + goto err_unmap_vq; + } + + evrh->num = num; + + evrh->virt = epf_virtio_map_vq(epf, pci_addr, num, &evrh->phys); + if (IS_ERR(evrh->virt)) + return evrh->virt; + + vring_init(&vring, num, evrh->virt, VIRTIO_PCI_VRING_ALIGN); + + err = vringh_init_iomem(&evrh->vrh, features, num, false, GFP_KERNEL, + vring.desc, vring.avail, vring.used); + if (err) + goto err_free_epf_vq; + + return evrh; + +err_free_epf_vq: + kfree(evrh); + +err_unmap_vq: + epf_virtio_unmap_vq(epf, evrh->virt, evrh->phys, evrh->num); + + return ERR_PTR(err); +} + +#define VIRTIO_PCI_LEGACY_CFG_BAR 0 + +static void __iomem *epf_virtio_alloc_bar(struct pci_epf *epf, size_t size) +{ + struct pci_epf_bar *config_bar = &epf->bar[VIRTIO_PCI_LEGACY_CFG_BAR]; + const struct pci_epc_features *features; + void __iomem *bar; + int err; + + features = pci_epc_get_features(epf->epc, epf->func_no, epf->vfunc_no); + if (!features) { + pr_debug("Failed to get PCI EPC features\n"); + return ERR_PTR(-EOPNOTSUPP); + } + + if (features->reserved_bar & BIT(VIRTIO_PCI_LEGACY_CFG_BAR)) { + pr_debug("Cannot use the PCI BAR for legacy virtio pci\n"); + return ERR_PTR(-EOPNOTSUPP); + } + + if (features->bar_fixed_size[VIRTIO_PCI_LEGACY_CFG_BAR]) { + if (size > + features->bar_fixed_size[VIRTIO_PCI_LEGACY_CFG_BAR]) { + pr_debug("PCI BAR size is not enough\n"); + return ERR_PTR(-ENOMEM); + } + } + + bar = pci_epf_alloc_space(epf, size, VIRTIO_PCI_LEGACY_CFG_BAR, + features->align, PRIMARY_INTERFACE); + if (!bar) { + pr_debug("Failed to allocate virtio-net config memory\n"); + return ERR_PTR(-ENOMEM); + } + + config_bar->flags |= PCI_BASE_ADDRESS_MEM_TYPE_64; + err = pci_epc_set_bar(epf->epc, epf->func_no, epf->vfunc_no, + config_bar); + if (err) { + pr_debug("Failed to set PCI BAR"); + goto err_free_space; + } + + return bar; + +err_free_space: + + pci_epf_free_space(epf, bar, VIRTIO_PCI_LEGACY_CFG_BAR, + PRIMARY_INTERFACE); + + return ERR_PTR(err); +} + +static void epf_virtio_free_bar(struct pci_epf *epf, void __iomem *bar) +{ + struct pci_epf_bar *config_bar = &epf->bar[VIRTIO_PCI_LEGACY_CFG_BAR]; + + pci_epc_clear_bar(epf->epc, epf->func_no, epf->vfunc_no, config_bar); + pci_epf_free_space(epf, bar, VIRTIO_PCI_LEGACY_CFG_BAR, + PRIMARY_INTERFACE); +} + +static void epf_virtio_init_bar(struct epf_virtio *evio, void __iomem *bar) +{ + evio->bar = bar; + + epf_virtio_cfg_write32(evio, VIRTIO_PCI_HOST_FEATURES, evio->features); + epf_virtio_cfg_write16(evio, VIRTIO_PCI_ISR, VIRTIO_PCI_ISR_QUEUE); + epf_virtio_cfg_write16(evio, VIRTIO_PCI_QUEUE_NUM, evio->vqlen); + epf_virtio_cfg_write16(evio, VIRTIO_PCI_QUEUE_NOTIFY, evio->nvq); + epf_virtio_cfg_write8(evio, VIRTIO_PCI_STATUS, 0); +} + +/** + * epf_virtio_init - initialize struct epf_virtio and setup BAR for virtio + * @evio: struct epf_virtio to initialize. + * @hdr: pci configuration space to show remote host. + * @bar_size: pci BAR size it depends on the virtio device type. + * + * Returns zero or a negative error. + */ +int epf_virtio_init(struct epf_virtio *evio, struct pci_epf_header *hdr, + size_t bar_size) +{ + struct pci_epf *epf = evio->epf; + void __iomem *bar; + int err; + + err = pci_epc_write_header(epf->epc, epf->func_no, epf->vfunc_no, hdr); + if (err) + return err; + + bar = epf_virtio_alloc_bar(epf, bar_size); + if (IS_ERR(bar)) + return PTR_ERR(bar); + + epf_virtio_init_bar(evio, bar); + + return 0; +} +EXPORT_SYMBOL_GPL(epf_virtio_init); + +/** + * epf_virtio_final - finalize struct epf_virtio. it frees bar and memories + * @evio: struct epf_virtio to finalize. + */ +void epf_virtio_final(struct epf_virtio *evio) +{ + epf_virtio_free_bar(evio->epf, evio->bar); + + for (int i = 0; i < evio->nvq; i++) + epf_virtio_free_vringh(evio->epf, evio->vrhs[i]); + + kfree(evio->vrhs); +} +EXPORT_SYMBOL_GPL(epf_virtio_final); + +static int epf_virtio_negotiate_vq(struct epf_virtio *evio) +{ + u32 pfn; + u16 sel; + int i = 0; + struct _pair { + u32 pfn; + u16 sel; + } *tmp; + int err = 0; + size_t nvq = evio->nvq; + + tmp = kmalloc_array(nvq, sizeof(tmp[0]), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + /* + * PCIe EP framework has no capability to hook access PCI BAR space from + * remote host driver, so poll the specific register, queue pfn to detect + * the writing from the driver. + * + * This implementation assumes that the address of each virtqueue is + * written only once. + */ + for (i = 0; i < nvq; i++) { + while (!(pfn = epf_virtio_cfg_read32(evio, + VIRTIO_PCI_QUEUE_PFN)) && + evio->running) + ; + + sel = epf_virtio_cfg_read16(evio, VIRTIO_PCI_QUEUE_SEL); + + epf_virtio_cfg_write32(evio, VIRTIO_PCI_QUEUE_PFN, 0); + + tmp[i].pfn = pfn; + tmp[i].sel = sel; + } + + if (!evio->running) + goto err_out; + + evio->vrhs = kmalloc_array(nvq, sizeof(evio->vrhs[0]), GFP_KERNEL); + if (!evio->vrhs) { + err = -ENOMEM; + goto err_out; + } + + for (i = 0; i < nvq; i++) { + phys_addr_t pci = tmp[i].pfn << VIRTIO_PCI_QUEUE_ADDR_SHIFT; + + evio->vrhs[i] = epf_virtio_alloc_vringh( + evio->epf, evio->features, pci, evio->vqlen); + if (IS_ERR(evio->vrhs[i])) { + err = PTR_ERR(evio->vrhs[i]); + goto err_free_evrhs; + } + } + + kfree(tmp); + + return 0; + +err_free_evrhs: + for (i -= 1; i > 0; i--) + epf_virtio_free_vringh(evio->epf, evio->vrhs[i]); + + kfree(evio->vrhs); + +err_out: + kfree(tmp); + + return err; +} + +static void epf_virtio_monitor_qnotify(struct epf_virtio *evio) +{ + const u16 qn_default = evio->nvq; + u16 tmp; + + /* Since there is no way to synchronize between the host and EP functions, + * it is possible to miss multiple notifications. + */ + while (evio->running) { + tmp = epf_virtio_cfg_read16(evio, VIRTIO_PCI_QUEUE_NOTIFY); + if (tmp == qn_default) + continue; + + epf_virtio_cfg_write16(evio, VIRTIO_PCI_QUEUE_NOTIFY, + qn_default); + + evio->qn_callback(evio->qn_param); + } +} + +static int epf_virtio_bgtask(void *param) +{ + struct epf_virtio *evio = param; + int err; + + err = epf_virtio_negotiate_vq(evio); + if (err < 0) { + pr_err("failed to negoticate configs with driver\n"); + return err; + } + + while (!(epf_virtio_cfg_read8(evio, VIRTIO_PCI_STATUS) & + VIRTIO_CONFIG_S_DRIVER_OK) && + evio->running) + ; + + if (evio->ic_callback && evio->running) + evio->ic_callback(evio->ic_param); + + epf_virtio_monitor_qnotify(evio); + + return 0; +} + +/** + * epf_virtio_launch_bgtask - spawn a kthread that emulates virtio device + * operations. + * @evio: It should be initialized prior with epf_virtio_init(). + * + * Returns zero or a negative error. + */ +int epf_virtio_launch_bgtask(struct epf_virtio *evio) +{ + evio->bgtask = kthread_create(epf_virtio_bgtask, evio, + "pci-epf-virtio/bgtask"); + if (IS_ERR(evio->bgtask)) + return PTR_ERR(evio->bgtask); + + evio->running = true; + + sched_set_fifo(evio->bgtask); + wake_up_process(evio->bgtask); + + return 0; +} +EXPORT_SYMBOL_GPL(epf_virtio_launch_bgtask); + +/** + * epf_virtio_terminate_bgtask - shutdown a device emulation kthread. + * @evio: struct epf_virtio it already launched bgtask. + */ +void epf_virtio_terminate_bgtask(struct epf_virtio *evio) +{ + evio->running = false; + + kthread_stop(evio->bgtask); +} +EXPORT_SYMBOL_GPL(epf_virtio_terminate_bgtask); + +/** + * epf_virtio_reset - reset virtio status + * @evio: struct epf_virtio to reset + * + * Returns zero or a negative error. + */ +int epf_virtio_reset(struct epf_virtio *evio) +{ + epf_virtio_terminate_bgtask(evio); + epf_virtio_init_bar(evio, evio->bar); + + return epf_virtio_launch_bgtask(evio); +} +EXPORT_SYMBOL_GPL(epf_virtio_reset); + +/** + * epf_virtio_vrh_memcpy - copy a data with CPU between remote and local vring. + * @evio: struct epf_virtio + * @svrh: vringh for source virtqueue. + * @siov: iovec to store buffer info for source virtqueue + * @dvrh: vringh for destination virtqueue. + * @diov: iovec to store buffer info for destination virtqueue + * @dir: direction of the copy. + * + * Returns zero, one or a negative. + * 0: there is no data in src virtio ring. + * 1: success to transfer data. + * negative: errors. + */ +int epf_virtio_vrh_memcpy(struct epf_virtio *evio, struct vringh *svrh, + struct vringh_kiov *siov, struct vringh *dvrh, + struct vringh_kiov *diov, + enum epf_virtio_copy_dir dir) +{ + int err; + u16 shead, dhead; + size_t slen, dlen, total_len; + void *svirt, *dvirt; + phys_addr_t sbase, dbase, phys; + struct pci_epf *epf = evio->epf; + + err = vringh_getdesc(svrh, siov, NULL, &shead); + if (err <= 0) { + if (err < 0) + pr_err("s err %d\n", err); + return err; + } + + err = vringh_getdesc(dvrh, NULL, diov, &dhead); + if (err <= 0) { + if (err < 0) + pr_err("d err %d\n", err); + return err; + } + + total_len = vringh_kiov_length(siov); + + for (; siov->i < siov->used; siov->i++, diov->i++) { + slen = siov->iov[siov->i].iov_len; + sbase = (u64)siov->iov[siov->i].iov_base; + dlen = diov->iov[diov->i].iov_len; + dbase = (u64)diov->iov[diov->i].iov_base; + + if (dlen < slen) { + pr_err("no space %ld < %ld\n", dlen, slen); + return -ENOSPC; + } + + if (dir == EPF_VIRTIO_COPY_DIR_FROM_DEV) { + svirt = pci_epc_map_addr(epf->epc, epf->func_no, + epf->vfunc_no, sbase, &phys, + slen); + if (IS_ERR(svirt)) { + pr_err("pci_epc_map_addr s %ld\n", + PTR_ERR(svirt)); + return PTR_ERR(svirt); + } + + dvirt = phys_to_virt(dbase); + memcpy_fromio(dvirt, svirt, slen); + + pci_epc_unmap_addr(epf->epc, epf->func_no, + epf->vfunc_no, phys, svirt, slen); + } else { + svirt = phys_to_virt(sbase); + dvirt = pci_epc_map_addr(epf->epc, epf->func_no, + epf->vfunc_no, dbase, &phys, + dlen); + if (IS_ERR(dvirt)) { + pr_err("pci_epc_map_addr d %ld\n", + PTR_ERR(dvirt)); + return PTR_ERR(dvirt); + } + + memcpy_toio(dvirt, svirt, slen); + + pci_epc_unmap_addr(epf->epc, epf->func_no, + epf->vfunc_no, phys, dvirt, dlen); + } + } + + vringh_complete(svrh, shead, total_len); + vringh_complete(dvrh, dhead, total_len); + + return 1; +} +EXPORT_SYMBOL_GPL(epf_virtio_vrh_memcpy); + +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/endpoint/functions/pci-epf-virtio.h b/drivers/pci/endpoint/functions/pci-epf-virtio.h new file mode 100644 index 000000000000..162029f6da70 --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-virtio.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PCI_EPF_VIRTIO_H__ +#define __PCI_EPF_VIRTIO_H__ + +#include +#include +#include + +struct epf_vringh { + struct vringh vrh; + void __iomem *virt; + phys_addr_t phys; + unsigned int num; +}; + +struct epf_virtio { + /* Base PCI endpoint function */ + struct pci_epf *epf; + + /* Virtio parameters */ + u64 features; + size_t bar_size; + size_t nvq; + size_t vqlen; + + /* struct to access virtqueue on remote host */ + struct epf_vringh **vrhs; + + /* struct for thread to emulate virtio device */ + struct task_struct *bgtask; + + /* Virtual address of pci configuration space */ + void __iomem *bar; + + /* Callback function and parameter for queue notifcation + * Note: PCI EP function cannot detect qnotify accurately, therefore this + * callback function should check all of virtqueue's changes. + */ + void (*qn_callback)(void *param); + void *qn_param; + + /* Callback function and parameter for initialize complete */ + void (*ic_callback)(void *param); + void *ic_param; + + bool running; +}; + +#define DEFINE_EPF_VIRTIO_CFG_READ(size) \ + static inline u##size epf_virtio_cfg_read##size( \ + struct epf_virtio *evio, size_t offset) \ + { \ + void __iomem *base = evio->bar + offset; \ + return ioread##size(base); \ + } + +DEFINE_EPF_VIRTIO_CFG_READ(8) +DEFINE_EPF_VIRTIO_CFG_READ(16) +DEFINE_EPF_VIRTIO_CFG_READ(32) + +#define DEFINE_EPF_VIRTIO_CFG_WRITE(size) \ + static inline void epf_virtio_cfg_write##size( \ + struct epf_virtio *evio, size_t offset, u##size value) \ + { \ + void __iomem *base = evio->bar + offset; \ + iowrite##size(value, base); \ + } + +DEFINE_EPF_VIRTIO_CFG_WRITE(8); +DEFINE_EPF_VIRTIO_CFG_WRITE(16); +DEFINE_EPF_VIRTIO_CFG_WRITE(32); + +#define DEFINE_EPF_VIRTIO_CFG_SET(size) \ + static inline void epf_virtio_cfg_set##size( \ + struct epf_virtio *evio, size_t offset, u##size value) \ + { \ + void __iomem *base = evio->bar + offset; \ + iowrite##size(ioread##size(base) | value, base); \ + } + +DEFINE_EPF_VIRTIO_CFG_SET(8) +DEFINE_EPF_VIRTIO_CFG_SET(16) +DEFINE_EPF_VIRTIO_CFG_SET(32) + +#define DEFINE_EPF_VIRTIO_CFG_CLEAR(size) \ + static inline void epf_virtio_cfg_clear##size( \ + struct epf_virtio *evio, size_t offset, u##size value) \ + { \ + void __iomem *base = evio->bar + offset; \ + iowrite##size(ioread##size(base) & ~value, base); \ + } + +DEFINE_EPF_VIRTIO_CFG_CLEAR(8) +DEFINE_EPF_VIRTIO_CFG_CLEAR(16) +DEFINE_EPF_VIRTIO_CFG_CLEAR(32) + +static inline void epf_virtio_cfg_memcpy_toio(struct epf_virtio *evio, + size_t offset, void *buf, + size_t len) +{ + void __iomem *base = evio->bar + offset; + + memcpy_toio(base, buf, len); +} + +int epf_virtio_init(struct epf_virtio *evio, struct pci_epf_header *hdr, + size_t bar_size); +void epf_virtio_final(struct epf_virtio *evio); +int epf_virtio_launch_bgtask(struct epf_virtio *evio); +void epf_virtio_terminate_bgtask(struct epf_virtio *evio); +int epf_virtio_reset(struct epf_virtio *evio); + +enum epf_virtio_copy_dir { + EPF_VIRTIO_COPY_DIR_FROM_DEV, + EPF_VIRTIO_COPY_DIR_TO_DEV, +}; + +int epf_virtio_vrh_memcpy(struct epf_virtio *evio, struct vringh *svrh, + struct vringh_kiov *siov, struct vringh *dvrh, + struct vringh_kiov *diov, + enum epf_virtio_copy_dir dir); + +#endif /* __PCI_EPF_VIRTIO_H__ */ From patchwork Fri Apr 14 12:39:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Shunsuke Mie X-Patchwork-Id: 13211424 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 38039C77B6E for ; Fri, 14 Apr 2023 12:39:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229924AbjDNMjZ (ORCPT ); Fri, 14 Apr 2023 08:39:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57196 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229540AbjDNMjY (ORCPT ); Fri, 14 Apr 2023 08:39:24 -0400 Received: from mail-pj1-x102a.google.com (mail-pj1-x102a.google.com [IPv6:2607:f8b0:4864:20::102a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8DB9426A1 for ; Fri, 14 Apr 2023 05:39:22 -0700 (PDT) Received: by mail-pj1-x102a.google.com with SMTP id g6so1314510pjx.4 for ; Fri, 14 Apr 2023 05:39:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=igel-co-jp.20221208.gappssmtp.com; s=20221208; t=1681475962; x=1684067962; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fuOjoATSwNwQ5isXLaWbsITf7ep/YO9eHaGnk3Xv1+k=; b=b7ywarHDTXn9DuzxgGH5T9X8yXaaRlqT+o0+z+U6K56Sv8klVOP0I5Sja/eBAUBiQA auAsHdsRzbKjwT2F5ktIGoYPu30zgnbAFR483hcB5Pz9pWW77rBoy5YnVGG/MANXW3AC wYUuWfXmJ/yjqrUl3NuDTFn/Q9EloGImclGA+aHoB+5UEG+EUe5WDswjqS4gpxUBRcgj YI8GZCrPcs360w5VTYi/qWwCXraGrUBWFsZU2h5AiBLHRG298zVOWR4yLTcVT4/7FbGy /cFujgoQrZINwbfcMBOcQgucZ7bZYfGx+d6aUQEgzZ2UEPxO0PfP3fw0UiRqW/R7wSe1 T+BQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681475962; x=1684067962; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=fuOjoATSwNwQ5isXLaWbsITf7ep/YO9eHaGnk3Xv1+k=; b=IamedXEQVNGZSBcg9APUsAps06x/ZsGgQIJoiJkZbz9URGas2jiY8m+CiOGsvUPOPS /eI0Pyq1BEJMnJNxyc3OEWxBXwwTbov02cNtQIkLyu0x5mnSGAV8e+gTBtxXyjfoMMOM 5Pf8l2cqh+dhhNNzo7ZQNPwqFEdFnIie4mIg3a/NisCuIaDq0zbCiX50RgMe/jxQU9Vq 9YHEYPY23O/JLM7VYDmK25jwIZm2+0qIo1W8XEsYF0tbJ9H8W4IjdiW8q23Y93OE5rGw Ead+VcIJRpxljl/AxnB4EJAIz0Ykd4oMPgLN0t2m54z5DWfLoBlwTRSvDNE8MQNk+IoU XnwQ== X-Gm-Message-State: AAQBX9duhb/nP1HQZyUgaupwnreMFnlaSlSlK5w9dSGEvwOSaFqo5OY9 j4Ksr0kAiowQ0cg16Hy1WHR+EQ== X-Google-Smtp-Source: AKy350bbTAYGABfXjfsLqUTU/WrsVCelzkOFGUGytTzIM5zBcMTZxDzCBF1sB9aPe7WOcn+g/Ntnkw== X-Received: by 2002:a17:902:7892:b0:1a1:97b5:c63e with SMTP id q18-20020a170902789200b001a197b5c63emr2479303pll.38.1681475961919; Fri, 14 Apr 2023 05:39:21 -0700 (PDT) Received: from tyrell.hq.igel.co.jp (napt.igel.co.jp. [219.106.231.132]) by smtp.gmail.com with ESMTPSA id v21-20020a1709028d9500b001a527761c31sm3015366plo.79.2023.04.14.05.39.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Apr 2023 05:39:21 -0700 (PDT) From: Shunsuke Mie To: Lorenzo Pieralisi Cc: =?utf-8?q?Krzysztof_Wilczy=C5=84ski?= , Manivannan Sadhasivam , Kishon Vijay Abraham I , Bjorn Helgaas , "Michael S. Tsirkin" , Jason Wang , Shunsuke Mie , Frank Li , Jon Mason , Randy Dunlap , Ren Zhijie , linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, virtualization@lists.linux-foundation.org Subject: [RFC PATCH 2/3] virtio_pci: add a definition of queue flag in ISR Date: Fri, 14 Apr 2023 21:39:02 +0900 Message-Id: <20230414123903.896914-3-mie@igel.co.jp> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230414123903.896914-1-mie@igel.co.jp> References: <20230414123903.896914-1-mie@igel.co.jp> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org Already it has beed defined a config changed flag of ISR, but not the queue flag. Add a macro for it. Signed-off-by: Shunsuke Mie --- include/uapi/linux/virtio_pci.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/uapi/linux/virtio_pci.h b/include/uapi/linux/virtio_pci.h index f703afc7ad31..d405bea27240 100644 --- a/include/uapi/linux/virtio_pci.h +++ b/include/uapi/linux/virtio_pci.h @@ -94,6 +94,9 @@ #endif /* VIRTIO_PCI_NO_LEGACY */ +/* Bits for ISR status field:only when if MSI-X is disabled */ +/* The bit of the ISR which indicates a queue entry update. */ +#define VIRTIO_PCI_ISR_QUEUE 0x1 /* The bit of the ISR which indicates a device configuration change. */ #define VIRTIO_PCI_ISR_CONFIG 0x2 /* Vector value used to disable MSI for queue */ From patchwork Fri Apr 14 12:39:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Shunsuke Mie X-Patchwork-Id: 13211425 X-Patchwork-Delegate: bhelgaas@google.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id EB478C77B72 for ; Fri, 14 Apr 2023 12:39:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230034AbjDNMji (ORCPT ); Fri, 14 Apr 2023 08:39:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57622 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229879AbjDNMjc (ORCPT ); Fri, 14 Apr 2023 08:39:32 -0400 Received: from mail-pj1-x1033.google.com (mail-pj1-x1033.google.com [IPv6:2607:f8b0:4864:20::1033]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 125FDB47A for ; Fri, 14 Apr 2023 05:39:27 -0700 (PDT) Received: by mail-pj1-x1033.google.com with SMTP id c10-20020a17090abf0a00b0023d1bbd9f9eso21512606pjs.0 for ; Fri, 14 Apr 2023 05:39:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=igel-co-jp.20221208.gappssmtp.com; s=20221208; t=1681475966; x=1684067966; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=/bZUOCFJCwCTXEmIix5ExPWra6yq8PYVXPbuX/TUPYk=; b=MWd39ynIJViiyKLNZn7EmzvAfFb5U2QYe7bxTITH6C0UqB4h0+hQOioJcUMB5sXmdJ T+0pxHnd5pF8+ZGgmMjFnmacv5+Sqbjyi+doh5eUfIXYXgHceY5LXPU4PZLz6kZTkj4/ Sk4f6jvOpziowEWlOzaCrm+flcJG9VblgAPwa3wu51aWiPxxBdyioxBEueuk70Vq1XOB tfgKhzc5a7T66KLw7BIOIH6nZj/oTzuveSJ74OE9h+5FEffp4M5jZuqqeIl/An8leEej 0hEu6KmRhV/4fzsRccGZyASLI4BSogF7Jhm2d+uGh4lXHOLgTibth7oDERYhVXMknPwb AoUA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681475966; x=1684067966; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=/bZUOCFJCwCTXEmIix5ExPWra6yq8PYVXPbuX/TUPYk=; b=kYfavrjJ7/Z10h2eVNERBIz/Oec6OpmKC61pzTdV5gHDDaOLmylG+lm9bTVQV7H0Em aPYrqtvXVKxLqyKP1OhYXP7TDNlrglUqoe1dDn5ueUuZRYAPph3HIMNLglcUsCetujeu qPlPAD6Cf0a5h8E5zyOdNOcWgynM2AghZjjf6kZH3bVxC1euARhhFNV+5lk9PMwWPVnI wm5UMAqIVWaYiNL/c+Tm7AWESEFO1T5w81QTd46/Mb7f+VAUDITmXynz389zSVgFyLkk 87CSPEOILmbspCj9GygnGv1RAfMN/lfUMM/9BGGtLPh82ZFzOcGxGZNXzXC8aY4S5Sxw IVuQ== X-Gm-Message-State: AAQBX9fChDTL57Lx7V6ViSZ+T8T+vmhXdQcZOlmMxT1lS00eZeYz/TTK PVd0ZJSYlXCBlNT9vmS7dDeBqw== X-Google-Smtp-Source: AKy350ak1/UH0qD0KBNwE0XAaWFqrzYdc0/21yPnYwvrMkNXVZpN7ZN3WtHAXMZqRGKGPlGPgDKswA== X-Received: by 2002:a17:902:b18c:b0:19f:2ae8:54b9 with SMTP id s12-20020a170902b18c00b0019f2ae854b9mr2159184plr.32.1681475966341; Fri, 14 Apr 2023 05:39:26 -0700 (PDT) Received: from tyrell.hq.igel.co.jp (napt.igel.co.jp. [219.106.231.132]) by smtp.gmail.com with ESMTPSA id v21-20020a1709028d9500b001a527761c31sm3015366plo.79.2023.04.14.05.39.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Apr 2023 05:39:25 -0700 (PDT) From: Shunsuke Mie To: Lorenzo Pieralisi Cc: =?utf-8?q?Krzysztof_Wilczy=C5=84ski?= , Manivannan Sadhasivam , Kishon Vijay Abraham I , Bjorn Helgaas , "Michael S. Tsirkin" , Jason Wang , Shunsuke Mie , Frank Li , Jon Mason , Randy Dunlap , Ren Zhijie , linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org, virtualization@lists.linux-foundation.org Subject: [RFC PATCH 3/3] PCI: endpoint: Add EP function driver to provide virtio-console functionality Date: Fri, 14 Apr 2023 21:39:03 +0900 Message-Id: <20230414123903.896914-4-mie@igel.co.jp> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230414123903.896914-1-mie@igel.co.jp> References: <20230414123903.896914-1-mie@igel.co.jp> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pci@vger.kernel.org Add a new PCIe endpoint function driver that works as a pci virtio-console device. The console connect to endpoint side console. It enables to communicate PCIe host and endpoint. Architecture is following: ┌────────────┐ ┌──────────────────────┬────────────┐ │virtioe │ │ │virtio │ │console drv │ ├───────────────┐ │console drv │ ├────────────┤ │(virtio console│ ├────────────┤ │ virtio bus │ │ device) │◄────►│ virtio bus │ ├────────────┤ ├---------------┤ └────────────┤ │ │ │ pci ep virtio │ │ │ pci bus │ │ console drv │ │ │ │ pcie ├───────────────┤ │ │ │ ◄─────► │ pci ep Bus │ │ └────────────┘ └───────────────┴───────────────────┘ PCIe Root PCIe Endpoint This driver has two roles. The first is as a PCIe endpoint virtio console function, which is implemented using the PCIe endpoint framework and PCIe EP virtio helpers. The second is as a virtual virtio console device connected to the virtio bus on PCIe endpoint Linux. Communication between the two is achieved by copying the virtqueue data between PCIe root and endpoint, respectively. This is a simple implementation and does not include features of virtio-console such as MULTIPORT, EMERG_WRITE, etc. As a result, each virtio console driver only displays /dev/hvc0. As an example of usage, by setting getty to /dev/hvc0, it is possible to login to another host. Signed-off-by: Shunsuke Mie --- drivers/pci/endpoint/functions/Kconfig | 12 + drivers/pci/endpoint/functions/Makefile | 1 + drivers/pci/endpoint/functions/pci-epf-vcon.c | 554 ++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 drivers/pci/endpoint/functions/pci-epf-vcon.c diff --git a/drivers/pci/endpoint/functions/Kconfig b/drivers/pci/endpoint/functions/Kconfig index cf16d33c9585..890e07b51cbe 100644 --- a/drivers/pci/endpoint/functions/Kconfig +++ b/drivers/pci/endpoint/functions/Kconfig @@ -44,3 +44,15 @@ config PCI_EPF_VIRTIO select VHOST_IOMEM help Helpers to implement PCI virtio Endpoint function + +config PCI_EPF_VCON + tristate "PCI Endpoint virito-console driver" + depends on PCI_ENDPOINT + select VHOST_RING + select PCI_EPF_VIRTIO + help + PCIe Endpoint virtio-console function implementatino. This module + enables to show the virtio-console as pci device to PCIe host side, and + another virtual virtio-console device registers to endpoint system. + Those devices are connected virtually and can communicate each other. + diff --git a/drivers/pci/endpoint/functions/Makefile b/drivers/pci/endpoint/functions/Makefile index a96f127ce900..b4056689ce33 100644 --- a/drivers/pci/endpoint/functions/Makefile +++ b/drivers/pci/endpoint/functions/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_PCI_EPF_TEST) += pci-epf-test.o obj-$(CONFIG_PCI_EPF_NTB) += pci-epf-ntb.o obj-$(CONFIG_PCI_EPF_VNTB) += pci-epf-vntb.o obj-$(CONFIG_PCI_EPF_VIRTIO) += pci-epf-virtio.o +obj-$(CONFIG_PCI_EPF_VCON) += pci-epf-vcon.o diff --git a/drivers/pci/endpoint/functions/pci-epf-vcon.c b/drivers/pci/endpoint/functions/pci-epf-vcon.c new file mode 100644 index 000000000000..39538133e39d --- /dev/null +++ b/drivers/pci/endpoint/functions/pci-epf-vcon.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PCI Endpoint function driver to impliment virtio-console device + * functionality. + */ +#include +#include +#include +#include +#include + +#include "pci-epf-virtio.h" + +static int virtio_queue_size = 0x100; +module_param(virtio_queue_size, int, 0444); +MODULE_PARM_DESC(virtio_queue_size, "A length of virtqueue"); + +struct epf_vcon { + /* To access virtqueues on remote host */ + struct epf_virtio evio; + struct vringh_kiov *rdev_iovs; + + /* To register a local virtio bus */ + struct virtio_device vdev; + + /* To access virtqueus of local host driver */ + struct vringh *vdev_vrhs; + struct vringh_kiov *vdev_iovs; + struct virtqueue **vdev_vqs; + + /* For transportation and notification */ + struct workqueue_struct *task_wq; + struct work_struct raise_irq_work, rx_work, tx_work; + + /* To retain virtio features. It is commonly used local and remote. */ + u64 features; + + /* To show a status whether this driver is ready and the remote is connected */ + bool connected; +}; + +enum { + VCON_VIRTQUEUE_RX, + VCON_VIRTQUEUE_TX, +}; + +static struct epf_vcon *vdev_to_vcon(struct virtio_device *vdev) +{ + return container_of(vdev, struct epf_vcon, vdev); +} + +static unsigned int epf_vcon_get_nvq(struct epf_vcon *vcon) +{ + /* This is a minimum implementation. Doesn't support any features of + * virtio console. It means driver and device use just 2 virtuque for tx + * and rx. + */ + return 2; +} + +static void epf_vcon_rx_handler(struct work_struct *work) +{ + struct epf_vcon *vcon = container_of(work, struct epf_vcon, rx_work); + struct epf_virtio *evio = &vcon->evio; + struct vringh *svrh, *dvrh; + struct vringh_kiov *siov, *diov; + int ret; + + if (unlikely(!vcon->connected)) + return; + + svrh = &evio->vrhs[VCON_VIRTQUEUE_TX]->vrh; + dvrh = &vcon->vdev_vrhs[VCON_VIRTQUEUE_RX]; + siov = &vcon->rdev_iovs[VCON_VIRTQUEUE_TX]; + diov = &vcon->vdev_iovs[VCON_VIRTQUEUE_RX]; + + do { + ret = epf_virtio_vrh_memcpy(evio, svrh, siov, dvrh, diov, + EPF_VIRTIO_COPY_DIR_FROM_DEV); + if (unlikely(ret < 0)) + pr_err("failed to copy desc on virtqueue for rx\n"); + } while (ret > 0); + + vring_interrupt(0, vcon->vdev_vqs[VCON_VIRTQUEUE_RX]); +} + +static void epf_vcon_tx_handler(struct work_struct *work) +{ + struct epf_vcon *vcon = container_of(work, struct epf_vcon, tx_work); + struct epf_virtio *evio = &vcon->evio; + struct vringh *svrh, *dvrh; + struct vringh_kiov *siov, *diov; + int ret; + + if (unlikely(!vcon->connected)) + return; + + svrh = &vcon->vdev_vrhs[VCON_VIRTQUEUE_TX]; + dvrh = &evio->vrhs[VCON_VIRTQUEUE_RX]->vrh; + siov = &vcon->vdev_iovs[VCON_VIRTQUEUE_TX]; + diov = &vcon->rdev_iovs[VCON_VIRTQUEUE_RX]; + + do { + ret = epf_virtio_vrh_memcpy(evio, svrh, siov, dvrh, diov, + EPF_VIRTIO_COPY_DIR_TO_DEV); + if (unlikely(ret < 0)) + pr_err("failed to copy desc on virtqueue for tx\n"); + } while (ret > 0); + + queue_work(vcon->task_wq, &vcon->raise_irq_work); +} + +static void epf_vcon_raise_irq_handler(struct work_struct *work) +{ + struct epf_vcon *vcon = + container_of(work, struct epf_vcon, raise_irq_work); + struct pci_epf *epf = vcon->evio.epf; + + pci_epc_raise_irq(epf->epc, epf->func_no, epf->vfunc_no, + PCI_EPC_IRQ_LEGACY, 0); +} + +static int epf_vcon_setup_common(struct epf_vcon *vcon) +{ + vcon->features = 0; + vcon->connected = false; + + vcon->task_wq = + alloc_workqueue("pci-epf-vcon/task-wq", + WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_UNBOUND, 0); + if (!vcon->task_wq) + return -ENOMEM; + + INIT_WORK(&vcon->rx_work, epf_vcon_rx_handler); + INIT_WORK(&vcon->tx_work, epf_vcon_tx_handler); + INIT_WORK(&vcon->raise_irq_work, epf_vcon_raise_irq_handler); + + return 0; +} + +static void epf_vcon_cleanup_common(struct epf_vcon *vcon) +{ + /* Should do first to stop polling in other kernel thread */ + vcon->connected = false; + + flush_work(&vcon->raise_irq_work); + flush_work(&vcon->tx_work); + flush_work(&vcon->rx_work); + + destroy_workqueue(vcon->task_wq); +} + +static void epf_vcon_qnotify_callback(void *param) +{ + struct epf_vcon *vcon = param; + + queue_work(vcon->task_wq, &vcon->rx_work); +} + +static void epf_vcon_initialize_complete(void *param) +{ + struct epf_vcon *vcon = param; + + pr_debug("Remote host has connected\n"); + + vcon->connected = true; + + /* send filled buffer */ + queue_work(vcon->task_wq, &vcon->tx_work); +} + +static struct pci_epf_header epf_vcon_pci_header = { + .vendorid = PCI_VENDOR_ID_REDHAT_QUMRANET, + .deviceid = VIRTIO_TRANS_ID_CONSOLE, + .subsys_vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET, + .subsys_id = VIRTIO_ID_CONSOLE, + .revid = 0, + .baseclass_code = PCI_BASE_CLASS_COMMUNICATION, + .interrupt_pin = PCI_INTERRUPT_PIN, +}; + +static int epf_vcon_setup_ep_func(struct epf_vcon *vcon, struct pci_epf *epf) +{ + int err; + struct epf_virtio *evio = &vcon->evio; + unsigned int nvq = epf_vcon_get_nvq(vcon); + + vcon->rdev_iovs = + kmalloc_array(nvq, sizeof(vcon->rdev_iovs[0]), GFP_KERNEL); + if (!vcon->rdev_iovs) + return -ENOMEM; + + for (int i = 0; i < nvq; i++) + vringh_kiov_init(&vcon->rdev_iovs[i], NULL, 0); + + evio->epf = epf; + evio->features = vcon->features; + evio->nvq = nvq; + evio->vqlen = virtio_queue_size; + + evio->qn_callback = epf_vcon_qnotify_callback; + evio->qn_param = vcon; + + evio->ic_callback = epf_vcon_initialize_complete; + evio->ic_param = vcon; + + err = epf_virtio_init(evio, &epf_vcon_pci_header, 0); + if (err) + goto err_cleanup_kiov; + + err = epf_virtio_launch_bgtask(evio); + if (err) + goto err_virtio_final; + + return 0; + +err_virtio_final: + epf_virtio_final(evio); + +err_cleanup_kiov: + for (int i = 0; i < nvq; i++) + vringh_kiov_cleanup(&vcon->rdev_iovs[i]); + + kfree(vcon->rdev_iovs); + + return err; +} + +static void epf_vcon_cleanup_ep_func(struct epf_vcon *vcon) +{ + epf_virtio_terminate_bgtask(&vcon->evio); + + epf_virtio_final(&vcon->evio); + + kfree(vcon->rdev_iovs); +} + +/* + * Functions for local virtio device operation + */ +static u64 epf_vcon_vdev_get_features(struct virtio_device *vdev) +{ + struct epf_vcon *vcon = vdev_to_vcon(vdev); + + return vcon->features; +} + +static int epf_vcon_vdev_finalize_features(struct virtio_device *vdev) +{ + struct epf_vcon *vcon = vdev_to_vcon(vdev); + + return vdev->features != vcon->features; +} + +static void epf_vcon_vdev_get_config(struct virtio_device *vdev, + unsigned int offset, void *buf, + unsigned int len) +{ + /* There is no config for virtio console because this console device + * doesn't any support features + */ + memset(buf, 0x00, len); +} + +static void epf_vcon_vdev_set_config(struct virtio_device *vdev, + unsigned int offset, const void *buf, + unsigned int len) +{ + /* Do nothing because this console device doesn't any support features */ +} + +static u8 epf_vcon_vdev_get_status(struct virtio_device *vdev) +{ + return 0; +} + +static void epf_vcon_vdev_set_status(struct virtio_device *vdev, u8 status) +{ + if (status & VIRTIO_CONFIG_S_FAILED) + pr_debug("driver failed to setup this device\n"); +} + +static void epf_vcon_vdev_reset(struct virtio_device *vdev) +{ + struct epf_vcon *vcon = vdev_to_vcon(vdev); + + epf_virtio_reset(&vcon->evio); +} + +static bool epf_vcon_vdev_vq_notify(struct virtqueue *vq) +{ + struct epf_vcon *vcon = vdev_to_vcon(vq->vdev); + + switch (vq->index) { + case VCON_VIRTQUEUE_RX: + case VCON_VIRTQUEUE_TX: + queue_work(vcon->task_wq, &vcon->tx_work); + break; + default: + return false; + } + + return true; +} + +static int epf_vcon_vdev_find_vqs(struct virtio_device *vdev, unsigned int nvqs, + struct virtqueue *vqs[], + vq_callback_t *callback[], + const char *const names[], const bool *ctx, + struct irq_affinity *desc) +{ + struct epf_vcon *vcon = vdev_to_vcon(vdev); + int err; + int qidx, i; + + if (nvqs > epf_vcon_get_nvq(vcon)) + return -EINVAL; + + for (qidx = 0, i = 0; i < nvqs; i++) { + struct virtqueue *vq; + const struct vring *vring; + + if (!names[i]) { + vqs[i] = NULL; + continue; + } + + vq = vring_create_virtqueue(qidx++, virtio_queue_size, + VIRTIO_PCI_VRING_ALIGN, vdev, true, + false, ctx ? ctx[i] : false, + epf_vcon_vdev_vq_notify, + callback[i], names[i]); + if (!vq) { + err = -ENOMEM; + goto err_del_vqs; + } + + vqs[i] = vq; + vcon->vdev_vqs[i] = vq; + + vring = virtqueue_get_vring(vq); + err = vringh_init_kern(&vcon->vdev_vrhs[i], vcon->features, + virtio_queue_size, false, GFP_KERNEL, + vring->desc, vring->avail, vring->used); + if (err) { + pr_err("failed to init vringh for vring %d\n", i); + goto err_del_vqs; + } + } + + return 0; + +err_del_vqs: + for (; i >= 0; i--) { + if (!names[i]) + continue; + + if (!vqs[i]) + continue; + + vring_del_virtqueue(vqs[i]); + } + return err; +} + +static void epf_vcon_vdev_del_vqs(struct virtio_device *vdev) +{ + struct epf_vcon *vcon = vdev_to_vcon(vdev); + + for (int i = 0; i < epf_vcon_get_nvq(vcon); i++) { + if (!vcon->vdev_vqs[i]) + continue; + + vring_del_virtqueue(vcon->vdev_vqs[i]); + } +} + +static const struct virtio_config_ops epf_vcon_vdev_config_ops = { + .get_features = epf_vcon_vdev_get_features, + .finalize_features = epf_vcon_vdev_finalize_features, + .get = epf_vcon_vdev_get_config, + .set = epf_vcon_vdev_set_config, + .get_status = epf_vcon_vdev_get_status, + .set_status = epf_vcon_vdev_set_status, + .reset = epf_vcon_vdev_reset, + .find_vqs = epf_vcon_vdev_find_vqs, + .del_vqs = epf_vcon_vdev_del_vqs, +}; + +static void epf_vcon_vdev_release(struct device *dev) +{ + /* Do nothing, because the struct virtio_device will be reused. */ +} + +static int epf_vcon_setup_vdev(struct epf_vcon *vcon, struct device *parent) +{ + int err; + struct virtio_device *vdev = &vcon->vdev; + const unsigned int nvq = epf_vcon_get_nvq(vcon); + + vcon->vdev_vrhs = + kmalloc_array(nvq, sizeof(vcon->vdev_vrhs[0]), GFP_KERNEL); + if (!vcon->vdev_vrhs) + return -ENOMEM; + + vcon->vdev_iovs = + kmalloc_array(nvq, sizeof(vcon->vdev_iovs[0]), GFP_KERNEL); + if (!vcon->vdev_iovs) { + err = -ENOMEM; + goto err_free_vrhs; + } + + for (int i = 0; i < nvq; i++) + vringh_kiov_init(&vcon->vdev_iovs[i], NULL, 0); + + vcon->vdev_vqs = + kmalloc_array(nvq, sizeof(vcon->vdev_vrhs[0]), GFP_KERNEL); + if (!vcon->vdev_vqs) { + err = -ENOMEM; + goto err_cleanup_kiov; + } + + vdev->dev.parent = parent; + vdev->dev.release = epf_vcon_vdev_release; + vdev->config = &epf_vcon_vdev_config_ops; + vdev->id.vendor = PCI_VENDOR_ID_REDHAT_QUMRANET; + vdev->id.device = VIRTIO_ID_CONSOLE; + + err = register_virtio_device(vdev); + if (err) + goto err_free_vdev_vqs; + + return 0; + +err_free_vdev_vqs: + kfree(vcon->vdev_vqs); + +err_cleanup_kiov: + for (int i = 0; i < nvq; i++) + vringh_kiov_cleanup(&vcon->vdev_iovs[i]); + + kfree(vcon->vdev_iovs); + +err_free_vrhs: + kfree(vcon->vdev_vrhs); + + return err; +} + +static void epf_vcon_cleanup_vdev(struct epf_vcon *vcon) +{ + unregister_virtio_device(&vcon->vdev); + /* Cleanup struct virtio_device that has kobject, otherwise error occures when + * reregister the virtio device. + */ + memset(&vcon->vdev, 0x00, sizeof(vcon->vdev)); + + kfree(vcon->vdev_vqs); + + for (int i = 0; i < epf_vcon_get_nvq(vcon); i++) + vringh_kiov_cleanup(&vcon->vdev_iovs[i]); + + kfree(vcon->vdev_iovs); + kfree(vcon->vdev_vrhs); +} + +static int epf_vcon_bind(struct pci_epf *epf) +{ + struct epf_vcon *vcon = epf_get_drvdata(epf); + int err; + + err = epf_vcon_setup_common(vcon); + if (err) + return err; + + err = epf_vcon_setup_ep_func(vcon, epf); + if (err) + goto err_cleanup_common; + + err = epf_vcon_setup_vdev(vcon, epf->epc->dev.parent); + if (err) + goto err_cleanup_ep_func; + + return 0; + +err_cleanup_common: + epf_vcon_cleanup_common(vcon); + +err_cleanup_ep_func: + epf_vcon_cleanup_ep_func(vcon); + + return err; +} + +static void epf_vcon_unbind(struct pci_epf *epf) +{ + struct epf_vcon *vcon = epf_get_drvdata(epf); + + epf_vcon_cleanup_common(vcon); + epf_vcon_cleanup_ep_func(vcon); + epf_vcon_cleanup_vdev(vcon); +} + +static struct pci_epf_ops epf_vcon_ops = { + .bind = epf_vcon_bind, + .unbind = epf_vcon_unbind, +}; + +static const struct pci_epf_device_id epf_vcon_ids[] = { + { .name = "pci_epf_vcon" }, + {} +}; + +static int epf_vcon_probe(struct pci_epf *epf) +{ + struct epf_vcon *vcon; + + vcon = devm_kzalloc(&epf->dev, sizeof(*vcon), GFP_KERNEL); + if (!vcon) + return -ENOMEM; + + epf_set_drvdata(epf, vcon); + + return 0; +} + +static struct pci_epf_driver epf_vcon_drv = { + .driver.name = "pci_epf_vcon", + .ops = &epf_vcon_ops, + .id_table = epf_vcon_ids, + .probe = epf_vcon_probe, + .owner = THIS_MODULE, +}; + +static int __init epf_vcon_init(void) +{ + int err; + + err = pci_epf_register_driver(&epf_vcon_drv); + if (err) + pr_err("Failed to register PCI EP virtio-console function\n"); + + return 0; +} +module_init(epf_vcon_init); + +static void epf_vcon_exit(void) +{ + pci_epf_unregister_driver(&epf_vcon_drv); +} +module_exit(epf_vcon_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Shunsuke Mie ");