From c82551999b6379147635aec8f5fa8af158a75be9 Mon Sep 17 00:00:00 2001 From: Zhu Yingjiang Date: Tue, 26 Jun 2018 09:59:35 +0800 Subject: [PATCH] audio mediator device model The device model is a userspace application on SOS to config the PCI devices for the UOS. Audio mediator device model is to config the virtual audio PCI device. Signed-off-by: Zhu Yingjiang --- devicemodel/hw/pci/virtio/virtio_audio.c | 385 +++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 devicemodel/hw/pci/virtio/virtio_audio.c diff --git a/devicemodel/hw/pci/virtio/virtio_audio.c b/devicemodel/hw/pci/virtio/virtio_audio.c new file mode 100644 index 000000000..455b26077 --- /dev/null +++ b/devicemodel/hw/pci/virtio/virtio_audio.c @@ -0,0 +1,385 @@ +/* + * 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 + +#include "dm.h" +#include "pci_core.h" +#include "virtio.h" +#include "virtio_kernel.h" +#include "vmmapi.h" /* for vmctx */ + +/* + * 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 2 + +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) printf params; } while (0) +#define WPRINTF(params) (printf 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 2 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 */ + 0, /* our capabilities */ +}; + +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->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_INITIAL; + } +} + +/* 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_CR_STATUS_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); + + 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; + + /* initialize config space */ + pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_AUDIO); + pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR); + 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, VIRTIO_VENDOR); + + 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; + assert(virt_audio->vbs_k.audio_fd >= 0); + 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);