/* * Copyright (C) 2018 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause * */ /* * virtio audio * audio mediator device model */ #include #include #include #include #include #include #include #include #include #include "dm.h" #include "pci_core.h" #include "virtio.h" #include "virtio_kernel.h" #include "vmmapi.h" /* for vmctx */ #include "log.h" /* * Size of queue was chosen experimentaly in a way * that it allows to do audio capture/playback without * any delay for interrupt/msg send, this value should * be tuned. */ #define VIRTIO_AUDIO_RINGSZ 1024 /* * Queue definitions. * Audio mediator uses two queues: one for interrupt and the other for messages. */ #define VIRTIO_AUDIO_VQ_NUM 4 /*4 currently we use 4 vq, may change later*/ const char *vbs_k_audio_dev_path = "/dev/vbs_k_audio"; static int virtio_audio_debug = 1; #define DPRINTF(params) do { if (virtio_audio_debug) pr_dbg params; } while (0) #define WPRINTF(params) (pr_err params) struct virtio_audio { struct virtio_base base; struct virtio_vq_info vq[VIRTIO_AUDIO_VQ_NUM]; pthread_mutex_t mtx; /* VBS-K variables */ struct { enum VBS_K_STATUS kstatus; int audio_fd; struct vbs_dev_info kdev; struct vbs_vqs_info kvqs; } vbs_k; }; static int virtio_audio_kernel_start(struct virtio_audio *virt_audio); static int virtio_audio_kernel_stop(struct virtio_audio *virt_audio); static int virtio_audio_kernel_reset(struct virtio_audio *virt_audio); static int virtio_audio_kernel_dev_set(struct vbs_dev_info *kdev, const char *name, int vmid, int nvq, uint32_t feature, uint64_t pio_start, uint64_t pio_len); static int virtio_audio_kernel_vq_set(struct vbs_vqs_info *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_audio_k_no_notify(void *base, struct virtio_vq_info *vq); static void virtio_audio_k_set_status(void *base, uint64_t status); static void virtio_audio_reset(void *base); static struct virtio_ops virtio_audio_ops_k = { "virtio_audio", /* our name */ VIRTIO_AUDIO_VQ_NUM, /* we support 4 virtqueue */ 0, /* config reg size */ virtio_audio_reset, /* reset */ virtio_audio_k_no_notify, /* device-wide qnotify */ NULL, /* read virtio config */ NULL, /* write virtio config */ NULL, /* apply negotiated features */ virtio_audio_k_set_status,/* called on guest set status */ }; static int virtio_audio_kernel_init(struct virtio_audio *virt_audio) { if (virt_audio->vbs_k.audio_fd != -1) { WPRINTF(("virtio_audio: Ooops! Re-entered!!\n")); return -VIRTIO_ERROR_REENTER; } virt_audio->vbs_k.audio_fd = open(vbs_k_audio_dev_path, O_RDWR); if (virt_audio->vbs_k.audio_fd < 0) { WPRINTF(("virtio_audio: Failed to open %s!\n", vbs_k_audio_dev_path)); return -VIRTIO_ERROR_FD_OPEN_FAILED; } DPRINTF(("virtio_audio: Open %s success!\n", vbs_k_audio_dev_path)); memset(&virt_audio->vbs_k.kdev, 0, sizeof(struct vbs_dev_info)); memset(&virt_audio->vbs_k.kvqs, 0, sizeof(struct vbs_vqs_info)); return VIRTIO_SUCCESS; } static int virtio_audio_kernel_dev_set(struct vbs_dev_info *kdev, const char *name, int vmid, int nvq, uint32_t feature, uint64_t pio_start, uint64_t pio_len) { /* init kdev */ strncpy(kdev->name, name, VBS_NAME_LEN); kdev->name[VBS_NAME_LEN - 1] = '\0'; kdev->vmid = vmid; kdev->nvq = nvq; kdev->negotiated_features = feature; kdev->pio_range_start = pio_start; kdev->pio_range_len = pio_len; return VIRTIO_SUCCESS; } static int virtio_audio_kernel_vq_set(struct vbs_vqs_info *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) { WPRINTF(("virtio_audio: wrong idx for vq_set!\n")); return -VIRTIO_ERROR_GENERAL; } /* init kvqs */ kvqs->nvq = nvq; kvqs->vqs[idx].qsize = qsize; kvqs->vqs[idx].pfn = pfn; kvqs->vqs[idx].msix_idx = msix_idx; kvqs->vqs[idx].msix_addr = msix_addr; kvqs->vqs[idx].msix_data = msix_data; return VIRTIO_SUCCESS; } static int virtio_audio_kernel_start(struct virtio_audio *virt_audio) { if (vbs_kernel_start(virt_audio->vbs_k.audio_fd, &virt_audio->vbs_k.kdev, &virt_audio->vbs_k.kvqs) < 0) { WPRINTF(("virtio_audio: Failed in vbs_k_start!\n")); return -VIRTIO_ERROR_START; } DPRINTF(("virtio_audio: vbs_k_started!\n")); return VIRTIO_SUCCESS; } static int virtio_audio_kernel_stop(struct virtio_audio *virt_audio) { return vbs_kernel_stop(virt_audio->vbs_k.audio_fd); } static int virtio_audio_kernel_reset(struct virtio_audio *virt_audio) { memset(&virt_audio->vbs_k.kdev, 0, sizeof(struct vbs_dev_info)); memset(&virt_audio->vbs_k.kvqs, 0, sizeof(struct vbs_vqs_info)); return vbs_kernel_reset(virt_audio->vbs_k.audio_fd); } static void virtio_audio_reset(void *base) { struct virtio_audio *virt_audio; virt_audio = (struct virtio_audio *)base; DPRINTF(("virtio_audio: device reset requested !\n")); virtio_reset_dev(&virt_audio->base); DPRINTF(("virtio_audio: kstatus %d\n", virt_audio->vbs_k.kstatus)); if (virt_audio->vbs_k.kstatus == VIRTIO_DEV_STARTED) { DPRINTF(("virtio_audio: VBS-K reset requested!\n")); virtio_audio_kernel_stop(virt_audio); virtio_audio_kernel_reset(virt_audio); virt_audio->vbs_k.kstatus = VIRTIO_DEV_INIT_SUCCESS; } } /* VBS-K interface function implementations */ static void virtio_audio_k_no_notify(void *base, struct virtio_vq_info *vq) { WPRINTF(("virtio_audio: 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_audio_k_set_status(void *base, uint64_t status) { struct virtio_audio *virt_audio; int nvq; struct msix_table_entry *mte; uint64_t msix_addr = 0; uint32_t msix_data = 0; int rc, i, j; virt_audio = (struct virtio_audio *)base; nvq = virt_audio->base.vops->nvq; if (virt_audio->vbs_k.kstatus == VIRTIO_DEV_INIT_SUCCESS && (status & VIRTIO_CONFIG_S_DRIVER_OK)) { /* time to kickoff VBS-K side */ /* init vdev first */ rc = virtio_audio_kernel_dev_set( &virt_audio->vbs_k.kdev, virt_audio->base.vops->name, virt_audio->base.dev->vmctx->vmid, nvq, virt_audio->base.negotiated_caps, /* currently we let VBS-K handle * kick register * * FIXME: the size should be returned * by a api in vhost. */ virt_audio->base.dev->bar[0].addr + 16, 2); for (i = 0; i < nvq; i++) { if (virt_audio->vq[i].msix_idx != VIRTIO_MSI_NO_VECTOR) { j = virt_audio->vq[i].msix_idx; mte = &virt_audio->base.dev->msix.table[j]; msix_addr = mte->addr; msix_data = mte->msg_data; } rc = virtio_audio_kernel_vq_set( &virt_audio->vbs_k.kvqs, nvq, i, virt_audio->vq[i].qsize, virt_audio->vq[i].pfn, virt_audio->vq[i].msix_idx, msix_addr, msix_data); if (rc < 0) { WPRINTF(("audio: kernel_set_vq failed, " "i %d ret %d\n", i, rc)); return; } } rc = virtio_audio_kernel_start(virt_audio); if (rc < 0) { WPRINTF(("virtio_audio: kernel_start() failed\n")); virt_audio->vbs_k.kstatus = VIRTIO_DEV_START_FAILED; } else { virt_audio->vbs_k.kstatus = VIRTIO_DEV_STARTED; } } } static int virtio_audio_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts) { struct virtio_audio *virt_audio; pthread_mutexattr_t attr; int rc; virt_audio = calloc(1, sizeof(struct virtio_audio)); if (!virt_audio) { WPRINTF(("virtio_audio: calloc returns NULL\n")); return -1; } virt_audio->vbs_k.kstatus = VIRTIO_DEV_INITIAL; virt_audio->vbs_k.audio_fd = -1; /* init mutex attribute properly */ rc = pthread_mutexattr_init(&attr); if (rc) DPRINTF(("mutexattr init failed with erro %d!\n", rc)); if (virtio_uses_msix()) { rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); if (rc) DPRINTF(("virtio_msix: mutexattr_settype " "failed with error %d!\n", rc)); } else { rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); if (rc) DPRINTF(("virtio_intx: mutexattr_settype " "failed with error %d!\n", rc)); } rc = pthread_mutex_init(&virt_audio->mtx, &attr); if (rc) DPRINTF(("mutex init failed with error %d!\n", rc)); virtio_linkup(&virt_audio->base, &virtio_audio_ops_k, virt_audio, dev, virt_audio->vq, BACKEND_VBSK); rc = virtio_audio_kernel_init(virt_audio); if (rc < 0) { WPRINTF(("virtio_audio: VBS-K init failed,error %d!\n", rc)); virt_audio->vbs_k.kstatus = VIRTIO_DEV_INIT_FAILED; free(virt_audio); return -1; } virt_audio->vbs_k.kstatus = VIRTIO_DEV_INIT_SUCCESS; virt_audio->base.mtx = &virt_audio->mtx; /* vq[0] and vq[1] are for interrupt and messages */ virt_audio->vq[0].qsize = VIRTIO_AUDIO_RINGSZ; virt_audio->vq[1].qsize = VIRTIO_AUDIO_RINGSZ; virt_audio->vq[2].qsize = VIRTIO_AUDIO_RINGSZ; virt_audio->vq[3].qsize = VIRTIO_AUDIO_RINGSZ; /* initialize config space */ pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_AUDIO); pci_set_cfgdata16(dev, PCIR_VENDOR, INTEL_VENDOR_ID); pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_MULTIMEDIA); pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_MULTIMEDIA_AUDIO); pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_AUDIO); pci_set_cfgdata16(dev, PCIR_SUBVEND_0, INTEL_VENDOR_ID); if (virtio_interrupt_init(&virt_audio->base, virtio_uses_msix())) { free(virt_audio); return -1; } virtio_set_io_bar(&virt_audio->base, 0); return 0; } static void virtio_audio_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts) { struct virtio_audio *virt_audio; virt_audio = dev->arg; if (!virt_audio) { DPRINTF(("%s: virtio_audio is NULL!\n", __func__)); return; } if (virt_audio->vbs_k.kstatus == VIRTIO_DEV_STARTED) { DPRINTF(("%s: deinit virtio_audio_k!\n", __func__)); virtio_audio_kernel_stop(virt_audio); virtio_audio_kernel_reset(virt_audio); virt_audio->vbs_k.kstatus = VIRTIO_DEV_INITIAL; if (virt_audio->vbs_k.audio_fd < 0) { WPRINTF(("virtio_audio: %s doesn't open!\n", vbs_k_audio_dev_path)); return; } close(virt_audio->vbs_k.audio_fd); virt_audio->vbs_k.audio_fd = -1; } pthread_mutex_destroy(&virt_audio->mtx); DPRINTF(("%s: free struct virtio_audio!\n", __func__)); free((struct virtio_audio *)dev->arg); } struct pci_vdev_ops pci_ops_virtio_audio = { .class_name = "virtio-audio", .vdev_init = virtio_audio_init, .vdev_deinit = virtio_audio_deinit, .vdev_barwrite = virtio_pci_write, .vdev_barread = virtio_pci_read }; DEFINE_PCI_DEVTYPE(pci_ops_virtio_audio);