/* * Copyright (C) 2018-2022 Intel Corporation. * * SPDX-License-Identifier: BSD-3-Clause * */ /* * virtio ipu * IPU mediator DM */ #include #include #include #include #include #include #include "dm.h" #include "pci_core.h" #include "virtio.h" #include "virtio_kernel.h" #include "vmmapi.h" /* * Size of queue was chosen experimentally in a way * that it allows to do IPU ISYS MIPI camera capture without * any delay for interrupt/msg send, this value should * be tuned. */ #define VIRTIO_IPU_RINGSZ 1024 /* * IPU mediation makes use of 2 VQs. * VQ0 for buffer mgmnt & 1 time configuration; * VQ1 for interrupt/msg exchange */ #define VIRTIO_IPU_VQ_NUM 2 #define IPU_VBS_DEV_PATH "/dev/vbs_ipu" static int ipu_log_level; #define TAG "virtio_ipu: " #define LERR 1 #define LWRN 2 #define LDBG 3 #define IPRINTF(lvl, fmt, args...) \ do { if (lvl <= ipu_log_level) pr_dbg(TAG fmt, ##args); } while (0) struct virtio_ipu { struct virtio_base base; struct virtio_vq_info vq[VIRTIO_IPU_VQ_NUM]; pthread_mutex_t mtx; /* VBS-K variables */ struct { enum VBS_K_STATUS ipu_kstatus; int ipu_fd; struct vbs_dev_info ipu_kdev; struct vbs_vqs_info ipu_kvqs; } vbs_k; }; static int virtio_ipu_k_init(struct virtio_ipu *ipu); static int virtio_ipu_k_start(struct virtio_ipu *ipu); static int virtio_ipu_k_stop(struct virtio_ipu *ipu); static int virtio_ipu_k_reset(struct virtio_ipu *ipu); static int virtio_ipu_k_dev_set(struct vbs_dev_info *ipu_kdev, const char *name, int vmid, int nvq, uint32_t feature, uint64_t pio_start, uint64_t pio_len); static int virtio_ipu_k_vq_set(struct vbs_vqs_info *ipu_kvqs, unsigned int nvq, unsigned int idx, uint16_t qsize, uint32_t pfn, uint16_t msix_idx, uint64_t msix_addr, uint32_t msix_data); static void virtio_ipu_no_notify(void *, struct virtio_vq_info *); static void virtio_ipu_set_status(void *, uint64_t); static void virtio_ipu_reset(void *); static struct virtio_ops virtio_ipu_ops_k = { "virtio_ipu", /* our name */ VIRTIO_IPU_VQ_NUM, /* we support 2 virtqueue */ 0, /* config reg size */ virtio_ipu_reset, /* reset */ virtio_ipu_no_notify, /* device-wide qnotify */ NULL, /* read virtio config */ NULL, /* write virtio config */ NULL, /* apply negotiated features */ virtio_ipu_set_status, /* called on guest set status */ }; static int virtio_ipu_k_init(struct virtio_ipu *ipu) { if (ipu->vbs_k.ipu_fd != -1) { IPRINTF(LWRN, "Ooops! Re-entered!!\n"); return -VIRTIO_ERROR_REENTER; } ipu->vbs_k.ipu_fd = open(IPU_VBS_DEV_PATH, O_RDWR); if (ipu->vbs_k.ipu_fd < 0) { IPRINTF(LWRN, "Failed to open %s!\n", IPU_VBS_DEV_PATH); return -VIRTIO_ERROR_FD_OPEN_FAILED; } IPRINTF(LDBG, "Open %s success fd:%d!\n", IPU_VBS_DEV_PATH, ipu->vbs_k.ipu_fd); memset(&ipu->vbs_k.ipu_kdev, 0, sizeof(struct vbs_dev_info)); memset(&ipu->vbs_k.ipu_kvqs, 0, sizeof(struct vbs_dev_info)); return VIRTIO_SUCCESS; } static int virtio_ipu_k_dev_set(struct vbs_dev_info *ipu_kdev, const char *name, int vmid, int nvq, uint32_t feature, uint64_t pio_start, uint64_t pio_len) { /* init kdev */ strncpy(ipu_kdev->name, name, VBS_NAME_LEN); ipu_kdev->vmid = vmid; ipu_kdev->nvq = nvq; ipu_kdev->negotiated_features = feature; ipu_kdev->pio_range_start = pio_start; ipu_kdev->pio_range_len = pio_len; return VIRTIO_SUCCESS; } static int virtio_ipu_k_vq_set(struct vbs_vqs_info *ipu_kvqs, unsigned int nvq, unsigned int idx, uint16_t qsize, uint32_t pfn, uint16_t msix_idx, uint64_t msix_addr, uint32_t msix_data) { if (nvq <= idx) { IPRINTF(LWRN, "wrong idx for vq_set!\n"); return -VIRTIO_ERROR_GENERAL; } /* init kvqs */ ipu_kvqs->nvq = nvq; ipu_kvqs->vqs[idx].qsize = qsize; ipu_kvqs->vqs[idx].pfn = pfn; ipu_kvqs->vqs[idx].msix_idx = msix_idx; ipu_kvqs->vqs[idx].msix_addr = msix_addr; ipu_kvqs->vqs[idx].msix_data = msix_data; return VIRTIO_SUCCESS; } static int virtio_ipu_k_start(struct virtio_ipu *ipu) { if (vbs_kernel_start(ipu->vbs_k.ipu_fd, &ipu->vbs_k.ipu_kdev, &ipu->vbs_k.ipu_kvqs) < 0) { IPRINTF(LWRN, "Failed in vbs_kernel_start!\n"); return -VIRTIO_ERROR_START; } IPRINTF(LDBG, "vbs_kernel_started!\n"); return VIRTIO_SUCCESS; } static int virtio_ipu_k_stop(struct virtio_ipu *ipu) { return vbs_kernel_stop(ipu->vbs_k.ipu_fd); } static int virtio_ipu_k_reset(struct virtio_ipu *ipu) { memset(&ipu->vbs_k.ipu_kdev, 0, sizeof(struct vbs_dev_info)); memset(&ipu->vbs_k.ipu_kvqs, 0, sizeof(struct vbs_vqs_info)); return vbs_kernel_reset(ipu->vbs_k.ipu_fd); } static void virtio_ipu_reset(void *base) { struct virtio_ipu *ipu; ipu = (struct virtio_ipu *)base; IPRINTF(LDBG, "device reset requested !\n"); virtio_reset_dev(&ipu->base); if (ipu->vbs_k.ipu_kstatus == VIRTIO_DEV_STARTED) { virtio_ipu_k_stop(ipu); virtio_ipu_k_reset(ipu); ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_INIT_SUCCESS; } } /* VBS-K interface function implementations */ static void virtio_ipu_no_notify(void *base, struct virtio_vq_info *vq) { IPRINTF(LWRN, "VBS-K mode! Should not reach here!!\n"); } /* * This callback gives us a chance to determine the timings * to kickoff VBS-K initialization */ static void virtio_ipu_set_status(void *base, uint64_t status) { struct virtio_ipu *ipu; int nvq; struct msix_table_entry *mte; uint64_t msix_addr = 0; uint32_t msix_data = 0; int rc, i, j; IPRINTF(LDBG, "set_status status:%ld\n", status); ipu = (struct virtio_ipu *) base; nvq = ipu->base.vops->nvq; if (ipu->vbs_k.ipu_kstatus == VIRTIO_DEV_INIT_SUCCESS && (status & VIRTIO_CONFIG_S_DRIVER_OK)) { /* time to kickoff VBS-K side */ /* init vdev first */ rc = virtio_ipu_k_dev_set( &ipu->vbs_k.ipu_kdev, ipu->base.vops->name, ipu->base.dev->vmctx->vmid, nvq, ipu->base.negotiated_caps, /* currently we let VBS-K handle * kick register */ ipu->base.dev->bar[0].addr + 16, 2); for (i = 0; i < nvq; i++) { if (ipu->vq[i].msix_idx != VIRTIO_MSI_NO_VECTOR) { j = ipu->vq[i].msix_idx; mte = &ipu->base.dev->msix.table[j]; msix_addr = mte->addr; msix_data = mte->msg_data; } rc = virtio_ipu_k_vq_set( &ipu->vbs_k.ipu_kvqs, nvq, i, ipu->vq[i].qsize, ipu->vq[i].pfn, ipu->vq[i].msix_idx, msix_addr, msix_data); if (rc < 0) { IPRINTF(LWRN, "kernel_set_vq failed"); IPRINTF(LWRN, "i %d ret %d\n", i, rc); return; } } rc = virtio_ipu_k_start(ipu); if (rc < 0) { IPRINTF(LWRN, "kernel_start() failed\n"); ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_START_FAILED; } else { ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_STARTED; } } } static int virtio_ipu_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts) { struct virtio_ipu *ipu; pthread_mutexattr_t attr; int rc; ipu = calloc(1, sizeof(struct virtio_ipu)); if (!ipu) { IPRINTF(LWRN, "calloc returns NULL\n"); return -1; } ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_INITIAL; ipu->vbs_k.ipu_fd = -1; /* init mutex attribute properly */ rc = pthread_mutexattr_init(&attr); if (rc) IPRINTF(LDBG, "mutexattr init failed with erro %d!\n", rc); if (virtio_uses_msix()) { rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); IPRINTF(LDBG, "mutexattr_settype failed with error %d!\n", rc); } else { rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); IPRINTF(LDBG, "mutexattr_settype failed with error %d!\n", rc); } rc = pthread_mutex_init(&ipu->mtx, &attr); if (rc) IPRINTF(LDBG, "mutex init failed with error %d!\n", rc); virtio_linkup(&ipu->base, &virtio_ipu_ops_k, ipu, dev, ipu->vq, BACKEND_VBSK); rc = virtio_ipu_k_init(ipu); if (rc < 0) { IPRINTF(LWRN, "VBS-K init failed with error %d!\n", rc); ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_INIT_FAILED; pthread_mutex_destroy(&ipu->mtx); free(ipu); return -1; } ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_INIT_SUCCESS; ipu->base.mtx = &ipu->mtx; ipu->vq[0].qsize = VIRTIO_IPU_RINGSZ; ipu->vq[1].qsize = VIRTIO_IPU_RINGSZ; /* initialize config space */ pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_IPU); pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID); pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_MEMORY); pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_IPU); pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID); if (virtio_interrupt_init(&ipu->base, virtio_uses_msix())) { pthread_mutex_destroy(&ipu->mtx); close(ipu->vbs_k.ipu_fd); free(ipu); return -1; } virtio_set_io_bar(&ipu->base, 0); return 0; } static void virtio_ipu_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts) { struct virtio_ipu *ipu; ipu = (struct virtio_ipu *)dev->arg; if (!ipu) { IPRINTF(LDBG, "is NULL!\n"); return; } if (ipu->vbs_k.ipu_kstatus == VIRTIO_DEV_STARTED) { IPRINTF(LDBG, "deinitializing\n"); virtio_ipu_k_stop(ipu); virtio_ipu_k_reset(ipu); ipu->vbs_k.ipu_kstatus = VIRTIO_DEV_INITIAL; if (ipu->vbs_k.ipu_fd >= 0) close(ipu->vbs_k.ipu_fd); ipu->vbs_k.ipu_fd = -1; } virtio_ipu_reset(ipu); pthread_mutex_destroy(&ipu->mtx); free(ipu); } struct pci_vdev_ops pci_ops_virtio_ipu = { .class_name = "virtio-ipu", .vdev_init = virtio_ipu_init, .vdev_deinit = virtio_ipu_deinit, .vdev_barwrite = virtio_pci_write, .vdev_barread = virtio_pci_read }; DEFINE_PCI_DEVTYPE(pci_ops_virtio_ipu);