clear-pkgs-linux-iot-lts2018/0750-ASoC-Intel-Skylake-Vir...

3317 lines
84 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Pawel Furtak <pawel.furtak@intel.com>
Date: Sun, 25 Nov 2018 18:05:54 +0100
Subject: [PATCH] ASoC: Intel: Skylake: Virt: Add virtualization layer to skl
driver
This patch adds initial draft of virtualization feature for
Skylake sound driver. Chosen virtualization method is API Forwarding.
Virtualization layer is designed to work with ACRN Hypervisor and is
implemented using VirtIO mechanisms. It consists of two main parts,
FrontEnd and BackEnd.
BackEnd service, residing on Host OS, forwards FE driver requests
to native driver.
FrontEnd service, residing on Guest OS, replaces all components
related to hardware (HDA bus, DSP, PCI) and instead of direct access
all request are sent to BE service using VirtIO queues.
Change-Id: Ic83b1906d2bf42474c30a434e30397f78cc454d2
Signed-off-by: Pawel Furtak <pawel.furtak@intel.com>
Tracked-On: OAM-72189
Reviewed-by: Rojewski, Cezary <cezary.rojewski@intel.com>
Tested-by: Rojewski, Cezary <cezary.rojewski@intel.com>
---
sound/soc/intel/skylake/skl.h | 3 +
sound/soc/intel/skylake/virtio/Makefile | 10 +
.../soc/intel/skylake/virtio/skl-virtio-be.c | 815 ++++++++++++++++
.../soc/intel/skylake/virtio/skl-virtio-be.h | 73 ++
.../intel/skylake/virtio/skl-virtio-card.c | 370 +++++++
.../intel/skylake/virtio/skl-virtio-common.h | 153 +++
.../soc/intel/skylake/virtio/skl-virtio-fe.c | 920 ++++++++++++++++++
.../soc/intel/skylake/virtio/skl-virtio-fe.h | 61 ++
.../intel/skylake/virtio/skl-virtio-miscdev.c | 442 +++++++++
.../soc/intel/skylake/virtio/skl-virtio-sst.c | 311 ++++++
sound/soc/intel/skylake/virtio/skl-virtio.h | 35 +
11 files changed, 3193 insertions(+)
create mode 100644 sound/soc/intel/skylake/virtio/Makefile
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-be.c
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-be.h
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-card.c
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-common.h
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-fe.c
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-fe.h
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-miscdev.c
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio-sst.c
create mode 100644 sound/soc/intel/skylake/virtio/skl-virtio.h
diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h
index 94cabe5b8afa..ef02c70035b6 100644
--- a/sound/soc/intel/skylake/skl.h
+++ b/sound/soc/intel/skylake/skl.h
@@ -159,6 +159,9 @@ struct skl {
bool nhlt_override;
bool mod_set_get_status;
struct ep_group_cnt grp_cnt;
+
+ /* list of virtual BE services */
+ struct list_head vbe_list;
};
#define skl_to_bus(s) (&(s)->hbus)
diff --git a/sound/soc/intel/skylake/virtio/Makefile b/sound/soc/intel/skylake/virtio/Makefile
new file mode 100644
index 000000000000..f9b693c44405
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/Makefile
@@ -0,0 +1,10 @@
+snd-soc-skl-virtio-sst-objs := virtio/skl-virtio-sst.o
+snd-soc-skl-virtio-card-objs := virtio/skl-virtio-card.o
+snd-soc-skl-virtio-fe-objs := virtio/skl-virtio-fe.o
+
+obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_VIRTIO_FE) := snd-soc-skl-virtio-sst.o \
+snd-soc-skl-virtio-fe.o snd-soc-skl-virtio-card.o
+
+ifdef CONFIG_SND_SOC_INTEL_SKYLAKE_VIRTIO_BE
+snd-soc-skl-objs += virtio/skl-virtio-be.o virtio/skl-virtio-miscdev.o
+endif
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-be.c b/sound/soc/intel/skylake/virtio/skl-virtio-be.c
new file mode 100644
index 000000000000..55a213575230
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-be.c
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// skl-virtio-be.c -- Virtio BE service for SKL architecture
+//
+// Copyright (C) 2018 Intel Corporation.
+//
+// Authors: Furtak, Pawel <pawel.furtak@intel.com>
+// Janca, Grzegorz <grzegorz.janca@intel.com>
+//
+// BE receives commands from FE drivers and forward them to appropriate
+// entity, such as DSP, PCM or sound card control. BE sends buffer position
+// updates to FE driver.
+
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/hw_random.h>
+#include <linux/uio.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/device.h>
+#include <sound/pcm_params.h>
+#include <linux/vbs/vq.h>
+#include <linux/vbs/vbs.h>
+#include <linux/vhm/acrn_common.h>
+#include <linux/vhm/acrn_vhm_ioreq.h>
+#include <linux/vhm/acrn_vhm_mm.h>
+#include <linux/vhm/vhm_vm_mngt.h>
+#include <linux/spinlock.h>
+#include "skl-virtio-be.h"
+#include "../skl.h"
+#include "../skl-sst-ipc.h"
+
+const struct vbe_substream_info *vbe_find_substream_info_by_pcm(
+ const struct snd_skl_vbe *vbe, char *pcm_id, int direction)
+{
+ const struct vbe_substream_info *info;
+
+ list_for_each_entry(info, &vbe->substr_info_list, list) {
+ if (info->direction == direction &&
+ strncmp(info->pcm->id, pcm_id,
+ ARRAY_SIZE(info->pcm->id)) == 0)
+ return info;
+ }
+ return NULL;
+}
+
+inline const struct vbe_substream_info *vbe_find_substream_info(
+ const struct snd_skl_vbe *vbe, const struct snd_pcm_substream *substr)
+{
+ return vbe_find_substream_info_by_pcm(vbe, substr->pcm->id,
+ substr->stream);
+}
+
+static const struct vbe_substream_info *vbe_skl_find_substream_info(
+ const struct skl *sdev, const struct snd_pcm_substream *substr)
+{
+ const struct snd_skl_vbe *vbe;
+ const struct vbe_substream_info *info;
+
+ list_for_each_entry(vbe, &sdev->vbe_list, list) {
+ info = vbe_find_substream_info(vbe, substr);
+ if (info != NULL)
+ return info;
+ }
+ return NULL;
+}
+
+static const struct snd_kcontrol *vbe_skl_find_kcontrol_by_name(
+ const struct skl *skl, char *kcontrol_name)
+{
+ const struct snd_kcontrol *kcontrol;
+
+ list_for_each_entry(
+ kcontrol, &skl->component->card->snd_card->controls, list) {
+ if (strncmp(kcontrol->id.name, kcontrol_name,
+ ARRAY_SIZE(kcontrol->id.name)) == 0)
+ return kcontrol;
+ }
+ return NULL;
+}
+
+inline int vbe_skl_is_valid_pcm_id(char *pcm_id)
+{
+ if (pcm_id == NULL || strlen(pcm_id) == 0 ||
+ strcmp(pcm_id, "((null))") == 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct snd_soc_pcm_runtime *vbe_skl_find_rtd_by_pcm_id(
+ const struct skl *skl, char *pcm_name)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ int ret = vbe_skl_is_valid_pcm_id(pcm_name);
+
+ if (ret < 0)
+ return NULL;
+
+ list_for_each_entry(rtd, &skl->component->card->rtd_list, list) {
+ if (strncmp(rtd->pcm->id, pcm_name,
+ ARRAY_SIZE(rtd->pcm->id)) == 0)
+ return rtd;
+ }
+ return NULL;
+}
+
+const struct snd_pcm *vbe_skl_find_pcm_by_name(struct skl *skl, char *pcm_name)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+
+ if (!strlen(pcm_name))
+ return NULL;
+
+ rtd = vbe_skl_find_rtd_by_pcm_id(skl, pcm_name);
+
+ return rtd ? rtd->pcm : NULL;
+}
+
+static bool vbe_skl_fill_posn_vqbuf(const struct snd_skl_vbe *vbe,
+ const struct virtio_vq_info *vq,
+ const struct vfe_hw_pos_request *posn)
+{
+ const struct device *dev = vbe->dev;
+ const struct iovec iov;
+ u16 idx;
+ int ret;
+
+ if (virtio_vq_has_descs(vq)) {
+ ret = virtio_vq_getchain(vq, &idx, &iov, 1, NULL);
+ if (ret <= 0)
+ return false;
+
+ if (iov.iov_len < sizeof(const struct vfe_hw_pos_request)) {
+ dev_err(dev, "iov len %lu, expecting len %lu\n",
+ iov.iov_len, sizeof(*posn));
+ virtio_vq_relchain(vq, idx, iov.iov_len);
+ }
+ memcpy(iov.iov_base, posn, sizeof(*posn));
+ virtio_vq_relchain(vq, idx, iov.iov_len);
+ return true;
+ }
+ return false;
+}
+
+void skl_notify_stream_update(struct hdac_bus *bus,
+ struct snd_pcm_substream *substream)
+{
+ const struct skl *skl = bus_to_skl(bus);
+ const struct vbe_substream_info *substr_info;
+ const struct snd_soc_pcm_runtime *rtd;
+ struct vfe_hw_pos_request pos_req, *hw_pos_req;
+ const struct snd_skl_vbe *vbe;
+ const struct virtio_vq_info *vq;
+ bool endchain;
+
+ substr_info = vbe_skl_find_substream_info(skl, substream);
+ if (!substr_info)
+ return;
+
+ rtd = substream->private_data;
+
+ pos_req.stream_dir = substr_info->direction;
+ pos_req.stream_pos = rtd->ops.pointer(substream);
+ strncpy(pos_req.pcm_id, substream->pcm->id,
+ ARRAY_SIZE(substream->pcm->id));
+
+ vbe = substr_info->vbe;
+ vq = &vbe->vqs[SKL_VIRTIO_IPC_NOT_RX_VQ];
+
+ /*
+ * let's try to get a notification RX vq available buffer
+ * If there is an available buffer, let's notify immediately
+ */
+ endchain = vbe_skl_fill_posn_vqbuf(vbe, vq, &pos_req);
+ if (endchain == true) {
+ virtio_vq_endchains(vq, true);
+ return;
+ }
+
+ hw_pos_req = kzalloc(sizeof(*hw_pos_req), GFP_KERNEL);
+ if (!hw_pos_req)
+ return;
+
+ *hw_pos_req = pos_req;
+ list_add_tail(&hw_pos_req->list, &vbe->posn_list);
+
+ if (endchain == true)
+ virtio_vq_endchains(vq, true);
+}
+
+int vbe_skl_allocate_runtime(const struct snd_soc_card *card,
+ struct snd_pcm_substream *substream)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ struct snd_pcm_runtime *runtime;
+ int size;
+
+ runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
+ if (!runtime)
+ return -ENOMEM;
+
+ size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status));
+ runtime->status = snd_malloc_pages(size, GFP_KERNEL);
+ if (!runtime->status)
+ goto alloc_free;
+
+ memset((void *)runtime->status, 0, size);
+
+ size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control));
+ runtime->control = snd_malloc_pages(size, GFP_KERNEL);
+ if (!runtime->control) {
+ snd_free_pages((void *)runtime->status,
+ PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)));
+ goto alloc_free;
+ }
+ memset((void *)runtime->control, 0, size);
+
+ init_waitqueue_head(&runtime->sleep);
+ init_waitqueue_head(&runtime->tsleep);
+ runtime->status->state = SNDRV_PCM_STATE_OPEN;
+
+ substream->runtime = runtime;
+
+ list_for_each_entry(rtd, &card->rtd_list, list) {
+ if (strcmp(rtd->pcm->id, substream->pcm->id) == 0) {
+ substream->private_data = rtd;
+ break;
+ }
+ }
+ return 0;
+alloc_free:
+ kfree(runtime);
+ return -ENOMEM;
+}
+
+void vbe_skl_initialize_substream_runtime(struct snd_pcm_runtime *runtime,
+ struct snd_pcm_hw_params *params)
+{
+ int bits;
+ int frames;
+
+ runtime->access = params_access(params);
+ runtime->format = params_format(params);
+ runtime->subformat = params_subformat(params);
+ runtime->channels = params_channels(params);
+ runtime->rate = params_rate(params);
+ runtime->period_size = params_period_size(params);
+ runtime->periods = params_periods(params);
+ runtime->buffer_size = params_buffer_size(params);
+ runtime->info = params->info;
+ runtime->rate_num = params->rate_num;
+ runtime->rate_den = params->rate_den;
+ runtime->no_period_wakeup =
+ (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
+ (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
+ runtime->no_rewinds =
+ (params->flags & SNDRV_PCM_HW_PARAMS_NO_REWINDS) ? 1 : 0;
+ bits = snd_pcm_format_physical_width(runtime->format);
+ runtime->sample_bits = bits;
+ bits *= runtime->channels;
+ runtime->frame_bits = bits;
+ frames = 1;
+ while (bits % 8 != 0) {
+ bits *= 2;
+ frames *= 2;
+ }
+ runtime->byte_align = bits / 8;
+ runtime->min_align = frames;
+ /* Default sw params */
+ runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
+ runtime->period_step = 1;
+ runtime->control->avail_min = runtime->period_size;
+ runtime->start_threshold = 1;
+ runtime->stop_threshold = runtime->buffer_size;
+ runtime->silence_threshold = 0;
+ runtime->silence_size = 0;
+ runtime->boundary = runtime->buffer_size << 4;
+}
+
+static int vbe_skl_prepare_dma(const struct snd_pcm_substream *substream,
+ int vm_id, const struct vfe_pcm_dma_conf *dma_conf)
+{
+ const struct snd_sg_buf *sg_buf;
+ int cnt;
+ u64 pcm_buffer_gpa = dma_conf->addr & ~(u64)0xfff;
+ u64 pcm_buffer_hpa = vhm_vm_gpa2hpa(vm_id, pcm_buffer_gpa);
+
+ if (!pcm_buffer_hpa)
+ return -EINVAL;
+
+ sg_buf = snd_pcm_substream_sgbuf(substream);
+ if (!sg_buf)
+ return -EINVAL;
+
+ sg_buf->table[0].addr = pcm_buffer_hpa | 0x10;
+ for (cnt = 1; cnt < sg_buf->pages; cnt++) {
+ pcm_buffer_hpa += PAGE_SIZE;
+ sg_buf->table[cnt].addr = pcm_buffer_hpa;
+ }
+
+ return 0;
+}
+
+static int vbe_skl_assemble_params(struct vfe_pcm_hw_params *vfe_params,
+ const struct snd_pcm_hw_params *params)
+{
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_ACCESS)->min =
+ vfe_params->access;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min =
+ vfe_params->channels;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min =
+ vfe_params->rate;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES)->min =
+ vfe_params->host_period_bytes;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE)->min =
+ vfe_params->buffer_size;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)->min =
+ vfe_params->buffer_bytes;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE)->min =
+ vfe_params->period_size;
+
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIODS)->min =
+ vfe_params->periods;
+
+ params_set_format(params, vfe_params->frame_fmt);
+
+ return 0;
+}
+
+static int vbe_skl_add_substream_info(struct snd_skl_vbe *vbe,
+ const struct snd_pcm_substream *substream)
+{
+ struct vbe_substream_info *substr_info =
+ kzalloc(sizeof(*substr_info), GFP_KERNEL);
+
+ if (!substr_info)
+ return -ENOMEM;
+
+ substr_info->pcm = substream->pcm;
+ substr_info->substream = substream;
+ substr_info->direction = substream->stream;
+ substr_info->vbe = vbe;
+
+ list_add(&substr_info->list, &vbe->substr_info_list);
+ return 0;
+}
+
+static int vbe_skl_pcm_open(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev,
+ int vm_id, const struct vbe_ipc_msg *msg)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ struct snd_pcm_substream *substream;
+ const struct snd_pcm_runtime *runtime;
+ int ret;
+ struct vfe_pcm_result *vbe_result = msg->rx_data;
+ const struct vfe_pcm_info *pcm_desc = &msg->header->desc.pcm;
+ const struct snd_pcm *pcm =
+ vbe_skl_find_pcm_by_name(vbe->sdev, pcm_desc->pcm_id);
+ int direction = pcm_desc->direction;
+
+ if (!pcm) {
+ dev_err(&sdev->pci->dev, "Can not find PCM [%s].\n",
+ pcm_desc->pcm_id);
+ return -ENODEV;
+ }
+
+ substream = pcm->streams[direction].substream;
+ runtime = substream->runtime;
+
+ if (substream->ref_count > 0)
+ return -EBUSY;
+
+ ret = vbe_skl_allocate_runtime(sdev->component->card, substream);
+ if (ret < 0)
+ return ret;
+ ret = vbe_skl_add_substream_info(vbe, substream);
+ if (ret < 0)
+ return ret;
+ substream->ref_count++; /* set it used */
+
+ rtd = substream->private_data;
+ ret = rtd->ops.open(substream);
+
+ if (vbe_result)
+ vbe_result->ret = ret;
+
+ return ret;
+}
+
+static int vbe_skl_pcm_close(const struct skl *sdev, int vm_id,
+ const struct vbe_substream_info *substr_info,
+ const struct vbe_ipc_msg *msg)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ int ret;
+ struct snd_pcm_substream *substream = substr_info->substream;
+ struct vfe_pcm_result *vbe_result = msg->rx_data;
+
+ list_del(&substr_info->list);
+ kfree(substr_info);
+
+ substream->ref_count = 0;
+ rtd = substream->private_data;
+ ret = rtd->ops.close(substream);
+
+ if (vbe_result)
+ vbe_result->ret = ret;
+
+ return ret;
+}
+
+static int vbe_skl_pcm_prepare(const struct skl *sdev, int vm_id,
+ const struct vbe_substream_info *substr_info,
+ const struct vbe_ipc_msg *msg)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ int ret;
+ const struct snd_pcm_substream *substream = substr_info->substream;
+ const struct vfe_pcm_dma_conf *dma_params = msg->tx_data;
+ struct vfe_pcm_result *vbe_result = msg->rx_data;
+
+ ret = vbe_skl_prepare_dma(substream, vm_id, dma_params);
+ if (ret < 0)
+ return ret;
+
+ rtd = substream->private_data;
+ ret = rtd->ops.prepare(substream);
+
+ if (vbe_result)
+ vbe_result->ret = ret;
+
+ return ret;
+}
+
+struct snd_pcm_hw_params hw_params;
+
+static int vbe_skl_pcm_hw_params(const struct skl *sdev, int vm_id,
+ const struct vbe_substream_info *substr_info,
+ const struct vbe_ipc_msg *msg)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ int ret;
+ const struct snd_pcm_substream *substream = substr_info->substream;
+ //TODO: check if tx and rx data have expected size
+ const struct vfe_pcm_hw_params *hw_params_ipc = msg->tx_data;
+ struct vfe_pcm_result *vbe_result = msg->rx_data;
+
+ vbe_skl_assemble_params(hw_params_ipc, &hw_params);
+ vbe_skl_initialize_substream_runtime(substream->runtime, &hw_params);
+
+ rtd = substream->private_data;
+ ret = rtd->ops.hw_params(substream, &hw_params);
+
+ if (vbe_result)
+ vbe_result->ret = ret;
+
+ return ret;
+}
+
+static int vbe_skl_pcm_trigger(struct skl *sdev, int vm_id,
+ const struct vbe_substream_info *substr_info,
+ const struct vbe_ipc_msg *msg)
+{
+ const struct snd_soc_pcm_runtime *rtd;
+ const struct snd_pcm_substream *substream = substr_info->substream;
+ int cmd = *(int *)msg->tx_data;
+
+ rtd = substream->private_data;
+ return rtd->ops.trigger(substream, cmd);
+}
+
+static int vbe_skl_kcontrol_put(const struct skl *sdev, int vm_id,
+ const struct snd_kcontrol *kcontrol,
+ const struct vbe_ipc_msg *msg)
+{
+ const struct vfe_kctl_value *kcontrol_val =
+ (struct vfe_kctl_value *)msg->tx_data;
+
+ if (kcontrol->put)
+ return kcontrol->put(kcontrol, &kcontrol_val->value);
+
+ return 0;
+}
+
+static int vbe_skl_cfg_hda(const struct skl *sdev, int vm_id,
+ const struct vbe_ipc_msg *msg)
+{
+ const struct hdac_bus *bus = &sdev->hbus;
+ struct vfe_hda_cfg *hda_cfg = msg->rx_data;
+ unsigned short gcap;
+
+ if (!hda_cfg || msg->rx_size != sizeof(*hda_cfg))
+ return -EINVAL;
+
+ hda_cfg->resource_length = pci_resource_len(sdev->pci, 0);
+ gcap = snd_hdac_chip_readw(bus, GCAP);
+
+ hda_cfg->cp_streams = (gcap >> 8) & 0x0f;
+ hda_cfg->pb_streams = (gcap >> 12) & 0x0f;
+
+ hda_cfg->ppcap = bus->ppcap ? bus->ppcap - bus->remap_addr : 0;
+ hda_cfg->spbcap = bus->spbcap ? bus->spbcap - bus->remap_addr : 0;
+ hda_cfg->mlcap = bus->mlcap ? bus->mlcap - bus->remap_addr : 0;
+ hda_cfg->gtscap = bus->gtscap ? bus->gtscap - bus->remap_addr : 0;
+ hda_cfg->drsmcap = bus->drsmcap ? bus->drsmcap - bus->remap_addr : 0;
+
+ return 0;
+}
+
+static int vbe_skl_msg_kcontrol_handle(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev,
+ int vm_id, const struct vbe_ipc_msg *msg)
+{
+ const struct vfe_kctl_info *kctl_desc = &msg->header->desc.kcontrol;
+ const struct snd_kcontrol *kcontrol =
+ vbe_skl_find_kcontrol_by_name(sdev, kctl_desc->kcontrol_id);
+
+ if (!kcontrol) {
+ dev_err(vbe->dev, "Can not find kcontrol [%s].\n",
+ kctl_desc->kcontrol_id);
+ return -ENODEV;
+ }
+
+ switch (msg->header->cmd) {
+ case VFE_MSG_KCTL_SET:
+ return vbe_skl_kcontrol_put(sdev, vm_id, kcontrol, msg);
+ default:
+ dev_err(vbe->dev, "Unknown command %d for kcontrol [%s].\n",
+ msg->header->cmd, kctl_desc->kcontrol_id);
+ break;
+ }
+
+ return 0;
+}
+
+static int vbe_skl_msg_cfg_handle(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev,
+ int vm_id, struct vbe_ipc_msg *msg)
+{
+ switch (msg->header->cmd) {
+ case VFE_MSG_CFG_HDA:
+ return vbe_skl_cfg_hda(sdev, vm_id, msg);
+ default:
+ dev_err(vbe->dev, "Unknown command %d for config get message.\n",
+ msg->header->cmd);
+ break;
+ }
+
+ return 0;
+}
+
+static int vbe_skl_msg_pcm_handle(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev, int vm_id, struct vbe_ipc_msg *msg)
+{
+ const struct vbe_substream_info *substream_info;
+ char *pcm_id;
+ int direction;
+
+ if (msg->header->cmd == VFE_MSG_PCM_OPEN)
+ return vbe_skl_pcm_open(vbe, sdev, vm_id, msg);
+
+ pcm_id = msg->header->desc.pcm.pcm_id;
+ direction = msg->header->desc.pcm.direction;
+ substream_info = vbe_find_substream_info_by_pcm(vbe, pcm_id, direction);
+
+ if (!substream_info) {
+ dev_err(vbe->dev,
+ "Can not find active substream [%s].\n", pcm_id);
+ return -ENODEV;
+ }
+
+ switch (msg->header->cmd) {
+ case VFE_MSG_PCM_CLOSE:
+ return vbe_skl_pcm_close(sdev, vm_id, substream_info, msg);
+ case VFE_MSG_PCM_PREPARE:
+ return vbe_skl_pcm_prepare(sdev, vm_id, substream_info, msg);
+ case VFE_MSG_PCM_HW_PARAMS:
+ return vbe_skl_pcm_hw_params(sdev, vm_id, substream_info, msg);
+ case VFE_MSG_PCM_TRIGGER:
+ return vbe_skl_pcm_trigger(sdev, vm_id, substream_info, msg);
+ default:
+ dev_err(vbe->dev, "PCM stream notification %d not supported\n",
+ msg->header->cmd);
+ }
+
+ return 0;
+}
+
+static int vbe_skl_not_fwd(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev, int vm_id, void *ipc_bufs[SKL_VIRTIO_NOT_VQ_SZ],
+ size_t ipc_lens[SKL_VIRTIO_NOT_VQ_SZ])
+{
+ struct vbe_ipc_msg msg;
+
+ if (sizeof(struct vfe_msg_header) != ipc_lens[SKL_VIRTIO_MSG_HEADER]) {
+ dev_err(vbe->dev, "Mismatch of IPC header size");
+ return -EINVAL;
+ }
+
+ msg.header = ipc_bufs[SKL_VIRTIO_MSG_HEADER];
+ msg.tx_data = ipc_bufs[SKL_VIRTIO_MSG_TX];
+ msg.rx_data = ipc_bufs[SKL_VIRTIO_MSG_RX];
+
+ msg.tx_size = ipc_lens[SKL_VIRTIO_MSG_TX];
+ msg.rx_size = ipc_lens[SKL_VIRTIO_MSG_RX];
+
+ switch (msg.header->cmd & VFE_MSG_TYPE_MASK) {
+ case VFE_MSG_PCM:
+ return vbe_skl_msg_pcm_handle(vbe, sdev, vm_id, &msg);
+ case VFE_MSG_KCTL:
+ return vbe_skl_msg_kcontrol_handle(vbe, sdev, vm_id, &msg);
+ case VFE_MSG_TPLG:
+ //not supported yet
+ break;
+ case VFE_MSG_CFG:
+ return vbe_skl_msg_cfg_handle(vbe, sdev, vm_id, &msg);
+ }
+
+ return 0;
+}
+
+static int vbe_skl_ipc_fwd(const struct snd_skl_vbe *vbe,
+ const struct skl *sdev, int vm_id,
+ void *ipc_buf, void *reply_buf, size_t count, size_t *reply_sz)
+{
+ struct vfe_dsp_ipc_msg *ipc_data = ipc_buf;
+ struct skl_sst *skl_sst = sdev->skl_sst;
+ int ret;
+
+ dev_dbg(vbe->dev, "IPC forward request. Header:0X%016llX tx_data:%p\n",
+ ipc_data->header,
+ ipc_data->data_size ? &ipc_data->data : NULL);
+ dev_dbg(vbe->dev, "tx_size:%zu rx_data:%p rx_size:%zu\n",
+ ipc_data->data_size,
+ *reply_sz ? reply_buf : NULL,
+ *reply_sz);
+
+ /* Tx IPC and wait for response */
+ ret = *reply_sz <= 0 ? 0 : sst_ipc_tx_message_wait(&skl_sst->ipc,
+ ipc_data->header,
+ ipc_data->data_size ? &ipc_data->data : NULL,
+ ipc_data->data_size,
+ *reply_sz ? reply_buf : NULL,
+ reply_sz);
+
+ if (ret < 0) {
+ dev_dbg(vbe->dev, "IPC reply error:%d\n", ret);
+ return ret;
+ }
+ if (*reply_sz > 0) {
+ print_hex_dump(KERN_DEBUG, "IPC response:", DUMP_PREFIX_OFFSET,
+ 8, 4, (char *)reply_buf, *reply_sz, false);
+ }
+
+ return 0;
+}
+
+static int vbe_skl_virtio_vq_handle(const struct snd_skl_vbe *vbe,
+ const struct virtio_vq_info *vq, u16 *idx, const struct iovec *iov,
+ void *reply_buf[], size_t *reply_len, int vq_id, int vq_size)
+{
+ int i;
+ struct device *dev = vbe->sdev->skl_sst->dev;
+ int ret = virtio_vq_getchain(vq, idx, iov, vq_size, NULL);
+
+ if (ret != vq_size) {
+ dev_err(dev, "notification buffers not paired, expected:%d, got:%d",
+ vq_size, ret);
+ if (ret < 0) {
+ virtio_vq_endchains(vq, true);
+ return ret;
+ }
+ for (i = 0; i <= ret; i++)
+ virtio_vq_relchain(vq, *idx + i, iov[i].iov_len);
+
+ virtio_vq_endchains(vq, true);
+ return ret;
+ }
+ for (i = 0; i < ret; i++) {
+ reply_len[i] = iov[vq_id+i].iov_len;
+ reply_buf[i] = iov[vq_id+i].iov_base;
+ }
+ return 0;
+}
+
+static void vbe_skl_ipc_fe_not_get(const struct snd_skl_vbe *vbe, int vq_idx)
+{
+ int ret;
+ u16 idx;
+ const struct iovec iov[SKL_VIRTIO_NOT_VQ_SZ];
+ void *reply_buf[SKL_VIRTIO_NOT_VQ_SZ];
+ size_t reply_len[SKL_VIRTIO_NOT_VQ_SZ];
+ const struct virtio_vq_info *vq = &vbe->vqs[vq_idx];
+ const struct device *dev = vbe->sdev->skl_sst->dev;
+ int vm_id = vbe->vmid;
+
+ memset(iov, 0, sizeof(iov));
+
+ /* while there are mesages in virtio queue */
+ while (virtio_vq_has_descs(vq)) {
+ ret = vbe_skl_virtio_vq_handle(vbe, vq, &idx, iov,
+ reply_buf, reply_len,
+ SKL_VIRTIO_IPC_MSG, SKL_VIRTIO_NOT_VQ_SZ);
+ if (ret) {
+ dev_err(dev, "Failed to handle virtio message");
+ return;
+ }
+
+ ret = vbe_skl_not_fwd(vbe, vbe->sdev, vm_id,
+ reply_buf, reply_len);
+ if (ret < 0)
+ dev_err(dev, "submit guest ipc command fail\n");
+
+ virtio_vq_relchain(vq, idx + SKL_VIRTIO_MSG_HEADER,
+ reply_len[SKL_VIRTIO_MSG_HEADER]);
+ }
+ virtio_vq_endchains(vq, true);
+}
+
+static void vbe_skl_ipc_fe_cmd_get(const struct snd_skl_vbe *vbe, int vq_idx)
+{
+ u16 idx;
+ int ret;
+ const struct iovec iov[SKL_VIRTIO_IPC_VQ_SZ];
+ void *reply_buf[SKL_VIRTIO_IPC_VQ_SZ];
+ size_t reply_len[SKL_VIRTIO_IPC_VQ_SZ];
+ const struct virtio_vq_info *vq = &vbe->vqs[vq_idx];
+ const struct device *dev = vbe->sdev->skl_sst->dev;
+ int vm_id = vbe->vmid;
+
+ memset(iov, 0, sizeof(iov));
+
+ /* while there are mesages in virtio queue */
+ while (virtio_vq_has_descs(vq)) {
+ ret = vbe_skl_virtio_vq_handle(vbe, vq, &idx, iov,
+ reply_buf, reply_len,
+ SKL_VIRTIO_IPC_MSG, SKL_VIRTIO_IPC_VQ_SZ);
+ if (ret) {
+ dev_err(dev, "Failed to handle virtio message");
+ return;
+ }
+
+ /* send IPC to HW */
+ ret = vbe_skl_ipc_fwd(vbe, vbe->sdev, vm_id, reply_buf[0],
+ reply_buf[1], reply_len[0], &reply_len[1]);
+ if (ret < 0)
+ dev_err(dev, "submit guest ipc command fail\n");
+
+ virtio_vq_relchain(vq, idx, reply_len[0]);
+ }
+
+ /* BE has finished the operations, now let's kick back */
+ virtio_vq_endchains(vq, false);
+}
+
+/* IPC notification reply from FE to DSP */
+static void vbe_skl_ipc_fe_not_reply_get(struct snd_skl_vbe *vbe, int vq_idx)
+{
+ const struct virtio_vq_info *vq;
+ const struct vfe_hw_pos_request *entry;
+ unsigned long flags;
+ bool endchain;
+
+ if (list_empty(&vbe->posn_list))
+ return;
+
+ vq = &vbe->vqs[vq_idx];
+ entry = list_first_entry(&vbe->posn_list,
+ struct vfe_hw_pos_request, list);
+
+ endchain = vbe_skl_fill_posn_vqbuf(vbe, vq, entry);
+
+ if (endchain == true) {
+ list_del(&entry->list);
+ kfree(entry);
+ virtio_vq_endchains(vq, true);
+ }
+}
+
+void vbe_skl_handle_kick(const struct snd_skl_vbe *vbe, int vq_idx)
+{
+ dev_dbg(vbe->dev, "vq_idx %d\n", vq_idx);
+
+ switch (vq_idx) {
+ case SKL_VIRTIO_IPC_CMD_TX_VQ:
+ /* IPC command from FE to DSP */
+ vbe_skl_ipc_fe_cmd_get(vbe, vq_idx);
+ break;
+ case SKL_VIRTIO_IPC_CMD_RX_VQ:
+ /* IPC command reply from DSP to FE - NOT kick */
+ break;
+ case SKL_VIRTIO_IPC_NOT_TX_VQ:
+ /* IPC notification reply from FE to DSP */
+ vbe_skl_ipc_fe_not_get(vbe, vq_idx);
+ break;
+ case SKL_VIRTIO_IPC_NOT_RX_VQ:
+ /* IPC notification from DSP to FE - NOT kick */
+ vbe_skl_ipc_fe_not_reply_get(vbe, vq_idx);
+ break;
+ default:
+ dev_err(vbe->dev, "idx %d is invalid\n", vq_idx);
+ break;
+ }
+}
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-be.h b/sound/soc/intel/skylake/virtio/skl-virtio-be.h
new file mode 100644
index 000000000000..67b1b7c752ac
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-be.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * skl-virtio-be.h -- Virtio BE service header
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ *
+ * definitions/declarations for virtio BE service
+ */
+
+#ifndef __SOUND_SOC_SKL_VIRTIO_BE_H
+#define __SOUND_SOC_SKL_VIRTIO_BE_H
+
+#include <linux/vbs/vbs.h>
+#include "skl-virtio-common.h"
+
+
+#define SKL_VIRTIO_NOT_VQ_SZ 3
+#define SKL_VIRTIO_IPC_VQ_SZ 2
+
+#define SKL_VIRTIO_MSG_HEADER 0
+#define SKL_VIRTIO_MSG_TX 1
+#define SKL_VIRTIO_MSG_RX 2
+
+struct snd_skl_vbe;
+
+extern int snd_skl_vbe_register(struct skl *sdev, struct snd_skl_vbe **svbe);
+extern int snd_skl_vbe_register_client(struct snd_skl_vbe *vbe);
+extern void vbe_skl_handle_kick(const struct snd_skl_vbe *vbe, int vq_idx);
+
+struct vbe_substream_info {
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+ int direction;
+
+ struct snd_skl_vbe *vbe;
+ struct list_head list;
+};
+
+
+struct snd_skl_vbe {
+ struct skl *sdev;
+ struct device *dev;
+ struct virtio_dev_info dev_info;
+ struct virtio_vq_info vqs[SKL_VIRTIO_NUM_OF_VQS];
+
+ spinlock_t posn_lock;
+
+ struct list_head client_list;
+ struct list_head substr_info_list;
+ struct list_head list;
+ struct list_head posn_list;
+
+ int vmid; /* vm id number */
+};
+
+struct snd_skl_vbe_client {
+ struct snd_skl_vbe *vbe;
+ int vhm_client_id;
+ int max_vcpu;
+ struct list_head list;
+ struct vhm_request *req_buf;
+};
+
+struct virtio_miscdev {
+ struct device *dev;
+ int (*open)(struct file *f, void *data);
+ long (*ioctl)(struct file *f, void *data, unsigned int ioctl,
+ unsigned long arg);
+ int (*release)(struct file *f, void *data);
+ void *priv;
+};
+
+#endif
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-card.c b/sound/soc/intel/skylake/virtio/skl-virtio-card.c
new file mode 100644
index 000000000000..0f35a641fa16
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-card.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// skl-virtio-card.c -- ASoC cAVS virtio sound card
+//
+// Copyright (C) 2018 Intel Corporation.
+//
+// Authors: Furtak, Pawel <pawel.furtak@intel.com>
+// Janca, Grzegorz <grzegorz.janca@intel.com>
+//
+// This module registers virtio sound card as a machine driver
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "skl-virtio-fe.h"
+
+struct snd_kcontrol_new skl_virtio_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+};
+
+static const struct snd_soc_dapm_widget skl_virtio_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("DiranaCp", NULL),
+ SND_SOC_DAPM_HP("DiranaPb", NULL),
+ SND_SOC_DAPM_MIC("HdmiIn", NULL),
+ SND_SOC_DAPM_MIC("TestPinCp", NULL),
+ SND_SOC_DAPM_HP("TestPinPb", NULL),
+ SND_SOC_DAPM_MIC("BtHfpDl", NULL),
+ SND_SOC_DAPM_HP("BtHfpUl", NULL),
+ SND_SOC_DAPM_MIC("ModemDl", NULL),
+ SND_SOC_DAPM_HP("ModemUl", NULL),
+};
+
+static const struct snd_soc_dapm_route skl_virtio_map[] = {
+ /* Speaker BE connections */
+ { "Speaker", NULL, "ssp4 Tx"},
+ { "ssp4 Tx", NULL, "codec0_out"},
+
+ { "dirana_in", NULL, "ssp2 Rx"},
+ { "ssp2 Rx", NULL, "DiranaCp"},
+
+ { "dirana_aux_in", NULL, "ssp2 Rx"},
+ { "ssp2 Rx", NULL, "DiranaCp"},
+
+ { "dirana_tuner_in", NULL, "ssp2 Rx"},
+ { "ssp2 Rx", NULL, "DiranaCp"},
+
+ { "DiranaPb", NULL, "ssp2 Tx"},
+ { "ssp2 Tx", NULL, "dirana_out"},
+
+ { "hdmi_ssp1_in", NULL, "ssp1 Rx"},
+ { "ssp1 Rx", NULL, "HdmiIn"},
+
+ { "TestPin_ssp5_in", NULL, "ssp5 Rx"},
+ { "ssp5 Rx", NULL, "TestPinCp"},
+
+ { "TestPinPb", NULL, "ssp5 Tx"},
+ { "ssp5 Tx", NULL, "TestPin_ssp5_out"},
+
+ { "BtHfp_ssp0_in", NULL, "ssp0 Rx"},
+ { "ssp0 Rx", NULL, "BtHfpDl"},
+
+ { "BtHfpUl", NULL, "ssp0 Tx"},
+ { "ssp0 Tx", NULL, "BtHfp_ssp0_out"},
+
+ { "Modem_ssp3_in", NULL, "ssp3 Rx"},
+ { "ssp3 Rx", NULL, "ModemDl"},
+
+ { "ModemUl", NULL, "ssp3 Tx"},
+ { "ssp3 Tx", NULL, "Modem_ssp3_out"},
+};
+
+static struct snd_soc_dai_link skl_virtio_dais[] = {
+ /* Front End DAI links */
+ {
+ .name = "Speaker Port",
+ .stream_name = "Speaker",
+ .cpu_dai_name = "Speaker Pin",
+ .platform_name = "virtio4",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Dirana Capture Port",
+ .stream_name = "Dirana Cp",
+ .cpu_dai_name = "Dirana Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "Dirana Playback Port",
+ .stream_name = "Dirana Pb",
+ .cpu_dai_name = "Dirana Pb Pin",
+ .platform_name = "virtio4",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "TestPin Capture Port",
+ .stream_name = "TestPin Cp",
+ .cpu_dai_name = "TestPin Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "TestPin Playback Port",
+ .stream_name = "TestPin Pb",
+ .cpu_dai_name = "TestPin Pb Pin",
+ .platform_name = "virtio4",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "BtHfp Capture Port",
+ .stream_name = "BtHfp Cp",
+ .cpu_dai_name = "BtHfp Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "BtHfp Playback Port",
+ .stream_name = "BtHfp Pb",
+ .cpu_dai_name = "BtHfp Pb Pin",
+ .platform_name = "virtio4",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "Modem Capture Port",
+ .stream_name = "Modem Cp",
+ .cpu_dai_name = "Modem Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "Modem Playback Port",
+ .stream_name = "Modem Pb",
+ .cpu_dai_name = "Modem Pb Pin",
+ .platform_name = "virtio4",
+ .nonatomic = 1,
+ .dynamic = 1,
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dpcm_playback = 1,
+ },
+ {
+ .name = "HDMI Capture Port",
+ .stream_name = "HDMI Cp",
+ .cpu_dai_name = "HDMI Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "Dirana Aux Capture Port",
+ .stream_name = "Dirana Aux Cp",
+ .cpu_dai_name = "Dirana Aux Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+ {
+ .name = "Dirana Tuner Capture Port",
+ .stream_name = "Dirana Tuner Cp",
+ .cpu_dai_name = "Dirana Tuner Cp Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .init = NULL,
+ .dpcm_capture = 1,
+ .ignore_suspend = 1,
+ .nonatomic = 1,
+ .dynamic = 1,
+ },
+
+
+ /* Back End DAI links */
+ {
+ /* SSP0 - BT */
+ .name = "SSP0-Codec",
+ .id = 0,
+ .cpu_dai_name = "SSP0 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ },
+ {
+ /* SSP1 - HDMI-In */
+ .name = "SSP1-Codec",
+ .id = 1,
+ .cpu_dai_name = "SSP1 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .no_pcm = 1,
+ },
+ {
+ /* SSP2 - Dirana */
+ .name = "SSP2-Codec",
+ .id = 2,
+ .cpu_dai_name = "SSP2 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ },
+ {
+ /* SSP3 - Modem */
+ .name = "SSP3-Codec",
+ .id = 3,
+ .cpu_dai_name = "SSP3 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ },
+ {
+ /* SSP4 - Amplifier */
+ .name = "SSP4-Codec",
+ .id = 4,
+ .cpu_dai_name = "SSP4 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ },
+ {
+ /* SSP5 - TestPin */
+ .name = "SSP5-Codec",
+ .id = 5,
+ .cpu_dai_name = "SSP5 Pin",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .platform_name = "virtio4",
+ .ignore_suspend = 1,
+ .dpcm_capture = 1,
+ .dpcm_playback = 1,
+ .no_pcm = 1,
+ },
+
+};
+
+static int skl_virtio_add_dai_link(struct snd_soc_card *card,
+ struct snd_soc_dai_link *link)
+{
+ link->platform_name = "virtio4";
+ link->nonatomic = 1;
+ return 0;
+}
+
+static struct snd_soc_card skl_virtio_card = {
+ .name = "skl_virtio_card",
+ .dai_link = skl_virtio_dais,
+ .num_links = ARRAY_SIZE(skl_virtio_dais),
+ .controls = skl_virtio_controls,
+ .num_controls = ARRAY_SIZE(skl_virtio_controls),
+ .dapm_widgets = skl_virtio_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(skl_virtio_widgets),
+ .dapm_routes = skl_virtio_map,
+ .num_dapm_routes = ARRAY_SIZE(skl_virtio_map),
+ .fully_routed = true,
+ .add_dai_link = skl_virtio_add_dai_link,
+};
+
+static int skl_virtio_card_probe(struct platform_device *pdev)
+{
+ struct snd_skl_vfe *vfe;
+ int ret;
+ struct snd_soc_card *card = &skl_virtio_card;
+
+ card->dev = &pdev->dev;
+ vfe = dev_get_drvdata(&pdev->dev);
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret < 0)
+ return ret;
+
+ vfe->notify_machine_probe(vfe, pdev, card);
+
+ return ret;
+}
+
+static int skl_virtio_card_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&skl_virtio_card);
+ return 0;
+}
+
+static struct platform_driver skl_virtio_audio = {
+ .probe = skl_virtio_card_probe,
+ .remove = skl_virtio_card_remove,
+ .driver = {
+ .name = "skl_virtio_card",
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(skl_virtio_audio)
+
+MODULE_DESCRIPTION("ASoC cAVS virtio sound card");
+MODULE_AUTHOR("Pawel Furtak");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:skl-virtio-card");
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-common.h b/sound/soc/intel/skylake/virtio/skl-virtio-common.h
new file mode 100644
index 000000000000..d25b5986ead6
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-common.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * skl-virtio-common.h -- Virtio common header
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ *
+ * common header for both virtio FE driver and BE service
+ */
+
+#include "../skl.h"
+
+#ifndef __SOUND_SOC_SKL_VIRTIO_COMMON_H
+#define __SOUND_SOC_SKL_VIRTIO_COMMON_H
+
+
+#define SKL_VIRTIO_IPC_CMD_TX_VQ 0
+#define SKL_VIRTIO_IPC_CMD_RX_VQ 1
+#define SKL_VIRTIO_IPC_NOT_TX_VQ 2
+#define SKL_VIRTIO_IPC_NOT_RX_VQ 3
+#define SKL_VIRTIO_NUM_OF_VQS 4
+
+#define SKL_VIRTIO_IPC_CMD_TX_VQ_NAME "snd-skl-ipc-cmd-tx"
+#define SKL_VIRTIO_IPC_CMD_RX_VQ_NAME "snd-skl-ipc-cmd-rx"
+#define SKL_VIRTIO_IPC_NOT_TX_VQ_NAME "snd-skl-ipc-not-tx"
+#define SKL_VIRTIO_IPC_NOT_RX_VQ_NAME "snd-skl-ipc-not-rx"
+
+#define SKL_VIRTIO_IPC_MSG 0
+#define SKL_VIRTIO_IPC_REPLY 1
+
+struct vfe_dsp_ipc_msg {
+ u64 header;
+ struct ipc_message *ipc;
+ size_t data_size;
+ void *data;
+};
+
+struct vfe_pcm_info {
+ char pcm_id[64];
+ int direction;
+};
+
+struct vfe_kctl_info {
+ char kcontrol_id[64];
+};
+
+struct vfe_msg_header {
+ int cmd;
+
+ union {
+ struct vfe_pcm_info pcm;
+ struct vfe_kctl_info kcontrol;
+ } desc;
+};
+
+struct vfe_ipc_msg {
+ struct vfe_msg_header header;
+
+ int tx_size;
+ void *tx_data;
+ int rx_size;
+ void *rx_data;
+
+ wait_queue_head_t *waitq;
+ bool *completed;
+
+ void *tx_buf;
+ void *rx_buf;
+};
+
+struct vbe_ipc_msg {
+ struct vfe_msg_header *header;
+
+ int tx_size;
+ void *tx_data;
+
+ int rx_size;
+ void *rx_data;
+};
+
+struct vfe_kctl_value {
+ struct snd_ctl_elem_value value;
+};
+
+/* stream ring info */
+struct vfe_pcm_dma_conf {
+ uint64_t addr;
+ uint32_t pages;
+ uint32_t size;
+ uint32_t offset;
+};
+
+
+struct vfe_pcm_hw_params {
+ uint32_t access;
+ uint32_t direction;
+ uint32_t frame_fmt;
+ uint32_t frame_subfmt;
+ uint32_t buffer_fmt;
+ uint32_t buffer_size;
+ uint32_t buffer_bytes;
+ uint32_t periods;
+ uint32_t period_size;
+ uint32_t stream_tag;
+ uint32_t rate;
+ uint32_t channels;
+ uint32_t sample_valid_bytes;
+ uint32_t sample_container_bytes;
+ uint32_t host_period_bytes;
+};
+
+struct vfe_hw_pos_request {
+ char pcm_id[64];
+ u32 stream_dir;
+ u64 stream_pos;
+ struct list_head list;
+};
+
+struct vfe_pcm_result {
+ int ret;
+};
+
+struct vfe_hda_cfg {
+ u32 resource_length;
+ u32 ppcap;
+ u32 spbcap;
+ u32 mlcap;
+ u32 gtscap;
+ u32 drsmcap;
+ u8 cp_streams;
+ u8 pb_streams;
+};
+
+#define VFE_MSG_TYPE_OFFSET 8
+#define VFE_MSG_TYPE_MASK (0xFF << VFE_MSG_TYPE_OFFSET)
+
+enum vfe_ipc_msg_type {
+ VFE_MSG_PCM = 1 << VFE_MSG_TYPE_OFFSET,
+ VFE_MSG_KCTL = 2 << VFE_MSG_TYPE_OFFSET,
+ VFE_MSG_TPLG = 3 << VFE_MSG_TYPE_OFFSET,
+ VFE_MSG_CFG = 4 << VFE_MSG_TYPE_OFFSET,
+
+ VFE_MSG_PCM_OPEN = VFE_MSG_PCM | 0x01,
+ VFE_MSG_PCM_CLOSE = VFE_MSG_PCM | 0x02,
+ VFE_MSG_PCM_PREPARE = VFE_MSG_PCM | 0x03,
+ VFE_MSG_PCM_HW_PARAMS = VFE_MSG_PCM | 0x04,
+ VFE_MSG_PCM_TRIGGER = VFE_MSG_PCM | 0x05,
+
+ VFE_MSG_KCTL_SET = VFE_MSG_KCTL | 0x01,
+
+ VFE_MSG_CFG_HDA = VFE_MSG_CFG | 0x01,
+};
+
+#endif
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-fe.c b/sound/soc/intel/skylake/virtio/skl-virtio-fe.c
new file mode 100644
index 000000000000..d406e6a9b21a
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-fe.c
@@ -0,0 +1,920 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// skl-virtio-fe.c -- Virtio FE audio driver for SKL architecture
+//
+// Copyright (C) 2018 Intel Corporation.
+//
+// Authors: Furtak, Pawel <pawel.furtak@intel.com>
+// Janca, Grzegorz <grzegorz.janca@intel.com>
+//
+// FE driver registers various operations such as DSP, PCM or sound card
+// control. The operations use virtio IPC to forward request to BE driver.
+// FE also receives buffer position updates form BE and informs ALSA about
+// the position.
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/vhm/acrn_common.h>
+#include <linux/vhm/acrn_vhm_ioreq.h>
+#include <linux/vhm/acrn_vhm_mm.h>
+#include <linux/vhm/vhm_vm_mngt.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <sound/pcm_params.h>
+
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+
+#include "../skl-sst-ipc.h"
+#include "skl-virtio-fe.h"
+#include "skl-virtio.h"
+
+#include <linux/time.h>
+
+static struct snd_skl_vfe *skl_vfe;
+
+static struct snd_skl_vfe *get_virtio_audio_fe(void)
+{
+ return skl_vfe;
+}
+
+struct vfe_substream_info *vfe_find_substream_info_by_pcm(
+ struct snd_skl_vfe *vfe, char *pcm_id, int direction)
+{
+ struct vfe_substream_info *info;
+
+ list_for_each_entry(info, &vfe->substr_info_list, list) {
+ if (info->direction == direction &&
+ strncmp(info->pcm->id, pcm_id,
+ ARRAY_SIZE(info->pcm->id)) == 0)
+ return info;
+ }
+
+ return NULL;
+}
+
+inline struct vfe_substream_info *vfe_find_substream_info(
+ struct snd_skl_vfe *vfe, struct snd_pcm_substream *substr)
+{
+ return vfe_find_substream_info_by_pcm(vfe,
+ substr->pcm->id, substr->stream);
+}
+
+inline int vfe_is_valid_pcm_id(char *pcm_id)
+{
+ if (pcm_id == NULL || strlen(pcm_id) == 0 ||
+ strcmp(pcm_id, "((null))") == 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+inline int vfe_is_valid_fe_substream(struct snd_pcm_substream *substream)
+{
+ return vfe_is_valid_pcm_id(substream->pcm->id);
+}
+
+struct vfe_kcontrol *vfe_find_kcontrol(struct snd_skl_vfe *vfe,
+ struct snd_kcontrol *kcontrol)
+{
+ struct vfe_kcontrol *vfe_kcontrol;
+
+ list_for_each_entry(vfe_kcontrol, &vfe->kcontrols_list, list) {
+ if (kcontrol == vfe_kcontrol->kcontrol)
+ return vfe_kcontrol;
+ }
+
+ return NULL;
+}
+
+static int vfe_send_virtio_msg(struct snd_skl_vfe *vfe,
+ struct virtqueue *vq, struct scatterlist *sgs, int sg_count,
+ void *data, bool out)
+{
+ int ret;
+
+ if (!vq)
+ return -EINVAL;
+
+ if (out)
+ ret = virtqueue_add_outbuf(vq, sgs, sg_count, data, GFP_KERNEL);
+ else
+ ret = virtqueue_add_inbuf(vq, sgs, sg_count, data, GFP_KERNEL);
+
+ if (ret < 0) {
+ dev_err(&vfe->vdev->dev,
+ "error: could not send messageover virtqueue %d\n",
+ ret);
+ return ret;
+ }
+
+ virtqueue_kick(vq);
+ return 0;
+}
+
+static int vfe_send_msg(struct snd_skl_vfe *vfe,
+ struct vfe_msg_header *msg_header, void *tx_data, int tx_size,
+ void *rx_data, int rx_size)
+{
+ wait_queue_head_t waitq;
+ struct scatterlist sgs[3];
+ struct vfe_ipc_msg *msg;
+ int ret;
+ bool completed = false;
+
+ if (!msg_header)
+ return -EINVAL;
+
+ msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ memcpy(&msg->header, msg_header, sizeof(msg->header));
+ msg->tx_data = tx_data;
+ msg->tx_size = tx_size;
+ if (tx_data) {
+ msg->tx_buf = kzalloc(tx_size, GFP_KERNEL);
+ if (!msg->tx_buf)
+ goto out_no_mem;
+ memcpy(msg->tx_buf, msg->tx_data, tx_size);
+ }
+ msg->rx_data = rx_data;
+ msg->rx_size = rx_size;
+ if (rx_data) {
+ msg->rx_buf = kzalloc(rx_size, GFP_KERNEL);
+ if (!msg->rx_buf)
+ goto out_no_mem;
+ memcpy(msg->rx_buf, msg->rx_data, rx_size);
+ }
+
+ sg_init_table(sgs, 3);
+ sg_set_buf(&sgs[0], &msg->header, sizeof(msg->header));
+ if (msg->tx_buf)
+ sg_set_buf(&sgs[1], msg->tx_buf, tx_size);
+
+ if (msg->rx_buf)
+ sg_set_buf(&sgs[2], msg->rx_buf, rx_size);
+
+ ret = vfe_send_virtio_msg(vfe, vfe->ipc_not_tx_vq, sgs, 3, msg, true);
+ if (ret < 0)
+ return ret;
+
+ // If response is expected, wait for it
+ if (rx_data) {
+ init_waitqueue_head(&waitq);
+
+ msg->waitq = &waitq;
+ msg->completed = &completed;
+
+ ret = wait_event_timeout(waitq, completed,
+ msecs_to_jiffies(VFE_MSG_MSEC_TIMEOUT));
+ if (ret == 0) {
+ dev_err(&vfe->vdev->dev, "Response from backend timed out\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+
+out_no_mem:
+ kfree(msg->rx_buf);
+ kfree(msg->tx_buf);
+ kfree(msg);
+
+ return -ENOMEM;
+}
+
+static int vfe_send_pos_request(struct snd_skl_vfe *vfe,
+ struct vfe_hw_pos_request *request)
+{
+ struct scatterlist sg;
+
+ sg_init_one(&sg, request, sizeof(*request));
+
+ return vfe_send_virtio_msg(vfe, vfe->ipc_not_rx_vq,
+ &sg, 1, request, false);
+}
+
+static int vfe_send_kctl_msg(struct snd_skl_vfe *vfe,
+ struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct vfe_kctl_value kcontrol_value;
+ struct vfe_msg_header msg_header;
+ struct vfe_kctl_info *kctl_desc = &msg_header.desc.kcontrol;
+
+ msg_header.cmd = VFE_MSG_KCTL_SET;
+ strncpy(kctl_desc->kcontrol_id, kcontrol->id.name,
+ ARRAY_SIZE(kcontrol->id.name));
+ kcontrol_value.value = *ucontrol;
+
+ return vfe_send_msg(vfe, &msg_header, &kcontrol_value,
+ sizeof(kcontrol_value), NULL, 0);
+}
+
+
+//TODO: make it to use same mechanism as vfe_send_pcm_msg
+static int vfe_send_dsp_ipc_msg(struct snd_skl_vfe *vfe,
+ struct ipc_message *msg)
+{
+ struct scatterlist sgs[2];
+ struct vfe_dsp_ipc_msg *ipc_msg;
+ int data_size = sizeof(ipc_msg->header) + sizeof(ipc_msg->data_size);
+
+ if (msg->tx_data != NULL && msg->tx_size > 0) {
+ data_size += msg->tx_size;
+ } else {
+ msg->complete = true;
+ list_del(&msg->list);
+ sst_ipc_tx_msg_reply_complete(&vfe->sdev.skl_sst->ipc, msg);
+
+ return 0;
+ }
+
+ ipc_msg = kzalloc(data_size, GFP_ATOMIC);
+ if (!ipc_msg)
+ return -ENOMEM;
+
+ ipc_msg->header = msg->header;
+ ipc_msg->ipc = msg;
+ ipc_msg->data_size = msg->tx_size;
+
+ if (msg->tx_data != NULL && msg->tx_size > 0)
+ memcpy(&ipc_msg->data, msg->tx_data, msg->tx_size);
+
+ sg_init_table(sgs, 2);
+ sg_set_buf(&sgs[SKL_VIRTIO_IPC_MSG],
+ ipc_msg, data_size);
+ sg_set_buf(&sgs[SKL_VIRTIO_IPC_REPLY],
+ msg->rx_data, msg->rx_size);
+
+ vfe->msg = msg;
+
+ return vfe_send_virtio_msg(vfe, vfe->ipc_cmd_tx_vq,
+ sgs, 2, ipc_msg, true);
+}
+
+/* send the IPC message completed, this means the BE has received the cmd */
+static void vfe_cmd_tx_done(struct virtqueue *vq)
+{
+ struct snd_skl_vfe *vfe = vq->vdev->priv;
+ struct vfe_dsp_ipc_msg *msg;
+ unsigned int buflen = 0;
+
+ while ((msg = virtqueue_get_buf(vfe->ipc_cmd_tx_vq, &buflen))
+ != NULL) {
+ msg->ipc->complete = true;
+ list_del(&msg->ipc->list);
+ sst_ipc_tx_msg_reply_complete(&vfe->sdev.skl_sst->ipc,
+ msg->ipc);
+ kfree(msg);
+ }
+}
+
+static void vfe_cmd_handle_rx(struct virtqueue *vq)
+{
+}
+
+static void vfe_not_tx_done(struct virtqueue *vq)
+{
+ struct snd_skl_vfe *vfe = vq->vdev->priv;
+ struct vfe_ipc_msg *msg;
+ unsigned int buflen = 0;
+
+ while ((msg = virtqueue_get_buf(vfe->ipc_not_tx_vq, &buflen))
+ != NULL) {
+
+ kfree(msg->tx_buf);
+ if (msg->rx_buf) {
+ memcpy(msg->rx_data, msg->rx_buf, msg->rx_size);
+ kfree(msg->rx_buf);
+ }
+
+ if (msg->waitq && msg->completed) {
+ *msg->completed = true;
+ wake_up(msg->waitq);
+ }
+
+ kfree(msg);
+ }
+}
+
+/*
+ * handle the pos_update, receive the posn and send to up layer, then
+ * resend the buffer to BE
+ */
+static void vfe_not_handle_rx(struct virtqueue *vq)
+{
+ struct snd_skl_vfe *vfe;
+
+ vfe = vq->vdev->priv;
+ schedule_work(&vfe->posn_update_work);
+}
+
+static void vfe_posn_update(struct work_struct *work)
+{
+ struct vfe_hw_pos_request *pos_req;
+ struct virtqueue *vq;
+ unsigned int buflen = 0;
+ struct vfe_substream_info *substr_info;
+ struct snd_skl_vfe *vfe =
+ container_of(work, struct snd_skl_vfe, posn_update_work);
+
+ vq = vfe->ipc_not_rx_vq;
+
+ while ((pos_req = virtqueue_get_buf(vq, &buflen)) != NULL) {
+ vfe_send_pos_request(vfe, pos_req);
+ substr_info = vfe_find_substream_info_by_pcm(vfe,
+ pos_req->pcm_id, pos_req->stream_dir);
+
+ // substream may be already closed on FE side
+ if (!substr_info)
+ return;
+
+ substr_info->hw_ptr = pos_req->stream_pos;
+ snd_pcm_period_elapsed(substr_info->substream);
+ }
+}
+
+int vfe_kcontrol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+ struct vfe_kcontrol *vfe_kcontrol = vfe_find_kcontrol(vfe, kcontrol);
+ int ret;
+
+ vfe_send_kctl_msg(vfe, kcontrol, ucontrol);
+
+ if (vfe_kcontrol->put)
+ ret = vfe_kcontrol->put(kcontrol, ucontrol);
+
+ return 0;
+}
+
+static struct vfe_msg_header
+vfe_get_pcm_msg_header(enum vfe_ipc_msg_type msg_type,
+ struct snd_pcm_substream *substream)
+{
+ struct vfe_msg_header msg_header;
+ struct vfe_pcm_info *pcm_desc = &msg_header.desc.pcm;
+
+ msg_header.cmd = msg_type;
+ strncpy(pcm_desc->pcm_id, substream->pcm->id,
+ ARRAY_SIZE(pcm_desc->pcm_id));
+ pcm_desc->direction = substream->stream;
+
+ return msg_header;
+}
+
+int vfe_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct vfe_substream_info *substr_info;
+ struct vfe_msg_header msg_header;
+ struct vfe_pcm_result vbe_result;
+ int ret;
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+
+ ret = skl_platform_open(substream);
+ if (ret < 0)
+ return ret;
+
+ // Ignore all substreams not associated with PCM
+ ret = vfe_is_valid_fe_substream(substream);
+ if (ret)
+ return 0;
+
+ msg_header = vfe_get_pcm_msg_header(VFE_MSG_PCM_OPEN, substream);
+
+ ret = vfe_send_msg(vfe, &msg_header, NULL, 0,
+ &vbe_result, sizeof(vbe_result));
+ if (ret < 0)
+ return ret;
+
+ substr_info = kzalloc(sizeof(*substr_info), GFP_KERNEL);
+ if (!substr_info)
+ return -ENOMEM;
+
+ substr_info->pcm = substream->pcm;
+ substr_info->substream = substream;
+ substr_info->direction = substream->stream;
+
+ list_add(&substr_info->list, &vfe->substr_info_list);
+
+ return vbe_result.ret;
+}
+
+int vfe_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct vfe_substream_info *sstream_info;
+ struct vfe_msg_header msg_header;
+ struct vfe_pcm_result vbe_result;
+ int ret;
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+
+ ret = vfe_is_valid_fe_substream(substream);
+ if (ret)
+ return 0;
+
+ sstream_info = vfe_find_substream_info(vfe, substream);
+
+ if (sstream_info) {
+ list_del(&sstream_info->list);
+ kfree(sstream_info);
+ }
+
+ msg_header = vfe_get_pcm_msg_header(VFE_MSG_PCM_CLOSE, substream);
+
+ ret = vfe_send_msg(vfe, &msg_header, NULL, 0,
+ &vbe_result, sizeof(vbe_result));
+ if (ret < 0)
+ return ret;
+
+ return vbe_result.ret;
+}
+
+int vfe_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct vfe_pcm_hw_params vfe_params;
+ struct vfe_msg_header msg_header;
+ struct vfe_pcm_result vbe_result;
+ int ret;
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+
+ ret = vfe_is_valid_fe_substream(substream);
+ if (ret)
+ return 0;
+
+ vfe_params.access = params_access(params);
+ vfe_params.direction = substream->stream;
+ vfe_params.sample_valid_bytes = params_width(params) >> 3;
+ vfe_params.buffer_fmt = SNDRV_PCM_INFO_INTERLEAVED;
+ vfe_params.rate = params_rate(params);
+ vfe_params.channels = params_channels(params);
+ vfe_params.host_period_bytes = params_period_bytes(params);
+ vfe_params.buffer_bytes = params_buffer_bytes(params);
+ vfe_params.buffer_size = params_buffer_size(params);
+ vfe_params.sample_container_bytes = params_width(params);
+ vfe_params.frame_fmt = params_format(params);
+ vfe_params.frame_subfmt = params_subformat(params);
+ vfe_params.period_size = params_period_size(params);
+ vfe_params.periods = params_periods(params);
+
+ msg_header = vfe_get_pcm_msg_header(VFE_MSG_PCM_HW_PARAMS, substream);
+
+ ret = vfe_send_msg(vfe, &msg_header, &vfe_params, sizeof(vfe_params),
+ &vbe_result, sizeof(vbe_result));
+ if (ret < 0)
+ return ret;
+
+ return vbe_result.ret;
+}
+
+int vfe_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+ struct vfe_msg_header msg_header;
+ int ret;
+
+ ret = skl_platform_pcm_trigger(substream, cmd);
+ if (ret < 0)
+ return ret;
+
+ ret = vfe_is_valid_fe_substream(substream);
+ if (ret)
+ return 0;
+
+ msg_header = vfe_get_pcm_msg_header(VFE_MSG_PCM_TRIGGER, substream);
+
+ return vfe_send_msg(vfe, &msg_header, &cmd, sizeof(cmd), NULL, 0);
+}
+
+int vfe_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct vfe_msg_header msg_header;
+ struct vfe_pcm_dma_conf dma_conf;
+ struct vfe_pcm_result vbe_result;
+ struct snd_sg_buf *sg_buf;
+ int ret;
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+
+ ret = vfe_is_valid_fe_substream(substream);
+ if (ret)
+ return 0;
+
+ sg_buf = snd_pcm_substream_sgbuf(substream);
+
+ dma_conf.addr = (u64)sg_buf->table[0].addr;
+ dma_conf.size = (u64)substream->runtime->dma_bytes;
+ dma_conf.pages = sg_buf->pages;
+ dma_conf.offset = (u64)0;
+
+ msg_header = vfe_get_pcm_msg_header(VFE_MSG_PCM_PREPARE, substream);
+
+ ret = vfe_send_msg(vfe, &msg_header, &dma_conf, sizeof(dma_conf),
+ &vbe_result, sizeof(vbe_result));
+ if (ret < 0)
+ return ret;
+
+ return vbe_result.ret;
+}
+
+snd_pcm_uframes_t vfe_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_skl_vfe *vfe = get_virtio_audio_fe();
+ struct vfe_substream_info *substr_info =
+ vfe_find_substream_info(vfe, substream);
+
+ return substr_info->hw_ptr;
+}
+
+static const char *const vfe_skl_vq_names[SKL_VIRTIO_NUM_OF_VQS] = {
+ SKL_VIRTIO_IPC_CMD_TX_VQ_NAME,
+ SKL_VIRTIO_IPC_CMD_RX_VQ_NAME,
+ SKL_VIRTIO_IPC_NOT_TX_VQ_NAME,
+ SKL_VIRTIO_IPC_NOT_RX_VQ_NAME,
+};
+
+static struct snd_soc_acpi_mach vfe_acpi_mach = {
+ .drv_name = "skl_virtio_card",
+ .fw_filename = "intel/dsp_fw_bxtn.bin",
+ .asoc_plat_name = "virtio4",
+};
+
+static struct pci_device_id vfe_pci_device_id = {
+ PCI_DEVICE(0x8086, 0x8063),
+ .driver_data = (unsigned long)&vfe_acpi_mach
+};
+
+int vfe_wrap_kcontrol(struct snd_skl_vfe *vfe, struct snd_kcontrol *kcontrol)
+{
+ struct vfe_kcontrol *vfe_kcontrol = devm_kzalloc(&vfe->vdev->dev,
+ sizeof(*vfe_kcontrol), GFP_KERNEL);
+
+ if (!vfe_kcontrol)
+ return -ENOMEM;
+
+ vfe_kcontrol->kcontrol = kcontrol;
+ vfe_kcontrol->put = kcontrol->put;
+ kcontrol->put = vfe_kcontrol_put;
+
+ list_add(&vfe_kcontrol->list, &vfe->kcontrols_list);
+ return 0;
+}
+
+int vfe_wrap_native_driver(struct snd_skl_vfe *vfe,
+ struct platform_device *pdev, struct snd_soc_card *card)
+{
+ struct snd_kcontrol *kctl;
+ int ret;
+
+ list_for_each_entry(kctl, &card->snd_card->controls, list) {
+ ret = vfe_wrap_kcontrol(vfe, kctl);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_pcm_ops vfe_platform_ops = {
+ .open = vfe_pcm_open,
+ .close = vfe_pcm_close,
+ .hw_params = vfe_pcm_hw_params,
+ .pointer = vfe_pcm_pointer,
+ .trigger = vfe_pcm_trigger,
+ .prepare = vfe_pcm_prepare,
+ .ioctl = snd_pcm_lib_ioctl,
+ .mmap = snd_pcm_lib_default_mmap,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+static const struct snd_soc_component_driver vfe_component_drv = {
+ .name = "virt-pcm",
+ .probe = skl_platform_soc_probe,
+ .ops = &vfe_platform_ops,
+ .pcm_new = skl_pcm_new,
+ .pcm_free = skl_pcm_free,
+};
+
+
+static int vfe_platform_register(struct snd_skl_vfe *vfe, struct device *dev)
+{
+ int result = skl_platform_component_register(dev, &vfe_component_drv);
+
+ return result;
+}
+
+static int vfe_machine_device_register(struct snd_skl_vfe *vfe, struct skl *skl)
+{
+ struct snd_soc_acpi_mach *mach = skl->mach;
+ struct platform_device *pdev;
+ int ret;
+
+ pdev = platform_device_alloc(mach->drv_name, -1);
+ if (!pdev) {
+ dev_err(&vfe->vdev->dev, "platform device alloc failed\n");
+ return -EIO;
+ }
+
+ dev_set_drvdata(&pdev->dev, vfe);
+
+ ret = platform_device_add(pdev);
+ if (ret < 0) {
+ dev_err(&vfe->vdev->dev, "failed to add machine device\n");
+ platform_device_put(pdev);
+ return ret;
+ }
+
+ skl->i2s_dev = pdev;
+
+ return 0;
+}
+
+static void vfe_machine_device_unregister(struct skl *skl)
+{
+ if (skl->i2s_dev)
+ platform_device_unregister(skl->i2s_dev);
+}
+
+static int vfe_skl_init_dsp(struct skl *skl)
+{
+ const struct skl_dsp_ops *ops;
+ struct skl_dsp_cores *cores;
+ int ret;
+
+ struct hdac_bus *bus = &skl->hbus;
+ int irq = bus->irq;
+
+ ops = skl_get_dsp_ops(skl->pci->device);
+ if (!ops)
+ return -EIO;
+
+ ret = ops->init(bus->dev, NULL, irq, skl->fw_name, ops->loader_ops(),
+ &skl->skl_sst, NULL);
+ if (ret < 0)
+ return ret;
+
+ skl->skl_sst->dsp_ops = ops;
+ cores = &skl->skl_sst->cores;
+ cores->count = ops->num_cores;
+
+ cores->state = devm_kcalloc(bus->dev, cores->count,
+ sizeof(*cores->state), GFP_KERNEL);
+ if (!cores->state)
+ return -ENOMEM;
+
+ cores->usage_count = devm_kcalloc(bus->dev, cores->count,
+ sizeof(*cores->usage_count), GFP_KERNEL);
+ if (!cores->usage_count)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&skl->skl_sst->notify_kctls);
+
+ return ret;
+}
+
+static int vfe_skl_init_hbus(struct snd_skl_vfe *vfe, struct skl *skl)
+{
+ struct vfe_msg_header msg_header;
+ struct vfe_hda_cfg hda_cfg;
+ struct hdac_bus *bus = &skl->hbus;
+ struct virtio_device *vdev = vfe->vdev;
+ int cp_streams, pb_streams, start_idx;
+ int ret;
+
+ msg_header.cmd = VFE_MSG_CFG_HDA;
+
+ ret = vfe_send_msg(vfe, &msg_header, NULL, 0,
+ &hda_cfg, sizeof(hda_cfg));
+ if (ret < 0)
+ return ret;
+
+ snd_hdac_ext_bus_init(bus, &vdev->dev, NULL, NULL, NULL);
+ bus->use_posbuf = 1;
+ bus->bdl_pos_adj = 0;
+ dev_set_drvdata(&vdev->dev, bus);
+
+ bus->remap_addr = devm_kzalloc(&vdev->dev,
+ hda_cfg.resource_length, GFP_KERNEL);
+ if (bus->remap_addr == NULL)
+ return -ENXIO;
+
+ bus->ppcap = hda_cfg.ppcap ? bus->remap_addr + hda_cfg.ppcap : 0;
+ bus->spbcap = hda_cfg.spbcap ? bus->remap_addr + hda_cfg.spbcap : 0;
+ bus->mlcap = hda_cfg.mlcap ? bus->remap_addr + hda_cfg.mlcap : 0;
+ bus->gtscap = hda_cfg.gtscap ? bus->remap_addr + hda_cfg.gtscap : 0;
+ bus->drsmcap = hda_cfg.drsmcap ? bus->remap_addr + hda_cfg.drsmcap : 0;
+
+ cp_streams = hda_cfg.cp_streams;
+ pb_streams = hda_cfg.pb_streams;
+
+ if (!pb_streams && !cp_streams)
+ return -EIO;
+
+ bus->num_streams = cp_streams + pb_streams;
+
+ /* initialize streams */
+ snd_hdac_ext_stream_init_all
+ (bus, 0, cp_streams, SNDRV_PCM_STREAM_CAPTURE);
+ start_idx = cp_streams;
+ snd_hdac_ext_stream_init_all
+ (bus, start_idx, pb_streams, SNDRV_PCM_STREAM_PLAYBACK);
+
+ ret = snd_hdac_bus_alloc_stream_pages(bus);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+
+static struct nhlt_acpi_table *vfe_skl_nhlt_init(struct device *dev)
+{
+ struct nhlt_acpi_table *nhlt;
+ struct nhlt_endpoint *nhlt_ep;
+ int ep_count = 6, cnt = 0, nhlt_size;
+
+ nhlt_size = sizeof(*nhlt) + sizeof(struct nhlt_endpoint) * ep_count;
+ nhlt = devm_kzalloc(dev, nhlt_size, GFP_KERNEL);
+ if (!nhlt)
+ return NULL;
+
+ //TODO: instead of hardcoded definition
+ //nhlt configuration should be read from BE
+ nhlt->endpoint_count = ep_count;
+ nhlt_ep = (struct nhlt_endpoint *)nhlt->desc;
+
+ for (cnt = 0; cnt < ep_count; cnt++) {
+ nhlt_ep->length = sizeof(struct nhlt_endpoint);
+ nhlt_ep->linktype = NHLT_LINK_SSP;
+ nhlt_ep->virtual_bus_id = cnt;
+
+ nhlt_ep = (struct nhlt_endpoint *)((u8 *)nhlt_ep +
+ nhlt_ep->length);
+ }
+
+ return nhlt;
+}
+
+static int vfe_skl_init(struct virtio_device *vdev)
+{
+ int err;
+ struct snd_skl_vfe *vfe = vdev->priv;
+ struct skl *skl = &vfe->sdev;
+
+ skl->pci = devm_kzalloc(&vdev->dev, sizeof(*skl->pci), GFP_KERNEL);
+ if (!skl->pci)
+ return -ENOMEM;
+
+ skl->pci->device = vfe_pci_device_id.device;
+ skl->pci->dev = vdev->dev;
+
+ skl->mach = &vfe_acpi_mach;
+ skl->mach->pdata = &vfe;
+
+ skl->fw_name = skl->mach->fw_filename;
+ skl->nhlt = vfe_skl_nhlt_init(&vdev->dev);
+
+ err = vfe_skl_init_hbus(vfe, skl);
+ if (err < 0)
+ return err;
+
+ strcpy(skl->tplg_name, "5a98-INTEL-NHLT-GPA-11-tplg.bin");
+
+ err = vfe_skl_init_dsp(skl);
+ if (err < 0)
+ return err;
+
+ err = vfe_platform_register(vfe, &vdev->dev);
+ if (err < 0)
+ return err;
+
+ err = vfe_machine_device_register(vfe, skl);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int vfe_init(struct virtio_device *vdev)
+{
+ struct snd_skl_vfe *vfe;
+ int ret;
+ struct virtqueue *vqs[SKL_VIRTIO_NUM_OF_VQS];
+ vq_callback_t *cbs[SKL_VIRTIO_NUM_OF_VQS] = {
+ vfe_cmd_tx_done,
+ vfe_cmd_handle_rx,
+ vfe_not_tx_done,
+ vfe_not_handle_rx
+ };
+
+ vfe = devm_kzalloc(&vdev->dev, sizeof(*vfe), GFP_KERNEL);
+ if (!vfe)
+ return -ENOMEM;
+
+ skl_vfe = vfe;
+ vfe->vdev = vdev;
+ vdev->priv = vfe;
+
+ INIT_LIST_HEAD(&vfe->kcontrols_list);
+ INIT_LIST_HEAD(&vfe->substr_info_list);
+
+ /* find virt queue for vfe to send/receive IPC message. */
+ ret = virtio_find_vqs(vfe->vdev, SKL_VIRTIO_NUM_OF_VQS,
+ vqs, cbs, vfe_skl_vq_names, NULL);
+ if (ret) {
+ dev_err(&vdev->dev, "error: find vqs fail with %d\n", ret);
+ return ret;
+ }
+
+ /* virtques */
+ vfe->ipc_cmd_tx_vq = vqs[SKL_VIRTIO_IPC_CMD_TX_VQ];
+ vfe->ipc_cmd_rx_vq = vqs[SKL_VIRTIO_IPC_CMD_RX_VQ];
+ vfe->ipc_not_tx_vq = vqs[SKL_VIRTIO_IPC_NOT_TX_VQ];
+ vfe->ipc_not_rx_vq = vqs[SKL_VIRTIO_IPC_NOT_RX_VQ];
+
+ INIT_WORK(&vfe->posn_update_work, vfe_posn_update);
+
+ virtio_device_ready(vdev);
+
+ vfe->send_dsp_ipc_msg = vfe_send_dsp_ipc_msg;
+ vfe->notify_machine_probe = vfe_wrap_native_driver;
+ ret = vfe_skl_init(vdev);
+ if (ret < 0)
+ return ret;
+
+ vfe->pos_not = devm_kmalloc(&vdev->dev,
+ sizeof(*vfe->pos_not), GFP_KERNEL);
+ if (!vfe->pos_not)
+ return -ENOMEM;
+
+ vfe_send_pos_request(vfe, vfe->pos_not);
+
+ return 0;
+}
+
+/*
+ * Probe and remove.
+ */
+static int vfe_probe(struct virtio_device *vdev)
+{
+ struct device *dev;
+ int ret;
+
+ dev = &vdev->dev;
+ dev->coherent_dma_mask = DMA_BIT_MASK(64);
+ dev->dma_mask = &dev->coherent_dma_mask;
+
+ ret = vfe_init(vdev);
+ if (ret < 0) {
+ dev_err(&vdev->dev, "failed to init virt frontend %d\n", ret);
+ return ret;
+ }
+
+ dev_info(&vdev->dev, "init virtual frontend success\n");
+ return 0;
+}
+
+static void vfe_remove(struct virtio_device *vdev)
+{
+ struct snd_skl_vfe *vfe = vdev->priv;
+
+ cancel_work_sync(&vfe->posn_update_work);
+ vfe_machine_device_unregister(&vfe->sdev);
+}
+
+//FIXME: remove or implement with error msg that config change is not supported
+static void virtaudio_config_changed(struct virtio_device *vdev)
+{
+}
+
+const struct virtio_device_id id_table[] = {
+ {VIRTIO_ID_AUDIO, VIRTIO_DEV_ANY_ID},
+ {0},
+};
+
+static struct virtio_driver vfe_audio_driver = {
+ .feature_table = NULL,
+ .feature_table_size = 0,
+ .driver.name = KBUILD_MODNAME,
+ .driver.owner = THIS_MODULE,
+ .id_table = id_table,
+ .probe = vfe_probe,
+ .remove = vfe_remove,
+ .config_changed = virtaudio_config_changed,
+};
+
+module_virtio_driver(vfe_audio_driver);
+
+MODULE_DEVICE_TABLE(virtio, id_table);
+MODULE_DESCRIPTION("Intel Broxton Virtio FE Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-fe.h b/sound/soc/intel/skylake/virtio/skl-virtio-fe.h
new file mode 100644
index 000000000000..f49977e9ce33
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-fe.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * skl-virtio-fe.h -- Virtio FE driver header
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ *
+ * definitions/declarations for virtio FE driver
+ */
+
+#ifndef __SOUND_SOC_SKL_VIRTIO_FE_H
+#define __SOUND_SOC_SKL_VIRTIO_FE_H
+
+#include "skl-virtio-common.h"
+
+#define VFE_MSG_MSEC_TIMEOUT 100
+
+struct vfe_substream_info {
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+ int direction;
+
+ u64 hw_ptr;
+ struct list_head list;
+};
+
+struct vfe_kcontrol {
+ struct snd_kcontrol *kcontrol;
+ struct list_head list;
+
+ snd_kcontrol_put_t *put;
+};
+
+struct snd_skl_vfe {
+ struct skl sdev;
+ struct virtio_device *vdev;
+
+ struct ipc_message *msg;
+ struct vfe_hw_pos_request *pos_not;
+
+ /* position update work */
+ struct work_struct posn_update_work;
+
+ /* IPC cmd from frontend to backend */
+ struct virtqueue *ipc_cmd_tx_vq;
+ /* IPC cmd reply from backend to frontend */
+ struct virtqueue *ipc_cmd_rx_vq;
+ /* IPC notification from backend to frontend */
+ struct virtqueue *ipc_not_rx_vq;
+ /* IPC notification reply from frontend to backend */
+ struct virtqueue *ipc_not_tx_vq;
+
+ struct list_head kcontrols_list;
+ struct list_head substr_info_list;
+
+ int (*send_dsp_ipc_msg)(struct snd_skl_vfe *vfe,
+ struct ipc_message *msg);
+ int (*notify_machine_probe)(struct snd_skl_vfe *vfe,
+ struct platform_device *pdev, struct snd_soc_card *card);
+};
+
+#endif
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-miscdev.c b/sound/soc/intel/skylake/virtio/skl-virtio-miscdev.c
new file mode 100644
index 000000000000..fa972782b94d
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-miscdev.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// skl-virtio-miscdev.c -- Miscellaneous Virtio device for BE service
+//
+// Copyright (C) 2018 Intel Corporation.
+//
+// Authors: Furtak, Pawel <pawel.furtak@intel.com>
+// Janca, Grzegorz <grzegorz.janca@intel.com>
+//
+// This module registers a device node /dev/vbs_k_audio, that handle
+// the communication between Device Model and the virtio backend service.
+// The device model can control the backend to : set the status,
+// set the vq account and etc. The config of the DM and VBS must be accordance.
+
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+
+#include "skl-virtio-be.h"
+#include "../skl-sst-ipc.h"
+
+
+static struct virtio_miscdev *virtio_audio;
+static int snd_audio_virtio_miscdev_register(struct device *dev, void *data,
+ struct virtio_miscdev **va);
+static int snd_audio_virtio_miscdev_unregister(void);
+
+static struct virtio_miscdev *get_virtio_audio(void)
+{
+ return virtio_audio;
+}
+
+void *snd_skl_get_virtio_audio(void)
+{
+ struct virtio_miscdev *vaudio = get_virtio_audio();
+
+ if (vaudio)
+ return vaudio->priv;
+
+ return NULL;
+}
+
+/* find client from client ID */
+static struct snd_skl_vbe_client *vbe_client_find(struct skl *sdev,
+ int client_id)
+{
+ struct snd_skl_vbe_client *client;
+ struct snd_skl_vbe *vbe;
+
+ list_for_each_entry(vbe, &sdev->vbe_list, list) {
+ list_for_each_entry(client, &vbe->client_list, list) {
+ if (client_id == client->vhm_client_id)
+ return client;
+ }
+ }
+
+ return NULL;
+}
+
+static int handle_kick(int client_id, unsigned long *ioreqs_map)
+{
+ struct vhm_request *req;
+ struct snd_skl_vbe_client *client;
+ struct snd_skl_vbe *vbe;
+ struct skl *sdev = snd_skl_get_virtio_audio();
+ int i, handle;
+
+ if (!sdev) {
+ pr_err("error: no BE registered for SOF!\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(sdev->skl_sst->dev, "virtio audio kick handling!\n");
+
+ /* get the client this notification is for/from? */
+ client = vbe_client_find(sdev, client_id);
+ if (!client) {
+ dev_err(sdev->skl_sst->dev, "Ooops! client %d not found!\n",
+ client_id);
+ return -EINVAL;
+ }
+ vbe = client->vbe;
+
+ /* go through all vcpu for the valid request buffer */
+ for (i = 0; i < client->max_vcpu; i++) {
+ req = &client->req_buf[i];
+ handle = 0;
+
+ /* ignore if not processing state */
+ if (atomic_read(&req->processed) != REQ_STATE_PROCESSING)
+ continue;
+
+ dev_dbg(sdev->skl_sst->dev,
+ "ioreq type %d, direction %d, addr 0x%llx, size 0x%llx, value 0x%x\n",
+ req->type,
+ req->reqs.pio_request.direction,
+ req->reqs.pio_request.address,
+ req->reqs.pio_request.size,
+ req->reqs.pio_request.value);
+
+ if (req->reqs.pio_request.direction == REQUEST_READ) {
+ /*
+ * currently we handle kick only,
+ * so read will return 0
+ */
+ req->reqs.pio_request.value = 0;
+ } else {
+ req->reqs.pio_request.value >= 0 ?
+ (handle = 1) : (handle = 0);
+ }
+
+ atomic_set(&req->processed, REQ_STATE_COMPLETE);
+ acrn_ioreq_complete_request(client->vhm_client_id, i);
+
+ /* handle VQ kick if needed */
+ if (handle)
+ vbe_skl_handle_kick(vbe, req->reqs.pio_request.value);
+ }
+
+ return 0;
+}
+
+int snd_skl_virtio_register_vbe(struct skl *sdev, struct snd_skl_vbe **svbe)
+{
+ struct snd_skl_vbe *vbe;
+ struct virtio_vq_info *vqs;
+ struct device *dev = &sdev->pci->dev;
+ int i;
+
+ vbe = devm_kzalloc(dev, sizeof(*vbe), GFP_KERNEL);
+ if (!vbe)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&vbe->client_list);
+ INIT_LIST_HEAD(&vbe->substr_info_list);
+ INIT_LIST_HEAD(&vbe->posn_list);
+ spin_lock_init(&vbe->posn_lock);
+ vbe->sdev = sdev;
+ vbe->dev = dev;
+
+ vqs = vbe->vqs;
+ for (i = 0; i < SKL_VIRTIO_NUM_OF_VQS; i++) {
+ vqs[i].dev = &vbe->dev_info;
+ /*
+ * currently relies on VHM to kick us,
+ * thus vq_notify not used
+ */
+ vqs[i].vq_notify = NULL;
+ }
+
+ /* link dev and vqs */
+ vbe->dev_info.vqs = vqs;
+
+ virtio_dev_init(&vbe->dev_info, vqs, SKL_VIRTIO_NUM_OF_VQS);
+
+ *svbe = vbe;
+
+ return 0;
+}
+
+/*
+ * register vhm client with virtio.
+ * vhm use the client to handle the io access from FE
+ */
+int snd_skl_virtio_register_client(struct snd_skl_vbe *vbe)
+{
+ struct virtio_dev_info *dev_info = &vbe->dev_info;
+ struct vm_info info;
+ struct snd_skl_vbe_client *client;
+ unsigned int vmid;
+ int ret;
+
+ /*
+ * vbs core has mechanism to manage the client
+ * there is no need to handle this in the special BE driver
+ * let's use the vbs core client management later
+ */
+ client = devm_kzalloc(vbe->dev, sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -EINVAL;
+ client->vbe = vbe;
+
+ vmid = dev_info->_ctx.vmid;
+ client->vhm_client_id = acrn_ioreq_create_client(vmid, handle_kick,
+ "snd_skl_vbe kick init\n");
+ if (client->vhm_client_id < 0) {
+ dev_err(vbe->dev, "failed to create client of acrn ioreq!\n");
+ return client->vhm_client_id;
+ }
+
+ ret = acrn_ioreq_add_iorange(client->vhm_client_id, REQ_PORTIO,
+ dev_info->io_range_start,
+ dev_info->io_range_start +
+ dev_info->io_range_len - 1);
+ if (ret < 0) {
+ dev_err(vbe->dev, "failed to add iorange to acrn ioreq!\n");
+ goto err;
+ }
+
+ /*
+ * setup the vm information, such as max_vcpu and max_gfn
+ * BE need this information to handle the vqs
+ */
+ ret = vhm_get_vm_info(vmid, &info);
+ if (ret < 0) {
+ dev_err(vbe->dev, "failed in vhm_get_vm_info!\n");
+ goto err;
+ }
+ client->max_vcpu = info.max_vcpu;
+
+ /* TODO: comment what this is doing */
+ client->req_buf = acrn_ioreq_get_reqbuf(client->vhm_client_id);
+ if (!client->req_buf) {
+ dev_err(vbe->dev, "failed in acrn_ioreq_get_reqbuf!\n");
+ goto err;
+ }
+
+ /* just attach once as vhm will kick kthread */
+ acrn_ioreq_attach_client(client->vhm_client_id, 0);
+
+ /* complete client init and add to list */
+ list_add(&client->list, &vbe->client_list);
+
+ return 0;
+err:
+ acrn_ioreq_destroy_client(client->vhm_client_id);
+ return -EINVAL;
+}
+
+static int snd_skl_virtio_open(struct file *f, void *data)
+{
+ struct skl *sdev = (struct skl *)data;
+ struct snd_skl_vbe *vbe;
+ int ret;
+
+ ret = snd_skl_virtio_register_vbe(sdev, &vbe);
+ if (ret)
+ return ret;
+
+ /*
+ * link to sdev->vbe_list
+ * Maybe virtio_miscdev managing the list is more reasonable.
+ * Let's use sdev to manage the FE audios now.
+ */
+ list_add(&vbe->list, &sdev->vbe_list);
+ f->private_data = vbe;
+
+ return 0;
+}
+
+static long snd_skl_virtio_ioctl(struct file *f, void *data, unsigned int ioctl,
+ unsigned long arg)
+{
+ struct snd_skl_vbe *vbe = f->private_data;
+ void __user *argp = (void __user *)arg;
+ int ret;
+
+ switch (ioctl) {
+ case VBS_SET_DEV:
+ ret = virtio_dev_ioctl(&vbe->dev_info, ioctl, argp);
+ if (!ret)
+ vbe->vmid = vbe->dev_info._ctx.vmid;
+ break;
+ case VBS_SET_VQ:
+ ret = virtio_vqs_ioctl(&vbe->dev_info, ioctl, argp);
+ if (ret)
+ return ret;
+
+ ret = snd_skl_virtio_register_client(vbe);
+ if (ret)
+ return ret;
+ /*
+ * TODO: load tplg and send to FE here
+ *
+ * The better method is FE driver send FE-tplg id
+ * and request FE-tplg.
+ * Then BE loads the corresponding tplg based on
+ * the FE-tplg id and send to FE driver.
+ */
+ break;
+ default:
+ pr_err("%s: _snd_virtio_miscdevice Unsupported ioctl cmd[%d].\n",
+ __func__, ioctl);
+ return -ENOIOCTLCMD;
+ }
+
+ return ret;
+}
+
+static int snd_skl_virtio_release(struct file *f, void *data)
+{
+ struct snd_skl_vbe *vbe = f->private_data;
+
+ list_del(&vbe->list);
+ devm_kfree(vbe->sdev->skl_sst->dev, vbe);
+ f->private_data = NULL;
+
+ return 0;
+}
+
+/**
+ * snd_soc_skl_virtio_miscdev_register() - init the virtio be audio driver
+ * @sdev: the skl structure
+ *
+ * This function registers the misc device, which will be used
+ * by the user space to communicate with the audio driver.
+ *
+ * Return: 0 for success or negative value for err
+ */
+int snd_soc_skl_virtio_miscdev_register(struct skl *sdev)
+{
+ struct virtio_miscdev *vaudio;
+ int ret;
+
+ ret = snd_audio_virtio_miscdev_register(sdev->skl_sst->dev,
+ sdev, &vaudio);
+ if (ret) {
+ dev_err(vaudio->dev, "failed to register misc device %d\n",
+ ret);
+ return ret;
+ }
+
+ INIT_LIST_HEAD(&sdev->vbe_list);
+
+ vaudio->open = snd_skl_virtio_open;
+ vaudio->ioctl = snd_skl_virtio_ioctl;
+ vaudio->release = snd_skl_virtio_release;
+
+ dev_info(vaudio->dev, "register misc device success\n");
+ return 0;
+}
+EXPORT_SYMBOL(snd_soc_skl_virtio_miscdev_register);
+
+/**
+ * snd_soc_skl_virtio_miscdev_unregister() - release the virtio be audio driver
+ *
+ * This function deregisters the misc device, and free virtio_miscdev
+ *
+ */
+int snd_soc_skl_virtio_miscdev_unregister(void)
+{
+ return snd_audio_virtio_miscdev_unregister();
+}
+EXPORT_SYMBOL(snd_soc_skl_virtio_miscdev_unregister);
+
+static int vbs_audio_open(struct inode *inode, struct file *f)
+{
+ struct virtio_miscdev *vaudio = get_virtio_audio();
+
+ if (!vaudio)
+ return -ENODEV; /* This should never happen */
+
+ dev_dbg(vaudio->dev, "virtio audio open\n");
+ if (vaudio->open)
+ return vaudio->open(f, virtio_audio->priv);
+
+ return 0;
+}
+
+static long vbs_audio_ioctl(struct file *f, unsigned int ioctl,
+ unsigned long arg)
+{
+ struct virtio_miscdev *vaudio = get_virtio_audio();
+
+ if (!vaudio)
+ return -ENODEV; /* This should never happen */
+
+ dev_dbg(vaudio->dev, "virtio audio ioctl\n");
+ if (vaudio->ioctl)
+ return vaudio->ioctl(f, vaudio->priv, ioctl, arg);
+ else
+ return -ENXIO;
+}
+
+static int vbs_audio_release(struct inode *inode, struct file *f)
+{
+ struct virtio_miscdev *vaudio = get_virtio_audio();
+
+ if (!vaudio)
+ return -ENODEV; /* This should never happen */
+
+ dev_dbg(vaudio->dev, "release virtio audio\n");
+
+ if (vaudio->release)
+ vaudio->release(f, vaudio->priv);
+
+ return 0;
+}
+
+static const struct file_operations vbs_audio_fops = {
+ .owner = THIS_MODULE,
+ .release = vbs_audio_release,
+ .unlocked_ioctl = vbs_audio_ioctl,
+ .open = vbs_audio_open,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice vbs_audio_k = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "vbs_k_audio",
+ .fops = &vbs_audio_fops,
+};
+
+static int snd_audio_virtio_miscdev_register(struct device *dev, void *data,
+ struct virtio_miscdev **va)
+{
+ struct virtio_miscdev *vaudio;
+ int ret;
+
+ ret = misc_register(&vbs_audio_k);
+ if (ret) {
+ dev_err(dev, "misc device register failed %d\n", ret);
+ return ret;
+ }
+
+ vaudio = kzalloc(sizeof(*vaudio), GFP_KERNEL);
+ if (!vaudio) {
+ misc_deregister(&vbs_audio_k);
+ return -ENOMEM;
+ }
+
+ vaudio->priv = data;
+ vaudio->dev = dev;
+ virtio_audio = vaudio;
+ *va = vaudio;
+
+ return 0;
+}
+
+static int snd_audio_virtio_miscdev_unregister(void)
+{
+ if (virtio_audio) {
+ misc_deregister(&vbs_audio_k);
+ kfree(virtio_audio);
+ virtio_audio = NULL;
+ }
+
+ return 0;
+}
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio-sst.c b/sound/soc/intel/skylake/virtio/skl-virtio-sst.c
new file mode 100644
index 000000000000..c3b3b4d6cd02
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio-sst.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// skl-virtio-sst.c -- DSP paravirtualization for SKL architecture
+//
+// Copyright (C) 2018 Intel Corporation.
+//
+// Authors: Furtak, Pawel <pawel.furtak@intel.com>
+// Janca, Grzegorz <grzegorz.janca@intel.com>
+//
+// Dummy implementation of HDA DSP library used on FE side.
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/device.h>
+
+#include "skl-virtio-fe.h"
+#include "skl-virtio.h"
+#include "../../common/sst-dsp.h"
+#include "../skl-fwlog.h"
+#include "../skl-sst-ipc.h"
+
+#define BXT_ADSP_SRAM0_BASE 0x80000
+
+#define BXT_ADSP_SRAM1_BASE 0xA0000
+
+#define BXT_ADSP_FW_BIN_HDR_OFFSET 0x2000
+
+static unsigned int vfe_get_errorcode(struct sst_dsp *ctx)
+{
+ return 0;
+}
+
+int
+vfe_load_library(struct sst_dsp *ctx, struct skl_lib_info *linfo, int lib_count)
+{
+ struct firmware stripped_fw;
+ struct skl_sst *skl = ctx->thread_context;
+ int ret = 0, i;
+
+ /* library indices start from 1 to N. 0 represents base FW */
+ for (i = 1; i < lib_count; i++) {
+ ret = skl_prepare_lib_load(skl, &skl->lib_info[i], &stripped_fw,
+ BXT_ADSP_FW_BIN_HDR_OFFSET, i);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int vfe_load_base_firmware(struct sst_dsp *ctx)
+{
+ struct skl_sst *skl = ctx->thread_context;
+ int ret;
+
+ dev_dbg(ctx->dev, "Request FW name:%s\n", ctx->fw_name);
+ if (ctx->fw == NULL) {
+ ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev);
+ if (ret < 0) {
+ dev_err(ctx->dev, "Request firmware failed %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* prase uuids on first boot */
+ if (skl->is_first_boot) {
+ ret = snd_skl_parse_uuids(ctx, ctx->fw,
+ BXT_ADSP_FW_BIN_HDR_OFFSET, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+
+
+int vfe_schedule_dsp_D0i3(struct sst_dsp *ctx)
+{
+ return 0;
+}
+
+int vfe_set_dsp_D0i0(struct sst_dsp *ctx)
+{
+ return 0;
+}
+
+static int vfe_set_dsp_D0(struct sst_dsp *ctx, unsigned int core_id)
+{
+ return 0;
+}
+
+static int vfe_set_dsp_D3(struct sst_dsp *ctx, unsigned int core_id)
+{
+ return 0;
+}
+
+irqreturn_t vfe_dsp_irq_thread_handler(int irq, void *context)
+{
+ return IRQ_HANDLED;
+}
+
+irqreturn_t vfe_dsp_sst_interrupt(int irq, void *dev_id)
+{
+ return IRQ_HANDLED;
+}
+
+void vfe_enable_miscbdcge(struct device *dev, bool enable)
+{
+}
+
+void vfe_clock_power_gating(struct device *dev, bool enable)
+{
+}
+
+static const struct skl_dsp_fw_ops vfe_fw_ops = {
+ .set_state_D0 = vfe_set_dsp_D0,
+ .set_state_D3 = vfe_set_dsp_D3,
+ .set_state_D0i3 = vfe_schedule_dsp_D0i3,
+ .set_state_D0i0 = vfe_set_dsp_D0i0,
+ .load_fw = vfe_load_base_firmware,
+ .get_fw_errcode = vfe_get_errorcode,
+ .load_library = vfe_load_library,
+};
+
+static struct sst_ops vfe_ops = {
+ .irq_handler = vfe_dsp_sst_interrupt,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .ram_read = sst_memcpy_fromio_32,
+ .ram_write = sst_memcpy_toio_32,
+ .free = skl_dsp_free,
+};
+
+static struct sst_dsp_device vfe_dev = {
+ .thread = vfe_dsp_irq_thread_handler,
+ .ops = &vfe_ops,
+};
+
+void vfe_ipc_tx_data_copy(struct ipc_message *msg, char *tx_data,
+ size_t tx_size)
+{
+ if (tx_size)
+ memcpy(msg->tx_data, tx_data, tx_size);
+}
+
+static void vfe_ipc_tx_msg(struct sst_generic_ipc *ipc, struct ipc_message *msg)
+{
+ struct skl_sst *skl_sst = container_of(ipc, struct skl_sst, ipc);
+ struct snd_skl_vfe *vfe = dev_get_drvdata(skl_sst->dev);
+
+ vfe->send_dsp_ipc_msg(vfe, msg);
+}
+
+int vfe_ipc_init(struct device *dev, struct skl_sst *skl)
+{
+ struct sst_generic_ipc *ipc;
+ int err;
+
+ ipc = &skl->ipc;
+ ipc->dsp = skl->dsp;
+ ipc->dev = dev;
+
+ ipc->tx_data_max_size = SKL_ADSP_W1_SZ;
+ ipc->rx_data_max_size = SKL_ADSP_W0_UP_SZ;
+
+ err = sst_ipc_init(ipc);
+ if (err)
+ return err;
+
+ ipc->ops.tx_msg = vfe_ipc_tx_msg;
+ ipc->ops.tx_data_copy = vfe_ipc_tx_data_copy;
+
+ return 0;
+}
+
+struct sst_dsp *vfe_dsp_ctx_init(struct device *dev,
+ struct sst_dsp_device *sst_dev, int irq)
+{
+ int ret;
+ struct sst_dsp *sst;
+
+ sst = devm_kzalloc(dev, sizeof(*sst), GFP_KERNEL);
+ if (sst == NULL)
+ return NULL;
+
+ spin_lock_init(&sst->spinlock);
+ mutex_init(&sst->mutex);
+ sst->dev = dev;
+ sst->sst_dev = sst_dev;
+ sst->irq = irq;
+ sst->ops = sst_dev->ops;
+ sst->thread_context = sst_dev->thread_context;
+
+ /* Initialise SST Audio DSP */
+ if (sst->ops->init) {
+ ret = sst->ops->init(sst, NULL);
+ if (ret < 0)
+ return NULL;
+ }
+
+ return sst;
+}
+
+int vfe_sst_ctx_init(struct device *dev, int irq, const char *fw_name,
+ struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp,
+ struct sst_dsp_device *skl_dev)
+{
+ struct skl_sst *skl;
+ struct sst_dsp *sst;
+
+ skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL);
+ if (skl == NULL)
+ return -ENOMEM;
+
+ skl->dev = dev;
+ skl_dev->thread_context = skl;
+ INIT_LIST_HEAD(&skl->uuid_list);
+ skl->dsp = vfe_dsp_ctx_init(dev, skl_dev, irq);
+ if (!skl->dsp) {
+ dev_err(skl->dev, "%s: no device\n", __func__);
+ return -ENODEV;
+ }
+
+ sst = skl->dsp;
+ sst->fw_name = fw_name;
+ sst->dsp_ops = dsp_ops;
+ init_waitqueue_head(&skl->mod_load_wait);
+ INIT_LIST_HEAD(&sst->module_list);
+
+ skl->is_first_boot = true;
+ if (dsp)
+ *dsp = skl;
+
+ return 0;
+}
+
+int vfe_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq,
+ const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_sst **dsp, void *ptr)
+{
+ struct skl_sst *skl;
+ struct sst_dsp *sst;
+ int ret;
+
+ ret = vfe_sst_ctx_init(dev, irq, fw_name, dsp_ops, dsp, &vfe_dev);
+ if (ret < 0) {
+ dev_err(dev, "%s: no device\n", __func__);
+ return ret;
+ }
+
+ skl = *dsp;
+ sst = skl->dsp;
+ sst->fw_ops = vfe_fw_ops;
+ sst->addr.lpe = mmio_base;
+ sst->addr.shim = mmio_base;
+ sst->addr.sram0_base = BXT_ADSP_SRAM0_BASE;
+ sst->addr.sram1_base = BXT_ADSP_SRAM1_BASE;
+ sst->addr.w0_stat_sz = SKL_ADSP_W0_STAT_SZ;
+ sst->addr.w0_up_sz = SKL_ADSP_W0_UP_SZ;
+
+ skl->enable_miscbdcge = vfe_enable_miscbdcge;
+ skl->clock_power_gating = vfe_clock_power_gating;
+
+ ret = vfe_ipc_init(dev, skl);
+ if (ret) {
+ skl_dsp_free(sst);
+ return ret;
+ }
+
+ skl->boot_complete = false;
+ init_waitqueue_head(&skl->boot_wait);
+ skl->d0i3.state = SKL_DSP_D0I3_NONE;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vfe_sst_dsp_init);
+
+int vfe_sst_init_fw(struct device *dev, struct skl_sst *ctx)
+{
+ int ret;
+ struct sst_dsp *sst = ctx->dsp;
+
+ ret = sst->fw_ops.load_fw(sst);
+ if (ret < 0) {
+ dev_err(dev, "Load base fw failed: %x\n", ret);
+ return ret;
+ }
+
+ if (ctx->lib_count > 1) {
+ ret = sst->fw_ops.load_library(sst, ctx->lib_info,
+ ctx->lib_count);
+ if (ret < 0) {
+ dev_err(dev, "Load Library failed : %x\n", ret);
+ return ret;
+ }
+ }
+ ctx->is_first_boot = false;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vfe_sst_init_fw);
+
+void vfe_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx)
+{
+}
+EXPORT_SYMBOL_GPL(vfe_sst_dsp_cleanup);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Virtio FE IPC driver");
diff --git a/sound/soc/intel/skylake/virtio/skl-virtio.h b/sound/soc/intel/skylake/virtio/skl-virtio.h
new file mode 100644
index 000000000000..935652be541a
--- /dev/null
+++ b/sound/soc/intel/skylake/virtio/skl-virtio.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * skl-virtio.h -- SKL Virtio driver header
+ *
+ * Copyright (C) 2018 Intel Corporation.
+ *
+ * definitions/declarations for skl virtio FE/BE driver
+ */
+
+#ifndef __SOUND_SOC_SKL_VIRTIO_H
+#define __SOUND_SOC_SKL_VIRTIO_H
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_VIRTIO_BE)
+
+extern int snd_soc_skl_virtio_miscdev_register(struct skl *sdev);
+extern void skl_notify_stream_update(struct hdac_bus *bus,
+ struct snd_pcm_substream *substr);
+#else
+
+#define snd_soc_skl_virtio_miscdev_register(...) {}
+#define skl_notify_stream_update(...) {}
+
+#endif
+
+#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKYLAKE_VIRTIO_FE)
+
+extern int vfe_sst_dsp_init(struct device *dev, void __iomem *mmio_base,
+ int irq, const char *fw_name, struct skl_dsp_loader_ops dsp_ops,
+ struct skl_sst **dsp, void *ptr);
+extern int vfe_sst_init_fw(struct device *dev, struct skl_sst *ctx);
+extern void vfe_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx);
+
+#endif
+
+#endif //__SOUND_SOC_SKL_VIRTIO_H
--
https://clearlinux.org