[PATCH v2 00/12] [RESEND] ASoC: SOF DSP virtualisation
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host from a file, specified on the QEMU command line. This solution is designed to use advanced DSP functionality, available on the host. In case no DSP is available on the host, a fall-back should be provided in the future to the pure audio virtualisation standard, currently developed by OASIS.
Extensive documentation is available at [1].
I'd like to have at least a tentative approval on this series to send vhost patches 3 and 9 to virtualisation lists / maintainers for review and eventual merging.
v2: one patch merged, one patch resubmitted separately, otherwise no changes.
[1] https://thesofproject.github.io/latest/developer_guides/virtualization/virtu...
Guennadi Liakhovetski (12): ASoC: add function parameters to enable forced path pruning ASoC: SOF: extract firmware-related operation into a function ASoC: SOF: VirtIO: make a function global vhost: convert VHOST_VSOCK_SET_RUNNING to a generic ioctl ASoC: SOF: support IPC with immediate response ASoC: SOF: add a power status IPC ASoC: SOF: add two helper lookup functions ASoC: SOF: add a VirtIO DSP driver ASoC: SOF: add a vhost driver: sound part vhost: add an SOF DSP driver ASoC: SOF: VirtIO: free guest pipelines upon termination ASoC: SOF: VirtIO: enable simultaneous playback and capture
drivers/vhost/Kconfig | 7 + drivers/vhost/Makefile | 5 + drivers/vhost/dsp.c | 731 ++++++++++++++++++++++ include/sound/soc-dpcm.h | 28 +- include/sound/soc-topology.h | 3 + include/sound/sof.h | 4 + include/sound/sof/header.h | 3 + include/sound/sof/topology.h | 1 + include/sound/sof/virtio.h | 206 +++++++ include/uapi/linux/vhost.h | 9 +- include/uapi/linux/vhost_types.h | 7 + include/uapi/linux/virtio_ids.h | 1 + sound/soc/soc-compress.c | 2 +- sound/soc/soc-dapm.c | 8 +- sound/soc/soc-pcm.c | 98 ++- sound/soc/sof/Kconfig | 8 + sound/soc/sof/Makefile | 8 + sound/soc/sof/core.c | 114 ++-- sound/soc/sof/ipc.c | 39 +- sound/soc/sof/loader.c | 4 + sound/soc/sof/ops.h | 10 +- sound/soc/sof/pcm.c | 13 +- sound/soc/sof/pm.c | 4 + sound/soc/sof/sof-audio.c | 33 + sound/soc/sof/sof-audio.h | 21 + sound/soc/sof/sof-priv.h | 52 ++ sound/soc/sof/topology.c | 71 ++- sound/soc/sof/vhost-be.c | 1248 ++++++++++++++++++++++++++++++++++++++ sound/soc/sof/virtio-fe.c | 922 ++++++++++++++++++++++++++++ 29 files changed, 3552 insertions(+), 108 deletions(-) create mode 100644 drivers/vhost/dsp.c create mode 100644 include/sound/sof/virtio.h create mode 100644 sound/soc/sof/vhost-be.c create mode 100644 sound/soc/sof/virtio-fe.c
This is a preparation for the host part of a virtualised VirtIO audio host-guest driver pair. It adds a "mode" parameter to soc_dpcm_runtime_update() to allow it to be used when stopping streaming in a virtual machine, which requires forced DPCM audio path pruning.
For audio virtualisation the host side driver will be using the vhost API, i.e. it will run completely in the kernel. When a guest begins to stream audio, the vhost calls snd_soc_runtime_activate() and soc_dpcm_runtime_update() to activate an audio path and update audio routing. When streaming is stopped, the vhost driver calls soc_dpcm_runtime_update() and snd_soc_runtime_deactivate(). The latter doesn't work at the moment, because the DPCM doesn't recognise the path as inactive. We address this by adding a "mode" parameter to soc_dpcm_runtime_update(). If virtualisation isn't used, the current behaviour isn't affected.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/sound/soc-dpcm.h | 28 ++++++++++++++++---- sound/soc/soc-compress.c | 2 +- sound/soc/soc-dapm.c | 8 +++--- sound/soc/soc-pcm.c | 67 +++++++++++++++++++++++++++++++++--------------- 4 files changed, 74 insertions(+), 31 deletions(-)
diff --git a/include/sound/soc-dpcm.h b/include/sound/soc-dpcm.h index 0f6c50b..b961c06 100644 --- a/include/sound/soc-dpcm.h +++ b/include/sound/soc-dpcm.h @@ -61,6 +61,23 @@ enum snd_soc_dpcm_trigger { SND_SOC_DPCM_TRIGGER_BESPOKE, };
+/** + * enum snd_soc_dpcm_update_mode - mode for calling soc_dpcm_runtime_update() + * + * @SND_SOC_DPCM_UPDATE_FULL: default mode, used for mux, mixer, and + * volume widgets + * @SND_SOC_DPCM_UPDATE_NEW_ONLY: a pipeline is starting. Skip checking + * for old paths. + * @SND_SOC_DPCM_UPDATE_OLD_ONLY: a pipeline is shutting down. Skip + * checking for new paths, force old path + * pruning. + */ +enum snd_soc_dpcm_update_mode { + SND_SOC_DPCM_UPDATE_FULL, + SND_SOC_DPCM_UPDATE_NEW_ONLY, + SND_SOC_DPCM_UPDATE_OLD_ONLY, +}; + /* * Dynamic PCM link * This links together a FE and BE DAI at runtime and stores the link @@ -133,7 +150,8 @@ struct snd_pcm_substream * snd_soc_dpcm_get_substream(struct snd_soc_pcm_runtime *be, int stream);
/* update audio routing between PCMs and any DAI links */ -int snd_soc_dpcm_runtime_update(struct snd_soc_card *card); +int snd_soc_dpcm_runtime_update(struct snd_soc_card *card, + enum snd_soc_dpcm_update_mode mode);
#ifdef CONFIG_DEBUG_FS void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd); @@ -143,11 +161,11 @@ static inline void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) } #endif
-int dpcm_path_get(struct snd_soc_pcm_runtime *fe, - int stream, struct snd_soc_dapm_widget_list **list_); +int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_); void dpcm_path_put(struct snd_soc_dapm_widget_list **list); -int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, - int stream, struct snd_soc_dapm_widget_list **list, int new); +int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list, bool new, bool force_prune); int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream); int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream); void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream); diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 50062eb..66d7e78 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -162,7 +162,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) dev_dbg(fe->dev, "Compress ASoC: %s no valid %s route\n", fe->dai_link->name, stream ? "capture" : "playback"); /* calculate valid and active FE <-> BE dpcms */ - dpcm_process_paths(fe, stream, &list, 1); + dpcm_process_paths(fe, stream, &list, true, false); fe->dpcm[stream].runtime = fe_substream->runtime;
fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 679ed60..a6dd99e 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2297,7 +2297,7 @@ int snd_soc_dapm_mux_update_power(struct snd_soc_dapm_context *dapm, card->update = NULL; mutex_unlock(&card->dapm_mutex); if (ret > 0) - snd_soc_dpcm_runtime_update(card); + snd_soc_dpcm_runtime_update(card, SND_SOC_DPCM_UPDATE_FULL); return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_mux_update_power); @@ -2362,7 +2362,7 @@ int snd_soc_dapm_mixer_update_power(struct snd_soc_dapm_context *dapm, card->update = NULL; mutex_unlock(&card->dapm_mutex); if (ret > 0) - snd_soc_dpcm_runtime_update(card); + snd_soc_dpcm_runtime_update(card, SND_SOC_DPCM_UPDATE_FULL); return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_mixer_update_power); @@ -3402,7 +3402,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, mutex_unlock(&card->dapm_mutex);
if (ret > 0) - snd_soc_dpcm_runtime_update(card); + snd_soc_dpcm_runtime_update(card, SND_SOC_DPCM_UPDATE_FULL);
return change; } @@ -3507,7 +3507,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_unlock(&card->dapm_mutex);
if (ret > 0) - snd_soc_dpcm_runtime_update(card); + snd_soc_dpcm_runtime_update(card, SND_SOC_DPCM_UPDATE_FULL);
return change; } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 289aebc..1a841e8 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1530,14 +1530,14 @@ static bool dpcm_be_is_active(struct snd_soc_dpcm *dpcm, int stream, }
static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, - struct snd_soc_dapm_widget_list **list_) + struct snd_soc_dapm_widget_list **list_, bool force) { struct snd_soc_dpcm *dpcm; int prune = 0;
/* Destroy any old FE <--> BE connections */ for_each_dpcm_be(fe, stream, dpcm) { - if (dpcm_be_is_active(dpcm, stream, *list_)) + if (!force && dpcm_be_is_active(dpcm, stream, *list_)) continue;
dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", @@ -1612,12 +1612,13 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, * FE substream. */ int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, - int stream, struct snd_soc_dapm_widget_list **list, int new) + int stream, struct snd_soc_dapm_widget_list **list, + bool new, bool force_prune) { if (new) return dpcm_add_paths(fe, stream, list); else - return dpcm_prune_paths(fe, stream, list); + return dpcm_prune_paths(fe, stream, list, force_prune); }
void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) @@ -2613,11 +2614,13 @@ static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) return ret; }
-static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream, + bool force) { struct snd_pcm_substream *substream = snd_soc_dpcm_get_substream(fe, stream); enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; + int event = force ? SND_SOC_DAPM_STREAM_STOP : SND_SOC_DAPM_STREAM_NOP; int err;
dev_dbg(fe->dev, "ASoC: runtime %s close on FE %s\n", @@ -2649,7 +2652,7 @@ static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) dev_err(fe->dev,"ASoC: shutdown FE failed %d\n", err);
/* run the stream event for each BE */ - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); + dpcm_dapm_stream_event(fe, stream, event);
return 0; } @@ -2742,7 +2745,8 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) return ret; }
-static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) +static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, bool new, + bool force_prune) { struct snd_soc_dapm_widget_list *list; int stream; @@ -2788,13 +2792,13 @@ static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) }
/* update any playback/capture paths */ - count = dpcm_process_paths(fe, stream, &list, new); + count = dpcm_process_paths(fe, stream, &list, new, force_prune); if (count) { dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); if (new) ret = dpcm_run_update_startup(fe, stream); else - ret = dpcm_run_update_shutdown(fe, stream); + ret = dpcm_run_update_shutdown(fe, stream, force_prune); if (ret < 0) dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n"); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); @@ -2812,25 +2816,46 @@ static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) /* Called by DAPM mixer/mux changes to update audio routing between PCMs and * any DAI links. */ -int snd_soc_dpcm_runtime_update(struct snd_soc_card *card) +int snd_soc_dpcm_runtime_update(struct snd_soc_card *card, + enum snd_soc_dpcm_update_mode mode) { struct snd_soc_pcm_runtime *fe; int ret = 0;
mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + /* shutdown all old paths first */ - for_each_card_rtds(card, fe) { - ret = soc_dpcm_fe_runtime_update(fe, 0); - if (ret) - goto out; - } + if (mode != SND_SOC_DPCM_UPDATE_NEW_ONLY) + /* + * This is entered if mode == FULL or OLD_ONLY. In both cases we + * have to call soc_dpcm_fe_runtime_update() but only in the + * OLD_ONLY case we have to set the "force" (last) parameter to + * "true." + */ + for_each_card_rtds(card, fe) { + /* + * check "old" paths (new = false), only force for + * shutting down. + */ + ret = soc_dpcm_fe_runtime_update(fe, false, + mode == SND_SOC_DPCM_UPDATE_OLD_ONLY); + if (ret) + goto out; + }
/* bring new paths up */ - for_each_card_rtds(card, fe) { - ret = soc_dpcm_fe_runtime_update(fe, 1); - if (ret) - goto out; - } + if (mode != SND_SOC_DPCM_UPDATE_OLD_ONLY) + /* + * This is entered if mode == FULL or NEW_ONLY. In both cases we + * have to call soc_dpcm_fe_runtime_update() with the "force" + * (last) parameter set to "false" + */ + for_each_card_rtds(card, fe) { + /* check "new" paths (new = true), no forcing */ + ret = soc_dpcm_fe_runtime_update(fe, true, false); + if (ret) + goto out; + }
out: mutex_unlock(&card->mutex); @@ -2886,7 +2911,7 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) }
/* calculate valid and active FE <-> BE dpcms */ - dpcm_process_paths(fe, stream, &list, 1); + dpcm_process_paths(fe, stream, &list, true, false);
ret = dpcm_fe_dai_startup(fe_substream); if (ret < 0)
In the VirtIO guest case the SOF will not be dealing with the firmware directly. Extract related functionality into a function to make the separation easier.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/core.c | 85 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 36 deletions(-)
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 91acfae..ca30d67 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -135,6 +135,53 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, * (System Suspend/Runtime Suspend) */
+static int sof_load_and_run_firmware(struct snd_sof_dev *sdev) +{ + /* load the firmware */ + int ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", + ret); + return ret; + } + + sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + + /* + * Boot the firmware. The FW boot status will be modified + * in snd_sof_run_firmware() depending on the outcome. + */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", + ret); + goto fw_run_err; + } + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || + (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { + sdev->dtrace_is_supported = true; + + /* init DMA trace */ + ret = snd_sof_init_trace(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to initialize trace %d\n", + ret); + } + } else { + dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); + } + + return 0; + +fw_run_err: + snd_sof_fw_unload(sdev); + + return ret; +} + static int sof_probe_continue(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; @@ -180,42 +227,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; }
- /* load the firmware */ - ret = snd_sof_load_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", - ret); + ret = sof_load_and_run_firmware(sdev); + if (ret < 0) goto fw_load_err; - } - - sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; - - /* - * Boot the firmware. The FW boot status will be modified - * in snd_sof_run_firmware() depending on the outcome. - */ - ret = snd_sof_run_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", - ret); - goto fw_run_err; - } - - if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || - (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { - sdev->dtrace_is_supported = true; - - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", - ret); - } - } else { - dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); - }
/* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -249,7 +263,6 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
fw_trace_err: snd_sof_free_trace(sdev); -fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: snd_sof_ipc_free(sdev);
sof_ipc_tx_message_unlocked() will be needed for VirtIO code, make it global.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/ipc.c | 7 ++++--- sound/soc/sof/sof-priv.h | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 1c67949..cf57085 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -231,9 +231,9 @@ static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, }
/* send IPC message from host to DSP */ -static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, - void *reply_data, size_t reply_bytes) +int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) { struct snd_sof_dev *sdev = ipc->sdev; struct snd_sof_ipc_msg *msg; @@ -284,6 +284,7 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
return ret; } +EXPORT_SYMBOL(sof_ipc_tx_message_unlocked);
/* send IPC message from host to DSP */ int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index a4b297c..922b671 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -493,6 +493,9 @@ int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); +int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes);
/* * Trace/debug
VHOST_VSOCK_SET_RUNNING is used by the vhost vsock driver to perform crucial VirtQueue initialisation, like assigning .private fields and calling vhost_vq_init_access(), and clean up. However, this ioctl is actually extremely useful for any vhost driver, that doesn't have a side channel to inform it of a status change, e.g. upon a guest reboot. This patch makes that ioctl generic, while preserving its numeric value and also keeping the original alias.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/uapi/linux/vhost.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index 40d028ee..c628103 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -93,6 +93,8 @@ #define VHOST_SET_BACKEND_FEATURES _IOW(VHOST_VIRTIO, 0x25, __u64) #define VHOST_GET_BACKEND_FEATURES _IOR(VHOST_VIRTIO, 0x26, __u64)
+#define VHOST_SET_RUNNING _IOW(VHOST_VIRTIO, 0x61, int) + /* VHOST_NET specific defines */
/* Attach virtio net ring to a raw socket, or tap device. @@ -114,6 +116,6 @@ /* VHOST_VSOCK specific defines */
#define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) -#define VHOST_VSOCK_SET_RUNNING _IOW(VHOST_VIRTIO, 0x61, int) +#define VHOST_VSOCK_SET_RUNNING VHOST_SET_RUNNING
#endif
Usually when an IPC message is sent, we have to wait for a reply from the DSP or from the host in the VirtIO case. However, sometimes in the VirtIO case a response is available immediately. Skip sleeping in such cases.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/ipc.c | 9 +++++---- sound/soc/sof/ops.h | 10 +++++++++- 2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index cf57085..6fa501c1 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -262,6 +262,10 @@ int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
sdev->msg = msg;
+ /* + * If snd_sof_dsp_send_msg() returns a positive number it means, that a + * response is already available, no need to sleep waiting for it + */ ret = snd_sof_dsp_send_msg(sdev, msg); /* Next reply that we receive will be related to this message */ if (!ret) @@ -279,10 +283,7 @@ int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, ipc_log_header(sdev->dev, "ipc tx", msg->header);
/* now wait for completion */ - if (!ret) - ret = tx_wait_done(ipc, msg, reply_data); - - return ret; + return tx_wait_done(ipc, msg, reply_data); } EXPORT_SYMBOL(sof_ipc_tx_message_unlocked);
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index a771500..43bcfbf 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -274,7 +274,15 @@ static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); }
-/* ipc */ +/** + * snd_sof_dsp_send_msg - call sdev ops to send a message + * @sdev: sdev context + * @msg: message to send + * + * Returns < 0 - an error code + * 0 - the message has been sent, wait for a reply + * > 0 - the message has been sent, a reply is already available + */ static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) {
In a virtualised configuration the runtime PM of the host and any guests aren't synchronised. But guests have to be able to tell the host when they suspend and resume and know, whether the host has been runtime suspended since that guests's topology had been sent to the host last time. This is needed to decide whether to re-send the topology again. To support this we add a new PM IPC message SOF_IPC_PM_VFE_POWER_STATUS and a reset counter to track the state of the DSP.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/sound/sof/header.h | 1 + sound/soc/sof/core.c | 2 ++ sound/soc/sof/ipc.c | 2 ++ sound/soc/sof/loader.c | 4 ++++ sound/soc/sof/sof-priv.h | 4 ++++ 5 files changed, 13 insertions(+)
diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h index b794795..1aaccb5a 100644 --- a/include/sound/sof/header.h +++ b/include/sound/sof/header.h @@ -77,6 +77,7 @@ #define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006) #define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007) #define SOF_IPC_PM_GATE SOF_CMD_TYPE(0x008) +#define SOF_IPC_PM_VFE_POWER_STATUS SOF_CMD_TYPE(0x010)
/* component runtime config - multiple different types */ #define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index ca30d67..42dd72e 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -8,6 +8,7 @@ // Author: Liam Girdwood liam.r.girdwood@linux.intel.com //
+#include <linux/atomic.h> #include <linux/firmware.h> #include <linux/module.h> #include <sound/soc.h> @@ -311,6 +312,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; #endif + atomic_set(&sdev->reset_count, 0); dev_set_drvdata(dev, sdev);
/* check all mandatory ops */ diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 6fa501c1..b0b38ca 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -105,6 +105,8 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "CLK_REQ"; break; case SOF_IPC_PM_CORE_ENABLE: str2 = "CORE_ENABLE"; break; + case SOF_IPC_PM_VFE_POWER_STATUS: + str2 = "VFE_POWER_STATUS"; break; default: str2 = "unknown type"; break; } diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 1f2e0be..80854c9 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -10,6 +10,7 @@ // Generic firmware loader. //
+#include <linux/atomic.h> #include <linux/firmware.h> #include <sound/sof.h> #include "ops.h" @@ -611,6 +612,9 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) /* fw boot is complete. Update the active cores mask */ sdev->enabled_cores_mask = init_core_mask;
+ /* increment reset count */ + atomic_add(1, &sdev->reset_count); + return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 922b671..0792125 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -11,6 +11,7 @@ #ifndef __SOUND_SOC_SOF_PRIV_H #define __SOUND_SOC_SOF_PRIV_H
+#include <linux/atomic.h> #include <linux/device.h> #include <sound/hdaudio.h> #include <sound/sof.h> @@ -425,6 +426,9 @@ struct snd_sof_dev { unsigned int extractor_stream_tag; #endif
+ /* VirtIO fields for host and guest */ + atomic_t reset_count; + /* DMA for Trace */ struct snd_dma_buffer dmatb; struct snd_dma_buffer dmatp;
Add two helper lookup functions for finding a widget by its component ID and a DAI by a pipeline ID.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/sof-audio.c | 24 ++++++++++++++++++++++++ sound/soc/sof/sof-audio.h | 4 ++++ 2 files changed, 28 insertions(+)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index fc4ed2a..b4c5fe2 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -395,6 +395,30 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, return NULL; }
+struct snd_sof_widget *snd_sof_find_swidget_id(struct snd_sof_dev *sdev, + unsigned int comp_id) +{ + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) + if (swidget->comp_id == comp_id) + return swidget; + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai_pipe(struct snd_sof_dev *sdev, + unsigned int pipeline_id) +{ + struct snd_sof_dai *dai; + + list_for_each_entry(dai, &sdev->dai_list, list) + if (dai->pipeline_id == pipeline_id) + return dai; + + return NULL; +} + /* * SOF Driver enumeration. */ diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index bf65f31a..4880b32 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -190,6 +190,10 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, int *direction); struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, unsigned int pcm_id); +struct snd_sof_widget *snd_sof_find_swidget_id(struct snd_sof_dev *sdev, + unsigned int comp_id); +struct snd_sof_dai *snd_sof_find_dai_pipe(struct snd_sof_dev *sdev, + unsigned int pipeline_id); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); void snd_sof_pcm_period_elapsed_work(struct work_struct *work);
Add a VirtIO driver, designed to work with the SOF vhost driver. This driver allows SOF to be used on Virtual Machines (VMs) where the host is also a Linux system, using the SOF driver natively. This driver communicates with the host using the VirtIO standard over Virtual Queues. This version uses 3 Virtual Queues: for control, for data and for position updates. The control Virtual Queue uses exactly the same IPC protocol as what is used by the SOF driver natively to communicate with the DSP. In the future a zero-copy capability should be added thus eliminating 2 out of 3 Virtual Queues and only preserving the control queue.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/sound/sof.h | 4 + include/sound/sof/header.h | 2 + include/sound/sof/topology.h | 1 + include/sound/sof/virtio.h | 129 ++++++ include/uapi/linux/virtio_ids.h | 1 + sound/soc/sof/Kconfig | 7 + sound/soc/sof/Makefile | 4 + sound/soc/sof/core.c | 29 +- sound/soc/sof/ipc.c | 16 +- sound/soc/sof/pcm.c | 9 + sound/soc/sof/sof-priv.h | 29 ++ sound/soc/sof/topology.c | 18 +- sound/soc/sof/virtio-fe.c | 891 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1115 insertions(+), 25 deletions(-) create mode 100644 include/sound/sof/virtio.h create mode 100644 sound/soc/sof/virtio-fe.c
diff --git a/include/sound/sof.h b/include/sound/sof.h index a0cbca0..2c63d8f 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -17,6 +17,8 @@
struct snd_sof_dsp_ops;
+struct sof_vfe; + /* * SOF Platform data. */ @@ -27,6 +29,8 @@ struct snd_sof_pdata {
struct device *dev;
+ struct sof_vfe *vfe; + /* * notification callback used if the hardware initialization * can take time or is handled in a workqueue. This callback diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h index 1aaccb5a..49ad3ee 100644 --- a/include/sound/sof/header.h +++ b/include/sound/sof/header.h @@ -67,6 +67,8 @@ #define SOF_IPC_TPLG_PIPE_COMPLETE SOF_CMD_TYPE(0x013) #define SOF_IPC_TPLG_BUFFER_NEW SOF_CMD_TYPE(0x020) #define SOF_IPC_TPLG_BUFFER_FREE SOF_CMD_TYPE(0x021) +#define SOF_IPC_TPLG_VFE_GET SOF_CMD_TYPE(0x030) +#define SOF_IPC_TPLG_VFE_COMP_ID SOF_CMD_TYPE(0x031)
/* PM */ #define SOF_IPC_PM_CTX_SAVE SOF_CMD_TYPE(0x001) diff --git a/include/sound/sof/topology.h b/include/sound/sof/topology.h index 402e025..6de3154 100644 --- a/include/sound/sof/topology.h +++ b/include/sound/sof/topology.h @@ -37,6 +37,7 @@ enum sof_comp_type { SOF_COMP_SELECTOR, /**< channel selector component */ SOF_COMP_DEMUX, SOF_COMP_ASRC, /**< Asynchronous sample rate converter */ + SOF_COMP_VIRT_CON, /**< Virtual connection, sent by the VirtIO guest */ /* keep FILEREAD/FILEWRITE as the last ones */ SOF_COMP_FILEREAD = 10000, /**< host test based file IO */ SOF_COMP_FILEWRITE = 10001, /**< host test based file IO */ diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h new file mode 100644 index 00000000..d7d94da --- /dev/null +++ b/include/sound/sof/virtio.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2018-2020 Intel Corporation. All rights reserved. + * + * Contact Information: + * Author: Luo Xionghu xionghu.luo@intel.com + * Liam Girdwood liam.r.girdwood@linux.intel.com + * Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com + */ + +#ifndef _SOF_VIRTIO_H +#define _SOF_VIRTIO_H + +#include <sound/sof/header.h> + +/* + * Currently we define 3 vqs: one for handling of IPC commands, one for + * handling of stream position updates, and one for audio data. + */ +enum { + SOF_VIRTIO_IPC_CMD_VQ, /* IPC commands and replies */ + SOF_VIRTIO_POSN_VQ, /* Stream position updates */ + SOF_VIRTIO_DATA_VQ, /* Audio data */ + /* Keep last */ + SOF_VIRTIO_NUM_OF_VQS, +}; + +/* command messages from FE to BE, trigger/open/hw_params and so on */ +#define SOF_VIRTIO_IPC_CMD_VQ_NAME "sof-ipc-cmd" + +/* the vq to get stream position updates */ +#define SOF_VIRTIO_POSN_VQ_NAME "sof-position" + +/* the vq for audio data */ +#define SOF_VIRTIO_DATA_VQ_NAME "sof-data" + +/** + * struct sof_vfe_ipc_tplg_req - request for topology data + * @hdr: the standard SOF IPC header + * @offset: the current offset when transferring a split file + */ +struct sof_vfe_ipc_tplg_req { + struct sof_ipc_cmd_hdr hdr; + size_t offset; +} __packed; + +/** + * struct sof_vfe_ipc_tplg_resp - response to a topology file request + * @reply: the standard SOF IPC response header + * @data: the complete topology file + * + * The topology file is transferred from the host to the guest over a virtual + * queue in chunks of SOF_IPC_MSG_MAX_SIZE - sizeof(struct sof_ipc_reply), so + * for data transfer the @data array is much smaller than 64KiB. 64KiB is what + * is included in struct sof_vfe for permanent storage of the complete file. + */ +struct sof_vfe_ipc_tplg_resp { + struct sof_ipc_reply reply; + /* There exist topology files already larger than 40KiB */ + uint8_t data[64 * 1024 - sizeof(struct sof_ipc_reply)]; +} __packed; + +/** + * struct sof_vfe_ipc_power_req - power status change IPC + * @hdr: the standard SOF IPC header + * @power: 1: on, 0: off + */ +struct sof_vfe_ipc_power_req { + struct sof_ipc_cmd_hdr hdr; + uint32_t power; +} __packed; + +enum sof_virtio_ipc_reset_status { + SOF_VIRTIO_IPC_RESET_NONE, /* Host hasn't been reset */ + SOF_VIRTIO_IPC_RESET_DONE, /* Host has been reset */ +}; + +/** + * struct sof_vfe_ipc_power_resp - response to a power status request + * @reply: the standard SOF IPC response header + * @reset_status: enum sof_virtio_ipc_reset_status + */ +struct sof_vfe_ipc_power_resp { + struct sof_ipc_reply reply; + uint32_t reset_status; +} __packed; + +#define SOF_VFE_MAX_DATA_SIZE (16 * 1024) + +/** + * struct dsp_sof_data_req - Audio data request + * + * @size: the size of audio data sent or requested, excluding the header + * @offset: offset in the DMA buffer + * @comp_id: component ID, used to identify the stream + * @data: audio data + * + * When used during playback, the data array actually contains audio data, when + * used for capture, the data part isn't sent. + */ +struct dsp_sof_data_req { + u32 size; + u32 offset; + u32 comp_id; + /* Only included for playback */ + u8 data[SOF_VFE_MAX_DATA_SIZE]; +} __packed; + +/** + * struct dsp_sof_data_resp - Audio data response + * + * @size: the size of audio data sent, excluding the header + * @error: response error + * @data: audio data + * + * When used during capture, the data array actually contains audio data, when + * used for playback, the data part isn't sent. + */ +struct dsp_sof_data_resp { + u32 size; + u32 error; + /* Only included for capture */ + u8 data[SOF_VFE_MAX_DATA_SIZE]; +} __packed; + +#define HDR_SIZE_REQ offsetof(struct dsp_sof_data_req, data) +#define HDR_SIZE_RESP offsetof(struct dsp_sof_data_resp, data) + +#endif diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 585e07b..0a15aa6 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -46,5 +46,6 @@ #define VIRTIO_ID_IOMMU 23 /* virtio IOMMU */ #define VIRTIO_ID_FS 26 /* virtio filesystem */ #define VIRTIO_ID_PMEM 27 /* virtio pmem */ +#define VIRTIO_ID_ADSP 28 /* virtio AudioDSP */
#endif /* _LINUX_VIRTIO_IDS_H */ diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 4dda4b6..e96d8ff 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -96,6 +96,13 @@ config SND_SOC_SOF_STRICT_ABI_CHECKS If you are not involved in SOF releases and CI development select "N".
+config SND_SOC_SOF_VIRTIO_FE + bool "SOF VirtIO guest role" + depends on SND_SOC_SOF_NOCODEC_SUPPORT + depends on VIRTIO + ---help--- + Enable SOF for a VirtIO based guest configuration. + config SND_SOC_SOF_DEBUG bool "SOF debugging features" help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 8eca2f8..b728d09 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -10,6 +10,10 @@ snd-sof-of-objs := sof-of-dev.o
snd-sof-nocodec-objs := nocodec.o
+ifdef CONFIG_SND_SOC_SOF_VIRTIO_FE +snd-sof-objs += virtio-fe.o +endif + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 42dd72e..d0bf082 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -178,7 +178,8 @@ static int sof_load_and_run_firmware(struct snd_sof_dev *sdev) return 0;
fw_run_err: - snd_sof_fw_unload(sdev); + if (!sdev->pdata->vfe) + snd_sof_fw_unload(sdev);
return ret; } @@ -228,9 +229,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; }
- ret = sof_load_and_run_firmware(sdev); - if (ret < 0) - goto fw_load_err; + /* virtio front-end mode will not touch HW, skip fw loading */ + if (!plat_data->vfe) { + ret = sof_load_and_run_firmware(sdev); + if (ret < 0) + goto fw_load_err; + }
/* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -264,7 +268,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
fw_trace_err: snd_sof_free_trace(sdev); - snd_sof_fw_unload(sdev); + if (!sdev->pdata->vfe) + snd_sof_fw_unload(sdev); fw_load_err: snd_sof_ipc_free(sdev); ipc_err: @@ -362,10 +367,12 @@ int snd_sof_device_remove(struct device *dev) cancel_work_sync(&sdev->probe_work);
if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { - snd_sof_fw_unload(sdev); + if (!pdata->vfe) { + snd_sof_fw_unload(sdev); + snd_sof_free_trace(sdev); + } snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); - snd_sof_free_trace(sdev); }
/* @@ -384,9 +391,11 @@ int snd_sof_device_remove(struct device *dev) if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) snd_sof_remove(sdev);
- /* release firmware */ - release_firmware(pdata->fw); - pdata->fw = NULL; + if (!pdata->vfe) { + /* release firmware */ + release_firmware(pdata->fw); + pdata->fw = NULL; + }
return 0; } diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index b0b38ca..8f52de0 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -25,18 +25,6 @@ * IPC message Tx/Rx message handling. */
-/* SOF generic IPC data */ -struct snd_sof_ipc { - struct snd_sof_dev *sdev; - - /* protects messages and the disable flag */ - struct mutex tx_mutex; - /* disables further sending of ipc's */ - bool disable_ipc_tx; - - struct snd_sof_ipc_msg msg; -}; - struct sof_ipc_ctrl_data_params { size_t msg_bytes; size_t hdr_bytes; @@ -84,6 +72,10 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "BUFFER_NEW"; break; case SOF_IPC_TPLG_BUFFER_FREE: str2 = "BUFFER_FREE"; break; + case SOF_IPC_TPLG_VFE_GET: + str2 = "VFE_GET"; break; + case SOF_IPC_TPLG_VFE_COMP_ID: + str2 = "VFE_COMP_ID"; break; default: str2 = "unknown type"; break; } diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 47cd741..3e05c41 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -677,6 +677,13 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, return -EINVAL; }
+ /* VirtIO guests have no .dai_config, DAIs are configured by the host */ + if (!dai->dai_config) { + dev_dbg(component->dev, "no DAI config for %s!\n", + rtd->dai_link->name); + return 0; + } + /* read rate and channels from topology */ switch (dai->dai_config->type) { case SOF_DAI_INTEL_SSP: @@ -783,6 +790,8 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->hw_free = sof_pcm_hw_free; pd->trigger = sof_pcm_trigger; pd->pointer = sof_pcm_pointer; + if (plat_data->vfe) + pd->copy_user = sof_vfe_pcm_copy_user;
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) pd->compr_ops = &sof_compressed_ops; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0792125..44780c2 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -55,6 +55,9 @@ (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST))
+/* The maximum number of components a virtio user vFE driver can use */ +#define SOF_VIRTIO_MAX_UOS_COMPS 1000 + /* DSP power state */ enum sof_dsp_power_states { SOF_DSP_PM_D0, @@ -250,6 +253,10 @@ struct snd_sof_dsp_ops { void (*set_mach_params)(const struct snd_soc_acpi_mach *mach, struct device *dev); /* optional */
+ /* VirtIO operations */ + int (*request_topology)(struct snd_sof_dev *sdev, + struct firmware *fw); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -445,6 +452,18 @@ struct snd_sof_dev { void *private; /* core does not touch this */ };
+/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* protects messages and the disable flag */ + struct mutex tx_mutex; + /* disables further sending of ipc's */ + bool disable_ipc_tx; + + struct snd_sof_ipc_msg msg; +}; + /* * Device Level. */ @@ -526,10 +545,20 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev);
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_VIRTIO_FE) +int sof_vfe_pcm_copy_user(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int channel, + unsigned long pos, void __user *buf, + unsigned long bytes); +#else +#define sof_vfe_pcm_copy_user NULL +#endif + /* * Platform specific ops. */ extern struct snd_compr_ops sof_compressed_ops; +extern struct snd_sof_dsp_ops snd_sof_virtio_fe_ops;
/* * DSP Architectures. diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index fe8ba3e..dcc897f 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1301,7 +1301,8 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, comp_dai.comp.hdr.size = sizeof(comp_dai); comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; comp_dai.comp.id = swidget->comp_id; - comp_dai.comp.type = SOF_COMP_DAI; + comp_dai.comp.type = sdev->pdata->vfe ? SOF_COMP_VIRT_CON : + SOF_COMP_DAI; comp_dai.comp.pipeline_id = index; comp_dai.config.hdr.size = sizeof(comp_dai.config);
@@ -3620,12 +3621,21 @@ static int sof_manifest(struct snd_soc_component *scomp, int index,
int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct firmware vfe_fw; const struct firmware *fw; int ret;
dev_dbg(scomp->dev, "loading topology:%s\n", file);
- ret = request_firmware(&fw, file, scomp->dev); + /* VirtIO guests request topology from the host */ + if (sdev->pdata->vfe) { + fw = &vfe_fw; + ret = sof_ops(sdev)->request_topology(sdev, file, &vfe_fw); + } else { + ret = request_firmware(&fw, file, sdev->dev); + } + if (ret < 0) { dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n", file, ret); @@ -3641,7 +3651,9 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) ret = -EINVAL; }
- release_firmware(fw); + if (!sdev->pdata->vfe) + release_firmware(fw); + return ret; } EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/virtio-fe.c b/sound/soc/sof/virtio-fe.c new file mode 100644 index 00000000..aa6da81 --- /dev/null +++ b/sound/soc/sof/virtio-fe.c @@ -0,0 +1,891 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2017-2020 Intel Corporation. All rights reserved. + * + * Author: Libin Yang libin.yang@intel.com + * Luo Xionghu xionghu.luo@intel.com + * Liam Girdwood liam.r.girdwood@linux.intel.com + * Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com + */ + +/* + * VirtIO Front-End (VFE) driver + * + * This driver presents a virtualised DSP interface to SOF. It implements an + * instance of SOF operations, which instead of providing means of communication + * with a physical DSP, communicate with the SOF driver, running in the host OS. + * This communication takes place over 3 VirtQueues: control, data, and position + * update. The control queue uses the SOF IPC message format, which make it + * possible for most messages to be passed 1-to-1 to the physical DSP on the + * host. The data and the position update queues should in the future be + * replaced with 0-copy buffer sharing. + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/virtio.h> +#include <linux/virtio_config.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_ring.h> +#include <sound/sof.h> +#include <sound/sof/virtio.h> + +#include "ops.h" +#include "sof-audio.h" +#include "sof-priv.h" + +/* 600ms for VirtQ IPC */ +#define SOF_VFE_DATA_TIMEOUT_MS 600 + +static const char *const sof_vq_names[SOF_VIRTIO_NUM_OF_VQS] = { + SOF_VIRTIO_IPC_CMD_VQ_NAME, + SOF_VIRTIO_POSN_VQ_NAME, + SOF_VIRTIO_DATA_VQ_NAME, +}; + +struct sof_vfe { + struct snd_sof_dev *sdev; + + /* IPC cmd from frontend to backend */ + struct virtqueue *ipc_cmd_vq; + + /* IPC position update from backend to frontend */ + struct virtqueue *posn_vq; + + /* audio data in both directions */ + struct virtqueue *data_vq; + + /* position update work */ + struct work_struct posn_update_work; + + /* current pending cmd message */ + struct snd_sof_ipc_msg *msg; + + /* current and pending notification */ + struct snd_sof_ipc_msg *not; + struct sof_ipc_stream_posn posn; + + /* + * IPC messages are blocked. "true" if the DSP hasn't been reset and + * therefore we don't have to re-send our topology. + */ + bool block_ipc; + struct sof_vfe_ipc_tplg_resp tplg; + + struct completion completion; + spinlock_t vq_lock; + + /* A shared capture / playback virtual queue data buffer */ + union { + struct dsp_sof_data_req data_req; + struct dsp_sof_data_resp data_resp; + }; + + /* Headers, used as a playback response or capture request */ + union { + u8 hdr_req[HDR_SIZE_REQ]; + u8 hdr_resp[HDR_SIZE_RESP]; + }; +}; + +/* Firmware ready IPC. */ +static int sof_vfe_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + return 0; +}; + +/* Send IPC to vBE */ +static int sof_vfe_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct scatterlist sg_out, sg_in; + struct scatterlist *sgs[] = {&sg_out, &sg_in}; + size_t msg_size = msg->msg_size; + void *msg_data = msg->msg_data; + struct device *dev = sdev->dev; + int ret; + + if (vfe->block_ipc) { + struct sof_ipc_reply *reply = msg->reply_data; + + reply->error = 0; + msg->reply_error = reply->error; + /* + * No need to take .ipc_lock: we return > 0, so + * sof_ipc_tx_message_unlocked() won't overwrite .ipc_complete + */ + msg->ipc_complete = true; + wake_up(&msg->waitq); + + return 1; + } + + /* Prepare and add 1 SG array for each of request and response */ + sg_init_one(&sg_out, msg_data, msg_size); + sg_init_one(&sg_in, msg->reply_data, msg->reply_size); + + /* Called under spin_lock_irq() in sof_ipc_tx_message_unlocked() */ + ret = virtqueue_add_sgs(vfe->ipc_cmd_vq, sgs, 1, 1, msg_data, + GFP_ATOMIC); + if (ret < 0) { + dev_err(dev, "error: could not send IPC %d\n", ret); + return ret; + } + + vfe->msg = msg; + + /* Notify the host */ + if (!virtqueue_kick(vfe->ipc_cmd_vq)) { + dev_err(dev, "error: IPC failed to kick host\n"); + return -EIO; + } + + return 0; +} + +/* Handle playback or capture data */ +static void sof_vfe_handle_data(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + + complete(&vfe->completion); +} + +/* IPC message sending completed. This means vBE has received the cmd */ +static void sof_vfe_cmd_tx_done(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + struct snd_sof_dev *sdev = vfe->sdev; + + do { + struct snd_sof_ipc_msg *msg = vfe->msg; + struct sof_ipc_reply *reply = msg->reply_data; + unsigned int len; + + /* Callbacks must be disabled when reading from a VirtQ */ + virtqueue_disable_cb(vq); + + spin_lock(&vfe->vq_lock); + /* + * virtqueue_get_buf() returns the "token" that was provided to + * virtqueue_add_*() functions + */ + while (virtqueue_get_buf(vq, &len)) { + unsigned long flags; + msg->reply_error = reply->error; + + /* Firmware panic? */ + if (msg->reply_error == -ENODEV) + sdev->ipc->disable_ipc_tx = true; + + spin_lock_irqsave(&sdev->ipc_lock, flags); + msg->ipc_complete = true; + wake_up(&msg->waitq); + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + } + spin_unlock(&vfe->vq_lock); + } while (!virtqueue_enable_cb(vq)); +} + +/* The high latency version, using VirtQueues */ +static void sof_vfe_posn_update(struct work_struct *work) +{ + struct sof_vfe *vfe = container_of(work, struct sof_vfe, + posn_update_work); + struct sof_ipc_stream_posn *posn = &vfe->posn; + struct virtqueue *vq = vfe->posn_vq; + struct snd_sof_dev *sdev = vfe->sdev; + struct snd_sof_pcm *spcm; + struct scatterlist sg; + unsigned int buflen; + int direction; + + /* virtio protects against re-entry */ + while (virtqueue_get_buf(vq, &buflen)) { + spcm = snd_sof_find_spcm_comp(sdev->component, posn->comp_id, + &direction); + if (!spcm) { + dev_err(sdev->dev, + "err: period elapsed for unused component %d\n", + posn->comp_id); + } else { + /* + * The position update requirement is valid. + * Let's update the position now. + */ + memcpy(&spcm->stream[direction].posn, posn, sizeof(*posn)); + snd_pcm_period_elapsed(spcm->stream[direction].substream); + } + + /* kick back the empty posn buffer immediately */ + sg_init_one(&sg, posn, sizeof(*posn)); + virtqueue_add_inbuf(vq, &sg, 1, posn, GFP_KERNEL); + if (!virtqueue_kick(vq)) + dev_err(sdev->dev, + "error: position update failed to kick host\n"); + } +} + +/* + * handle the position update, receive the posn and send to up layer, then + * resend the buffer to BE + */ +static void sof_vfe_posn_handle_rx(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + + schedule_work(&vfe->posn_update_work); +} + +static int sof_vfe_register(struct snd_sof_dev *sdev) +{ + sdev->pdata->vfe->sdev = sdev; + sdev->next_comp_id = SOF_VIRTIO_MAX_UOS_COMPS; + + return 0; +} + +/* Some struct snd_sof_dsp_ops operations are compulsory, but unused by vFE */ +static int sof_vfe_deregister(struct snd_sof_dev *sdev) +{ + return 0; +} + +static int sof_vfe_run(struct snd_sof_dev *sdev) +{ + return 0; +} + +static void sof_vfe_block_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *dest, + size_t size) +{ +} + +static void sof_vfe_block_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *src, + size_t size) +{ +} + +static int sof_vfe_load_firmware(struct snd_sof_dev *sdev) +{ + return 0; +} + +static void sof_vfe_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ +} + +static int sof_vfe_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return 0; +} + +static int sof_vfe_sof_runtime_dummy(struct snd_sof_dev *sdev) +{ + return 0; +} + +/* Pre-queue a position update buffer. */ +static int sof_vfe_prequeue_position(struct snd_sof_dev *sdev) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct scatterlist sg_in; + int ret; + + sg_init_one(&sg_in, &vfe->posn, sizeof(vfe->posn)); + /* Only 1 input buffer, no output buffers */ + ret = virtqueue_add_inbuf(vfe->posn_vq, &sg_in, 1, &vfe->posn, + GFP_KERNEL); + if (ret < 0) { + dev_err(sdev->dev, "%s(): failed %d to add a buffer\n", + __func__, ret); + return ret; + } + + /* Send the buffer */ + if (!virtqueue_kick(vfe->posn_vq)) { + dev_err(sdev->dev, + "error: position update failed to kick host\n"); + return -EIO; + } + + return 0; +} + +static int sof_vfe_request_topology(struct snd_sof_dev *sdev, + struct firmware *fw) +{ + struct sof_vfe_ipc_tplg_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_VFE_GET, + }, + }; + struct sof_vfe_ipc_tplg_resp *partdata = kmalloc(SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + size_t part_size = SOF_IPC_MSG_MAX_SIZE - sizeof(partdata->reply), + data_size = 0, to_copy = 0; + struct sof_vfe *vfe = sdev->pdata->vfe; + struct device *dev = sdev->dev; + int ret; + + if (!partdata) + return -ENOMEM; + + mutex_lock(&sdev->ipc->tx_mutex); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, + "Cannot resume VFE sof-audio device. Error %d\n", ret); + return ret; + } + + do { + ret = sof_ipc_tx_message_unlocked(sdev->ipc, rq.hdr.cmd, + &rq, sizeof(rq), partdata, + SOF_IPC_MSG_MAX_SIZE); + if (ret < 0) + goto free; + + if (partdata->reply.hdr.size <= sizeof(partdata->reply)) { + ret = -EINVAL; + goto free; + } + + if (data_size && data_size - to_copy != + partdata->reply.hdr.size - sizeof(partdata->reply)) { + ret = -EINVAL; + goto free; + } + + /* + * Size is consistent and decreasing, we're guaranteed to exit + * this loop eventually + */ + data_size = partdata->reply.hdr.size - sizeof(partdata->reply); + to_copy = min_t(size_t, data_size, part_size); + memcpy(vfe->tplg.data + rq.offset, partdata->data, to_copy); + if (!rq.offset) + fw->size = data_size; + rq.offset += part_size; + } while (data_size > part_size); + + /* Get our first component ID */ + rq.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_VFE_COMP_ID; + rq.hdr.size = sizeof(rq.hdr); + ret = sof_ipc_tx_message_unlocked(sdev->ipc, rq.hdr.cmd, + &rq, rq.hdr.size, partdata, + sizeof(partdata->reply) + sizeof(u32)); + if (ret < 0) + goto free; + + sdev->next_comp_id = *(u32 *)partdata->data; + + fw->data = vfe->tplg.data; + fw->pages = NULL; + + ret = sof_vfe_prequeue_position(sdev); + +free: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(partdata); + + return ret; +} + +static unsigned long get_dma_offset(struct snd_pcm_runtime *runtime, + int channel, unsigned long hwoff) +{ + return hwoff + channel * (runtime->dma_bytes / runtime->channels); +} + +static int sof_vfe_pcm_read_part(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_pcm_substream *substream, + int channel, unsigned long posn, + void __user *buf, unsigned long chunk_size) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct dsp_sof_data_resp *data = &vfe->data_resp; + struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; + struct dsp_sof_data_req *req = (struct dsp_sof_data_req *)vfe->hdr_req; + unsigned int len; + int ret; + + /* put response size in request */ + req->size = chunk_size; + req->comp_id = spcm->stream[substream->stream].comp_id; + req->offset = get_dma_offset(substream->runtime, channel, posn); + + /* Add input and output buffers */ + sg_init_one(&sg_out, vfe->hdr_req, sizeof(vfe->hdr_req)); + sg_init_one(&sg_in, data, chunk_size + HDR_SIZE_RESP); + + ret = virtqueue_add_sgs(vfe->data_vq, sgs, 1, 1, vfe->hdr_req, + GFP_KERNEL); + if (ret < 0) + dev_err(sdev->dev, "error: could not send data %d\n", ret); + + /* Kick the host */ + if (!virtqueue_kick(vfe->data_vq)) { + dev_err(sdev->dev, "error: data failed to kick host\n"); + return -EIO; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + /* Get a response with disabled callbacks */ + virtqueue_disable_cb(vfe->data_vq); + + spin_lock(&vfe->vq_lock); + virtqueue_get_buf(vfe->data_vq, &len); + spin_unlock(&vfe->vq_lock); + + virtqueue_enable_cb(vfe->data_vq); + + if (ret < 0) + return ret; + if (data->error < 0) + return data->error; + + if (copy_to_user(buf, data->data, chunk_size)) + return -EFAULT; + + return 0; +} + +static int sof_vfe_pcm_write_part(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_pcm_substream *substream, + int channel, unsigned long posn, + void __user *buf, unsigned long chunk_size) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct dsp_sof_data_req *data = &vfe->data_req; + struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; + struct dsp_sof_data_resp *resp = (struct dsp_sof_data_resp *)vfe->hdr_resp; + unsigned int len; + int ret; + + data->size = chunk_size; + data->comp_id = spcm->stream[substream->stream].comp_id; + data->offset = get_dma_offset(substream->runtime, channel, posn); + + if (copy_from_user(data->data, buf, chunk_size)) + return -EFAULT; + + /* Similar to the capture case above */ + sg_init_one(&sg_out, data, chunk_size + HDR_SIZE_REQ); + sg_init_one(&sg_in, vfe->hdr_resp, sizeof(vfe->hdr_resp)); + + ret = virtqueue_add_sgs(vfe->data_vq, sgs, 1, 1, vfe->hdr_resp, + GFP_KERNEL); + if (ret < 0) + dev_err(sdev->dev, "error: could not send data %d\n", ret); + + if (!virtqueue_kick(vfe->data_vq)) { + dev_err(sdev->dev, "error: data failed to kick host\n"); + return -EIO; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + virtqueue_disable_cb(vfe->data_vq); + + spin_lock(&vfe->vq_lock); + virtqueue_get_buf(vfe->data_vq, &len); + spin_unlock(&vfe->vq_lock); + + virtqueue_enable_cb(vfe->data_vq); + + return ret < 0 ? ret : resp->error; +} + +/* The slow version, using VirtQueues for playback and capture data */ +int sof_vfe_pcm_copy_user(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int channel, + unsigned long posn, void __user *buf, + unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = snd_sof_find_spcm_dai(sdev->component, rtd); + unsigned int i, n = (bytes + SOF_VFE_MAX_DATA_SIZE - 1) / + SOF_VFE_MAX_DATA_SIZE; + int ret = 0; + + if (!spcm || spcm->scomp != sdev->component) { + dev_err(sdev->dev, "%s(): invalid SPCM 0x%p!\n", __func__, + spcm); + return -ENODEV; + } + + mutex_lock(&sdev->ipc->tx_mutex); + + for (i = 0; i < n; i++) { + size_t n_bytes = i == n - 1 ? bytes % SOF_VFE_MAX_DATA_SIZE : + SOF_VFE_MAX_DATA_SIZE; + + reinit_completion(&sdev->pdata->vfe->completion); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = sof_vfe_pcm_write_part(sdev, spcm, substream, + channel, posn, buf, n_bytes); + else + ret = sof_vfe_pcm_read_part(sdev, spcm, substream, + channel, posn, buf, n_bytes); + + if (ret < 0) + break; + + buf += n_bytes; + posn += n_bytes; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + return ret; +} + +#define SOF_VFE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_driver virtio_dai[] = { + { + .name = "VirtIO DAI", + .playback = SOF_DAI_STREAM("playback", 1, 8, + SNDRV_PCM_RATE_8000_192000, SOF_VFE_FORMATS), + .capture = SOF_DAI_STREAM("capture", 1, 8, + SNDRV_PCM_RATE_8000_192000, SOF_VFE_FORMATS), + }, +}; + +static int sof_vfe_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + int ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) + dev_err(sdev->dev, + "Cannot resume VFE sof-audio device. Error %d\n", ret); + + return ret; +} + +static int sof_vfe_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + + return 0; +} + +/* virtio fe ops */ +struct snd_sof_dsp_ops snd_sof_vfe_ops = { + /* device init */ + .probe = sof_vfe_register, + .remove = sof_vfe_deregister, + + /* + * PM: these are never called, they are only needed to prevent core.c + * from disabling runtime PM + */ + .runtime_suspend = sof_vfe_sof_runtime_dummy, + .runtime_resume = sof_vfe_sof_runtime_dummy, + + /* IPC */ + .send_msg = sof_vfe_send_msg, + .fw_ready = sof_vfe_fw_ready, + + /* machine driver */ + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + + /* DAI drivers */ + .drv = virtio_dai, + .num_drv = 1, + + .pcm_open = sof_vfe_pcm_open, + .pcm_close = sof_vfe_pcm_close, + + .run = sof_vfe_run, + .block_read = sof_vfe_block_read, + .block_write = sof_vfe_block_write, + .load_firmware = sof_vfe_load_firmware, + .ipc_msg_data = sof_vfe_ipc_msg_data, + .ipc_pcm_params = sof_vfe_ipc_pcm_params, + + .request_topology = sof_vfe_request_topology, + + .hw_info = SNDRV_PCM_INFO_INTERLEAVED, +}; + +static const struct sof_dev_desc virt_desc = { + .nocodec_tplg_filename = "", + .default_tplg_path = "", + .resindex_lpe_base = -1, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .ipc_timeout = SOF_VFE_DATA_TIMEOUT_MS, + .ops = &snd_sof_vfe_ops, +}; + +static void sof_virtio_vfe_init(struct snd_sof_dev *sdev, + struct sof_vfe *vfe) +{ + /* + * Currently we only support one VM. comp_id from 0 to + * SOF_VIRTIO_MAX_UOS_COMPS - 1 is for the host. Other comp_id numbers + * are for VM1. + * This will be overwritten during topology setup. + */ + sdev->next_comp_id = SOF_VIRTIO_MAX_UOS_COMPS; + vfe->sdev = sdev; +} + +static int sof_vfe_init(struct virtio_device *vdev) +{ + struct device *dev = &vdev->dev; + struct snd_soc_acpi_mach *mach; + struct snd_sof_pdata *sof_pdata; + int ret; + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); + if (!mach) + return -ENOMEM; + + mach->drv_name = "sof-nocodec"; + mach->mach_params.platform = dev_name(dev); + sof_pdata->tplg_filename = virt_desc.nocodec_tplg_filename; + + ret = sof_nocodec_setup(dev, &snd_sof_vfe_ops); + if (ret < 0) + return ret; + + mach->pdata = &snd_sof_vfe_ops; + + sof_pdata->name = dev_name(&vdev->dev); + sof_pdata->machine = mach; + sof_pdata->desc = &virt_desc; + sof_pdata->dev = dev; + sof_pdata->vfe = vdev->priv; + sof_pdata->tplg_filename_prefix = virt_desc.default_tplg_path; + + /* allow runtime_pm */ + pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret < 0) { + dev_err(dev, "Cannot register VFE sof-audio device. Error %d\n", + ret); + } else { + sof_virtio_vfe_init(dev_get_drvdata(dev), vdev->priv); + dev_dbg(dev, "created VFE machine %s\n", + dev_name(&sof_pdata->pdev_mach->dev)); + } + + return ret; +} + +static int sof_vfe_runtime_suspend(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct sof_vfe_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 0, + }; + struct sof_vfe_ipc_power_resp resp = {.reply.error = 0}; + + return sof_ipc_tx_message(sdev->ipc, rq.hdr.cmd, &rq, sizeof(rq), + &resp, sizeof(resp)); +} + +static int sof_vfe_runtime_resume(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct sof_vfe *vfe = sdev->pdata->vfe; + struct sof_vfe_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 1, + }; + struct sof_vfe_ipc_power_resp resp = {.reply.error = 0}; + int ret = sof_ipc_tx_message(sdev->ipc, rq.hdr.cmd, &rq, sizeof(rq), + &resp, sizeof(resp)); + if (ret < 0) + return ret; + + if (resp.reply.error < 0) + return resp.reply.error; + + /* + * We are resuming. Check if the host needs the topology. We could in + * principle skip restoring pipelines completely, but it also does + * certain additional things, e.g. setting an enabled core mask + */ + vfe->block_ipc = resp.reset_status == SOF_VIRTIO_IPC_RESET_NONE; + + /* restore pipelines */ + ret = sof_restore_pipelines(sdev->dev); + if (ret < 0) + dev_err(dev, + "error: failed to restore pipeline after resume %d\n", + ret); + + /* We're done resuming, from now all IPC have to be sent */ + vfe->block_ipc = false; + + return ret; +} + +/* Probe and remove. */ +static int sof_vfe_probe(struct virtio_device *vdev) +{ + struct virtqueue *vqs[SOF_VIRTIO_NUM_OF_VQS]; + /* the processing callback number must be the same as the vqueues.*/ + vq_callback_t *cbs[SOF_VIRTIO_NUM_OF_VQS] = { + sof_vfe_cmd_tx_done, + sof_vfe_posn_handle_rx, + sof_vfe_handle_data, + }; + struct device *dev = &vdev->dev; + struct sof_vfe *vfe; + int ret; + + /* + * The below two shouldn't be necessary, it's done in + * virtio_pci_modern_probe() by calling dma_set_mask_and_coherent() + */ + + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + if (ret < 0) + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + if (ret < 0) + dev_warn(dev, "failed to set DMA mask: %d\n", ret); + + vfe = devm_kzalloc(dev, sizeof(*vfe), GFP_KERNEL); + if (!vfe) + return -ENOMEM; + + vdev->priv = vfe; + + INIT_WORK(&vfe->posn_update_work, sof_vfe_posn_update); + init_completion(&vfe->completion); + spin_lock_init(&vfe->vq_lock); + + /* create virt queue for vfe to send/receive IPC message. */ + ret = virtio_find_vqs(vdev, SOF_VIRTIO_NUM_OF_VQS, + vqs, cbs, sof_vq_names, NULL); + if (ret) { + dev_err(dev, "error: find vqs fail with %d\n", ret); + return ret; + } + + /* virtques */ + vfe->ipc_cmd_vq = vqs[SOF_VIRTIO_IPC_CMD_VQ]; + vfe->posn_vq = vqs[SOF_VIRTIO_POSN_VQ]; + vfe->data_vq = vqs[SOF_VIRTIO_DATA_VQ]; + + virtio_device_ready(vdev); + + ret = sof_vfe_init(vdev); + if (ret < 0) + goto free_vqs; + + return 0; + +free_vqs: + vdev->config->del_vqs(vdev); + + return ret; +} + +static void sof_vfe_remove(struct virtio_device *vdev) +{ + /* free virtio resurces and unregister device */ + struct sof_vfe *vfe = vdev->priv; + + sof_vfe_runtime_suspend(&vdev->dev); + + pm_runtime_disable(&vdev->dev); + + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + cancel_work_sync(&vfe->posn_update_work); + + /* unregister the SOF device */ + snd_sof_device_remove(&vdev->dev); + + /* Detach the possibly pre-queued position update buffers */ + virtqueue_detach_unused_buf(vfe->posn_vq); +} + +static void virtaudio_config_changed(struct virtio_device *vdev) +{ +} + +/* + * Need to patch QEMU to create a virtio audio device, e.g. per + * -device virtio-snd-pci,snd=snd0 where Device ID must be + * 0x1040 + VIRTIO_ID_ADSP and Vendor ID = PCI_VENDOR_ID_REDHAT_QUMRANET + */ +static const struct virtio_device_id id_table[] = { + {VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID}, + {0}, +}; + +static const struct dev_pm_ops sof_vfe_pm = { + SET_RUNTIME_PM_OPS(sof_vfe_runtime_suspend, sof_vfe_runtime_resume, + NULL) +}; + +static struct virtio_driver sof_vfe_audio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .pm = &sof_vfe_pm, + }, + .id_table = id_table, + .probe = sof_vfe_probe, + .remove = sof_vfe_remove, + .config_changed = virtaudio_config_changed, +}; + +module_virtio_driver(sof_vfe_audio_driver); + +MODULE_DEVICE_TABLE(virtio, id_table);
The SOF VirtIO driver uses a vhost driver as a counterpart to communicate with the DSP. This patch adds a sound interface of the vhost driver.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/sound/soc-topology.h | 3 + include/sound/sof/virtio.h | 73 +++ include/uapi/linux/vhost.h | 5 + include/uapi/linux/vhost_types.h | 7 + sound/soc/soc-pcm.c | 31 +- sound/soc/sof/Kconfig | 1 + sound/soc/sof/Makefile | 4 + sound/soc/sof/core.c | 6 + sound/soc/sof/ipc.c | 5 + sound/soc/sof/pcm.c | 4 +- sound/soc/sof/pm.c | 4 + sound/soc/sof/sof-audio.c | 9 + sound/soc/sof/sof-audio.h | 17 + sound/soc/sof/sof-priv.h | 16 + sound/soc/sof/topology.c | 55 +- sound/soc/sof/vhost-be.c | 1093 ++++++++++++++++++++++++++++++++++++++ 16 files changed, 1321 insertions(+), 12 deletions(-) create mode 100644 sound/soc/sof/vhost-be.c
diff --git a/include/sound/soc-topology.h b/include/sound/soc-topology.h index 5223896..ea0c2a6 100644 --- a/include/sound/soc-topology.h +++ b/include/sound/soc-topology.h @@ -34,6 +34,9 @@ /* object scan be loaded and unloaded in groups with identfying indexes */ #define SND_SOC_TPLG_INDEX_ALL 0 /* ID that matches all FW objects */
+#define SOC_VIRT_DAI_PLAYBACK "VM FE Playback" +#define SOC_VIRT_DAI_CAPTURE "VM FE Capture" + /* dynamic object type */ enum snd_soc_dobj_type { SND_SOC_DOBJ_NONE = 0, /* object is not dynamic */ diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h index d7d94da..fc98664 100644 --- a/include/sound/sof/virtio.h +++ b/include/sound/sof/virtio.h @@ -11,6 +11,8 @@ #ifndef _SOF_VIRTIO_H #define _SOF_VIRTIO_H
+#include <linux/list.h> + #include <sound/sof/header.h>
/* @@ -126,4 +128,75 @@ struct dsp_sof_data_resp { #define HDR_SIZE_REQ offsetof(struct dsp_sof_data_req, data) #define HDR_SIZE_RESP offsetof(struct dsp_sof_data_resp, data)
+struct snd_sof_dev; +struct sof_ipc_stream_posn; + +#if IS_ENABLED(CONFIG_VHOST_SOF) +struct firmware; + +struct vhost_dsp; +struct sof_vhost_ops { + int (*update_posn)(struct vhost_dsp *dsp, + struct sof_ipc_stream_posn *posn); +}; + +struct dsp_sof_client { + const struct firmware *fw; + struct snd_sof_dev *sdev; + /* List of guest endpoints, connecting to the host mixer or demux */ + struct list_head pipe_conn; + /* List of vhost instances on a DSP */ + struct list_head list; + + /* Component ID range index in the bitmap */ + unsigned int id; + + /* the comp_ids for this vm audio */ + int comp_id_begin; + int comp_id_end; + + unsigned int reset_count; + + struct vhost_dsp *vhost; +}; + +/* The below functions are only referenced when VHOST_SOF is selected */ +struct device; +void dsp_sof_client_release(struct dsp_sof_client *client); +struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, + struct vhost_dsp *dsp); +struct device *dsp_sof_dev_init(const struct sof_vhost_ops *ops); +struct vhost_dsp_topology; +int dsp_sof_set_tplg(struct dsp_sof_client *client, + const struct vhost_dsp_topology *tplg); +/* Copy audio data between DMA and VirtQueue */ +int dsp_sof_stream_data(struct dsp_sof_client *client, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply); +/* Forward an IPC message from a guest to the DSP */ +int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, + void *ipc_buf, void *reply_buf, + size_t count, size_t reply_sz); + +/* The below functions are always referenced, they need dummy counterparts */ +int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn); +void dsp_sof_suspend(struct snd_sof_dev *sdev); +void dsp_sof_dev_set(struct snd_sof_dev *sdev); +#else +static inline int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + return 0; +} + +static inline void dsp_sof_suspend(struct snd_sof_dev *sdev) +{ +} + +static inline void dsp_sof_dev_set(struct snd_sof_dev *sdev) +{ +} +#endif + #endif diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index c628103..a9b0f99a 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -118,4 +118,9 @@ #define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) #define VHOST_VSOCK_SET_RUNNING VHOST_SET_RUNNING
+/* VHOST_DSP specific defines */ + +#define VHOST_DSP_SET_GUEST_TPLG _IOW(VHOST_VIRTIO, 0x80, \ + struct vhost_dsp_topology) + #endif diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h index c907290..898660d 100644 --- a/include/uapi/linux/vhost_types.h +++ b/include/uapi/linux/vhost_types.h @@ -13,6 +13,7 @@
#include <linux/types.h> #include <linux/compiler.h> +#include <linux/limits.h> #include <linux/virtio_config.h> #include <linux/virtio_ring.h>
@@ -119,6 +120,12 @@ struct vhost_scsi_target { unsigned short reserved; };
+/* VHOST_DSP */ + +struct vhost_dsp_topology { + char name[NAME_MAX + 1]; +}; + /* Feature bits */ /* Log all write descriptors. Can be changed while device is active. */ #define VHOST_F_LOG_ALL 26 diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 1a841e8..2759217 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -24,6 +24,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dpcm.h> +#include <sound/soc-topology.h> #include <sound/initval.h>
#define DPCM_MAX_BE_USERS 8 @@ -1468,10 +1469,15 @@ static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, int stream;
/* adjust dir to stream */ - if (dir == SND_SOC_DAPM_DIR_OUT) + if (dir == SND_SOC_DAPM_DIR_OUT) { + if (!strcmp(widget->sname, SOC_VIRT_DAI_PLAYBACK)) + return false; stream = SNDRV_PCM_STREAM_PLAYBACK; - else + } else { + if (!strcmp(widget->sname, SOC_VIRT_DAI_CAPTURE)) + return false; stream = SNDRV_PCM_STREAM_CAPTURE; + }
rtd = dpcm_get_be(card, widget, stream); if (rtd) @@ -3018,14 +3024,6 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->pcm = pcm; pcm->private_data = rtd;
- if (rtd->dai_link->no_pcm || rtd->dai_link->params) { - if (playback) - pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; - if (capture) - pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; - goto out; - } - /* ASoC PCM operations */ if (rtd->dai_link->dynamic) { rtd->ops.open = dpcm_fe_dai_open; @@ -3045,6 +3043,19 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.pointer = soc_pcm_pointer; }
+ if (rtd->dai_link->no_pcm || rtd->dai_link->params) { + /* + * Usually in this case we also don't need to assign .ops + * callbacks, but in case of a "no PCM" pipeline, used by a VM + * we use the .prepare() hook to configure the hardware. + */ + if (playback) + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; + if (capture) + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; + goto out; + } + for_each_rtd_components(rtd, i, component) { const struct snd_soc_component_driver *drv = component->driver;
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index e96d8ff..90c772b 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -195,6 +195,7 @@ config SND_SOC_SOF tristate select SND_SOC_TOPOLOGY select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_SUPPORT + select VHOST if VHOST_SOF help This option is not user-selectable but automagically handled by 'select' statements at a higher level diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index b728d09..d947afc 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -14,6 +14,10 @@ ifdef CONFIG_SND_SOC_SOF_VIRTIO_FE snd-sof-objs += virtio-fe.o endif
+ifdef CONFIG_VHOST_SOF +snd-sof-objs += vhost-be.o +endif + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index d0bf082..09f01ea 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -13,6 +13,8 @@ #include <linux/module.h> #include <sound/soc.h> #include <sound/sof.h> +#include <sound/sof/virtio.h> +#include "sof-audio.h" #include "sof-priv.h" #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) @@ -222,6 +224,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto dbg_err; }
+ /* enable the vhost driver on this device */ + dsp_sof_dev_set(sdev); + /* init the IPC */ sdev->ipc = snd_sof_ipc_init(sdev); if (!sdev->ipc) { @@ -333,6 +338,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->vbe_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock);
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 8f52de0..fa8d3e2 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -14,6 +14,8 @@ #include <linux/mutex.h> #include <linux/types.h>
+#include <sound/sof/virtio.h> + #include "sof-priv.h" #include "sof-audio.h" #include "ops.h" @@ -451,6 +453,9 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id)
memcpy(&stream->posn, &posn, sizeof(posn));
+ /* optionally update position for vBE */ + dsp_sof_update_guest_posn(sdev, &posn); + /* only inform ALSA for period_wakeup mode */ if (!stream->substream->runtime->no_period_wakeup) snd_sof_pcm_period_elapsed(stream->substream); diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 3e05c41..0684b4c 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -91,7 +91,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) * To avoid sending IPC before the previous IPC is handled, we * schedule delayed work here to call the snd_pcm_period_elapsed(). */ - schedule_work(&spcm->stream[substream->stream].period_elapsed_work); + if (spcm->stream[substream->stream].substream) + schedule_work(&spcm->stream[substream->stream].period_elapsed_work); } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
@@ -748,6 +749,7 @@ static int sof_pcm_probe(struct snd_soc_component *component)
/* load the default topology */ sdev->component = component; + sdev->card = component->card;
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, "%s/%s", diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index c410822..0898d17 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -8,6 +8,8 @@ // Author: Liam Girdwood liam.r.girdwood@linux.intel.com //
+#include <sound/sof/virtio.h> + #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" @@ -247,6 +249,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* reset FW state */ sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
+ dsp_sof_suspend(sdev); + return ret; }
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index b4c5fe2..20413ec 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -151,6 +151,7 @@ int sof_restore_pipelines(struct device *dev) struct snd_sof_dai *dai; struct sof_ipc_comp_dai *comp_dai; struct sof_ipc_cmd_hdr *hdr; + struct sof_ipc_buffer *buffer; int ret;
/* restore pipeline components */ @@ -182,6 +183,14 @@ int sof_restore_pipelines(struct device *dev) pipeline = swidget->private; ret = sof_load_pipeline_ipc(dev, pipeline, &r); break; + + case snd_soc_dapm_buffer: + + buffer = swidget->private; + if (!buffer->size) + break; + + /* Fall through */ default: hdr = swidget->private; ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 4880b32..14682b9 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -41,6 +41,7 @@ struct snd_sof_pcm_stream { * active or not while suspending the stream */ bool suspend_ignored; + size_t guest_offset; };
/* ALSA SOF PCM device */ @@ -106,6 +107,7 @@ struct snd_sof_dai { struct snd_soc_component *scomp; const char *name; const char *cpu_dai_name; + unsigned int pipeline_id;
struct sof_ipc_comp_dai comp_dai; struct sof_ipc_dai_config *dai_config; @@ -216,4 +218,19 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
+#if IS_ENABLED(CONFIG_VHOST_SOF) +int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction); +#else +static inline int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction) +{ + return 0; +} +#endif + #endif diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 44780c2..c123e7f 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -57,6 +57,18 @@
/* The maximum number of components a virtio user vFE driver can use */ #define SOF_VIRTIO_MAX_UOS_COMPS 1000 +#define SOF_VIRTIO_COMP_ID_UNASSIGNED 0xffffffff + +/* + * in virtio iovec array: + * iovec[0]: the ipc message data between vFE and vBE + * iovec[1]: the ipc reply data between vFE and vBE + */ +#define SOF_VIRTIO_IPC_MSG 0 +#define SOF_VIRTIO_IPC_REPLY 1 + +/* Maximum supported number of VirtIO clients */ +#define SND_SOF_MAX_VFES BITS_PER_LONG
/* DSP power state */ enum sof_dsp_power_states { @@ -368,6 +380,7 @@ struct snd_sof_dev { * can't use const */ struct snd_soc_component_driver plat_drv; + struct snd_soc_card *card;
/* current DSP power state */ struct sof_dsp_power_state dsp_power_state; @@ -435,6 +448,9 @@ struct snd_sof_dev {
/* VirtIO fields for host and guest */ atomic_t reset_count; + struct list_head vbe_list; + struct list_head connector_list; + unsigned long vfe_mask[DIV_ROUND_UP(SND_SOF_MAX_VFES, BITS_PER_LONG)];
/* DMA for Trace */ struct snd_dma_buffer dmatb; diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index dcc897f..34e1020 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1333,6 +1333,7 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index,
if (ret == 0 && dai) { dai->scomp = scomp; + dai->pipeline_id = swidget->pipeline_id; memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai)); }
@@ -1379,6 +1380,13 @@ static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index,
swidget->private = buffer;
+ /* + * VirtIO dummy buffers between a dummy "aif_in" / "aif_out" widget and + * a mixer / demux respectively + */ + if (!buffer->size) + return 0; + ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer, sizeof(*buffer), r, sizeof(*r)); if (ret < 0) { @@ -1424,6 +1432,16 @@ static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, struct sof_ipc_comp_host *host; int ret;
+ /* + * For now just drop any virtual PCMs. Might need to use a more robust + * identification than the name + */ + if ((dir == SOF_IPC_STREAM_PLAYBACK && + !strcmp(SOC_VIRT_DAI_PLAYBACK, swidget->widget->sname)) || + (dir == SOF_IPC_STREAM_CAPTURE && + !strcmp(SOC_VIRT_DAI_CAPTURE, swidget->widget->sname))) + return 0; + host = kzalloc(sizeof(*host), GFP_KERNEL); if (!host) return -ENOMEM; @@ -3144,6 +3162,15 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST; link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST;
+ /* + * set .no_pcm on VirtIO hosts for pseudo PCMs, used as anchors + * for guest pipeline linking + */ + if (link->stream_name && + (!strcmp(link->stream_name, "vm_fe_playback") || + !strcmp(link->stream_name, "vm_fe_capture"))) + link->no_pcm = true; + /* nothing more to do for FE dai links */ return 0; } @@ -3365,6 +3392,32 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, }
/* + * In VirtIO case the host topology will contain a dummy PCM and a + * buffer at each location, where a partial guest topology will be + * attached. These dummy widgets shall not be sent to the DSP. We use + * them to identify and store VirtIO guest connection points. + */ + if (source_swidget->id == snd_soc_dapm_buffer) { + struct sof_ipc_buffer *buffer = source_swidget->private; + /* Is this a virtual playback buffer? */ + if (!buffer->size) { + ret = dsp_sof_add_conn(sdev, sink_swidget, + source_swidget, + SOF_IPC_STREAM_PLAYBACK); + goto err; + } + } else if (sink_swidget->id == snd_soc_dapm_buffer) { + struct sof_ipc_buffer *buffer = sink_swidget->private; + /* Is this a virtual capture buffer? */ + if (!buffer->size) { + ret = dsp_sof_add_conn(sdev, source_swidget, + sink_swidget, + SOF_IPC_STREAM_CAPTURE); + goto err; + } + } + + /* * Don't send routes whose sink widget is of type * output or out_drv to the DSP */ @@ -3631,7 +3684,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) /* VirtIO guests request topology from the host */ if (sdev->pdata->vfe) { fw = &vfe_fw; - ret = sof_ops(sdev)->request_topology(sdev, file, &vfe_fw); + ret = sof_ops(sdev)->request_topology(sdev, &vfe_fw); } else { ret = request_firmware(&fw, file, sdev->dev); } diff --git a/sound/soc/sof/vhost-be.c b/sound/soc/sof/vhost-be.c new file mode 100644 index 00000000..79d6f8c --- /dev/null +++ b/sound/soc/sof/vhost-be.c @@ -0,0 +1,1093 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2017-2020 Intel Corporation. All rights reserved. + * + * Author: Libin Yang libin.yang@intel.com + * Luo Xionghu xionghu.luo@intel.com + * Liam Girdwood liam.r.girdwood@linux.intel.com + * Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com + */ + +#include <asm/unaligned.h> + +#include <linux/atomic.h> +#include <linux/device.h> +#include <linux/file.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/hw_random.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/uio.h> +#include <linux/vhost_types.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> + +#include <sound/pcm_params.h> +#include <sound/sof.h> + +#include <sound/sof/virtio.h> + +#include "sof-audio.h" +#include "sof-priv.h" +#include "ops.h" + +/* A connection of a guest pipeline into the host topology */ +struct dsp_pipeline_connect { + int host_pipeline_id; + int guest_pipeline_id; + int host_component_id; + int guest_component_id; + enum sof_ipc_stream_direction direction; + struct list_head list; +}; + +static const char dsp_pcm_name[] = "VHost PCM"; + +/* + * This function is used to find a BE substream. It uses the dai_link stream + * name for that. The current dai_link stream names are "vm_fe_playback" and + * "vm_fe_capture," which means only one Virtual Machine is supported and the VM + * only supports one playback pcm and one capture pcm. After we switch to the + * new topology, we can support multiple VMs and multiple PCM streams for each + * VM. This function may be abandoned after switching to the new topology. + * + * Note: if this function returns substream != NULL, then *rtd != NULL too (if + * rtd != NULL, of course). If it returns NULL, *rtd hasn't been changed. + */ +static struct snd_pcm_substream *dsp_sof_get_substream(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime **rtd, int direction) +{ + struct snd_soc_card *card = sdev->card; + struct snd_soc_pcm_runtime *r; + + for_each_card_rtds(card, r) { + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = r->pcm; + if (!pcm || !pcm->internal) + continue; + + /* Find a substream dedicated to the vFE. */ + substream = pcm->streams[direction].substream; + if (substream) { + struct snd_soc_dai_link *dai_link = r->dai_link; + + /* FIXME: replace hard-coded stream name */ + if (dai_link->stream_name && + (!strcmp(dai_link->stream_name, "vm_fe_playback") || + !strcmp(dai_link->stream_name, "vm_fe_capture"))) { + if (rtd) + *rtd = r; + return substream; + } + } + } + + return NULL; +} + +static struct snd_sof_pcm *dsp_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction) +{ + return snd_sof_find_spcm_comp(sdev->component, comp_id, direction); +} + +/* + * Prepare hardware parameters, required for buffer allocation and PCM + * configuration + */ +static int dsp_sof_assemble_params(struct sof_ipc_pcm_params *pcm, + struct snd_pcm_hw_params *params) +{ + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = + pcm->params.channels; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = + pcm->params.rate; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES)->min = + pcm->params.host_period_bytes; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)->min = + pcm->params.buffer.size; + + snd_mask_none(fmt); + switch (pcm->params.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S16); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S32); + break; + case SOF_IPC_FRAME_FLOAT: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_FLOAT); + break; + default: + return -EINVAL; + } + return 0; +} + +/* Handle SOF_IPC_STREAM_PCM_PARAMS IPC */ +static int dsp_sof_stream_hw_params(struct snd_sof_dev *sdev, + struct sof_ipc_pcm_params *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct snd_pcm_hw_params params; + int direction = pcm->params.direction; + int ret; + + /* find the proper substream */ + substream = dsp_sof_get_substream(sdev, NULL, direction); + if (!substream) + return -ENODEV; + + runtime = substream->runtime; + if (!runtime) { + dev_err(sdev->dev, "no runtime is available for hw_params\n"); + return -ENODEV; + } + + /* TODO: codec hw_params */ + + /* Use different stream_tag from FE. This is the real tag */ + dsp_sof_assemble_params(pcm, ¶ms); + + /* Allocate a duplicate of the guest buffer */ + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(¶ms)); + if (ret < 0) { + dev_err(sdev->dev, + "error %d: could not allocate %d bytes for PCM "%s"\n", + ret, params_buffer_bytes(¶ms), substream->pcm->name); + return ret; + } + + return snd_sof_pcm_platform_hw_params(sdev, substream, ¶ms, + &pcm->params); +} + +/* Allocate a runtime object and buffer pages */ +static int dsp_sof_pcm_open(struct snd_sof_dev *sdev, void *ipc_data) +{ + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + struct sof_ipc_pcm_params *pcm = ipc_data; + struct snd_pcm_runtime *runtime; + struct snd_sof_pcm *spcm; + u32 comp_id = pcm->comp_id; + size_t size; + int direction, ret; + + spcm = dsp_sof_find_spcm_comp(sdev, comp_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "%s(): no SPCM for comp %u\n", __func__, comp_id); + return -ENODEV; + } + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) { + dev_err(sdev->dev, "%s(): no substream for comp %u\n", __func__, comp_id); + return -ENODEV; + } + if (substream->ref_count > 0) + return -EBUSY; + substream->ref_count++; /* set it used */ + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) + return -ENOMEM; + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); + runtime->status = alloc_pages_exact(size, GFP_KERNEL); + if (!runtime->status) { + ret = -ENOMEM; + goto eruntime; + } + memset((void *)runtime->status, 0, size); + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); + runtime->control = alloc_pages_exact(size, GFP_KERNEL); + if (!runtime->control) { + dev_err(sdev->dev, "fail to alloc pages for runtime->control"); + ret = -ENOMEM; + goto estatus; + } + 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; + substream->private_data = rtd; + rtd->dpcm[direction].runtime = runtime; + substream->stream = direction; + + substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + substream->dma_buffer.dev.dev = sdev->dev; + + /* check with spcm exists or not */ + spcm->stream[direction].posn.host_posn = 0; + spcm->stream[direction].posn.dai_posn = 0; + spcm->stream[direction].substream = substream; + spcm->stream[direction].guest_offset = 0; + + /* TODO: codec open */ + + snd_sof_pcm_platform_open(sdev, substream); + + return 0; + +estatus: + free_pages_exact(runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); +eruntime: + kfree(runtime); + return ret; +} + +static void dsp_sof_stream_close(struct snd_sof_dev *sdev, int direction) +{ + struct snd_pcm_substream *substream = dsp_sof_get_substream(sdev, NULL, + direction); + if (!substream) + return; + + /* TODO: codec close */ + + substream->ref_count = 0; + if (substream->runtime) { + snd_sof_pcm_platform_close(sdev, substream); + + free_pages_exact(substream->runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + free_pages_exact(substream->runtime->control, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))); + kfree(substream->runtime); + substream->runtime = NULL; + } +} + +/* Handle the SOF_IPC_STREAM_PCM_FREE IPC */ +static int dsp_sof_pcm_close(struct snd_sof_dev *sdev, void *ipc_data) +{ + struct snd_sof_pcm *spcm; + struct sof_ipc_stream *stream; + int direction; + + stream = (struct sof_ipc_stream *)ipc_data; + + spcm = dsp_sof_find_spcm_comp(sdev, stream->comp_id, &direction); + if (!spcm) + return 0; + + dsp_sof_stream_close(sdev, direction); + + return 0; +} + +/* Copy audio data from DMA buffers for capture */ +static int dsp_sof_stream_capture(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + size_t data_size = req->size; + int ret; + + stream->guest_offset = req->offset; + + if (req->offset + data_size > runtime->dma_bytes) { + reply->size = 0; + ret = -ENOBUFS; + } else { + stream->guest_offset += data_size; + + memcpy(reply->data, runtime->dma_area + req->offset, data_size); + reply->size = data_size; + ret = 0; + } + + reply->error = ret; + + return ret; +} + +/* Copy audio data to DMA buffers for playback */ +static int dsp_sof_stream_playback(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + size_t data_size = req->size; + int ret; + + stream->guest_offset = req->offset; + + if (req->offset + data_size > runtime->dma_bytes) { + ret = -ENOBUFS; + } else { + stream->guest_offset += data_size; + + memcpy(runtime->dma_area + req->offset, req->data, data_size); + ret = 0; + } + + reply->error = ret; + reply->size = 0; + + return ret; +} + +/* Send or receive audio data */ +int dsp_sof_stream_data(struct dsp_sof_client *client, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + int direction; + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pcm *spcm = dsp_sof_find_spcm_comp(sdev, + req->comp_id, &direction); + struct snd_pcm_substream *substream = dsp_sof_get_substream(sdev, NULL, + direction); + + if (!spcm || !substream) { + reply->error = -ENODEV; + reply->size = 0; + return reply->error; + } + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + return dsp_sof_stream_playback(spcm->stream + direction, + substream->runtime, req, reply); + + return dsp_sof_stream_capture(spcm->stream + direction, + substream->runtime, req, reply); +} +EXPORT_SYMBOL_GPL(dsp_sof_stream_data); + +/* Handle the stream IPC */ +static int dsp_sof_ipc_stream(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr, void *reply_buf) +{ + struct sof_ipc_pcm_params *pcm; + struct sof_ipc_stream *stream; + struct snd_soc_pcm_runtime *rtd; + struct snd_pcm_substream *substream; + int ret = 0, direction, comp_id; + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + struct snd_soc_dpcm *dpcm; + + switch (cmd) { + case SOF_IPC_STREAM_PCM_PARAMS: + ret = dsp_sof_pcm_open(sdev, hdr); + if (ret < 0) + break; + pcm = container_of(hdr, struct sof_ipc_pcm_params, hdr); + ret = dsp_sof_stream_hw_params(sdev, pcm); + break; + case SOF_IPC_STREAM_TRIG_START: + stream = container_of(hdr, struct sof_ipc_stream, hdr); + comp_id = stream->comp_id; + if (!dsp_sof_find_spcm_comp(sdev, comp_id, &direction)) { + ret = -ENODEV; + break; + } + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) { + ret = -ENODEV; + break; + } + + /* Create an RTD, a CPU DAI when parsing aif_in */ + snd_soc_runtime_activate(rtd, direction); + snd_soc_dpcm_runtime_update(sdev->card, SND_SOC_DPCM_UPDATE_NEW_ONLY); + + dpcm = list_first_entry(&rtd->dpcm[direction].be_clients, + struct snd_soc_dpcm, list_be); + + if (list_empty(&rtd->dpcm[direction].be_clients)) + dev_warn(rtd->dev, "BE client list empty\n"); + else if (!dpcm->be) + dev_warn(rtd->dev, "No BE\n"); + else + dpcm->be->dpcm[direction].state = SND_SOC_DPCM_STATE_HW_PARAMS; + + ret = rtd->ops.prepare(substream); + if (ret < 0) + break; + snd_sof_pcm_platform_trigger(sdev, substream, + SNDRV_PCM_TRIGGER_START); + break; + case SOF_IPC_STREAM_PCM_FREE: + dsp_sof_pcm_close(sdev, hdr); + break; + } + + return ret; +} + +/* validate component IPC */ +static int dsp_sof_ipc_comp(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_ctrl_data *cdata = container_of(hdr, + struct sof_ipc_ctrl_data, rhdr.hdr); + + return cdata->comp_id < client->comp_id_begin || + cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; +} + +/* process PM IPC */ +static int dsp_sof_ipc_pm(struct dsp_sof_client *client, struct sof_ipc_cmd_hdr *hdr, + struct sof_vfe_ipc_power_resp *resp) +{ + struct snd_sof_dev *sdev = client->sdev; + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + struct sof_vfe_ipc_power_req *rq; + unsigned int reset_count; + int ret; + + switch (cmd) { + case SOF_IPC_PM_VFE_POWER_STATUS: + rq = container_of(hdr, struct sof_vfe_ipc_power_req, hdr); + if (rq->power) { + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) + return ret; + } + + /* + * The DSP is runtime-PM active now for IPC processing, so + * .reset_count won't change + */ + reset_count = atomic_read(&sdev->reset_count); + resp->reply.hdr.size = sizeof(*resp); + resp->reply.hdr.cmd = SOF_IPC_GLB_PM_MSG | + SOF_IPC_PM_VFE_POWER_STATUS; + resp->reply.error = 0; + resp->reset_status = reset_count == client->reset_count ? + SOF_VIRTIO_IPC_RESET_NONE : SOF_VIRTIO_IPC_RESET_DONE; + + if (!rq->power) { + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + } + return 1; + } + + return 0; +} + +static int dsp_sof_error_reply(struct sof_ipc_reply *rhdr, unsigned int cmd, + int err) +{ + rhdr->hdr.size = sizeof(*rhdr); + rhdr->hdr.cmd = cmd; + rhdr->error = err; + + return err; +} + +int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction) +{ + struct dsp_pipeline_connect *conn; + + if (w_host->pipeline_id == w_guest->pipeline_id) + return 0; + + conn = devm_kmalloc(sdev->dev, sizeof(*conn), GFP_KERNEL); + if (!conn) + return -ENOMEM; + + /* + * We'll need this mapping twice: first to overwrite a sink or source ID + * for SOF_IPC_TPLG_COMP_CONNECT, then to overwrite the scheduling + * component ID for SOF_IPC_TPLG_PIPE_NEW + */ + conn->host_pipeline_id = w_host->pipeline_id; + conn->guest_pipeline_id = w_guest->pipeline_id; + conn->host_component_id = w_host->comp_id; + conn->direction = direction; + + list_add_tail(&conn->list, &sdev->connector_list); + + return 0; +} + +/* Handle some special cases of the "new component" IPC */ +static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) +{ + struct sof_ipc_comp *comp = container_of(hdr, struct sof_ipc_comp, hdr); + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pcm *spcm, *last; + struct sof_ipc_comp_host *host; + struct dsp_pipeline_connect *conn; + + if (comp->id < client->comp_id_begin || + comp->id >= client->comp_id_end) + return -EINVAL; + + switch (comp->type) { + case SOF_COMP_VIRT_CON: + list_for_each_entry(conn, &sdev->connector_list, list) + if (conn->guest_pipeline_id == comp->pipeline_id) { + /* This ID will have to be overwritten */ + conn->guest_component_id = comp->id; + break; + } + + dsp_sof_error_reply(rhdr, hdr->cmd, 0); + + /* The firmware doesn't need this component */ + return 1; + case SOF_COMP_HOST: + /* + * TODO: below is a temporary solution. next step is + * to create a whole pcm stuff incluing substream + * based on Liam's suggestion. + */ + + /* + * let's create spcm in HOST ipc + * spcm should be created in pcm load, but there is no such ipc + * so we create it here. It is needed for the "period elapsed" + * IPC from the firmware, which will use the host ID to route + * the IPC back to the PCM. + */ + host = container_of(comp, struct sof_ipc_comp_host, comp); + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = + SOF_VIRTIO_COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = + SOF_VIRTIO_COMP_ID_UNASSIGNED; + spcm->stream[host->direction].comp_id = host->comp.id; + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].posn.comp_id = + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].posn.comp_id = + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id; + INIT_WORK(&spcm->stream[host->direction].period_elapsed_work, + snd_sof_pcm_period_elapsed_work); + last = list_last_entry(&sdev->pcm_list, struct snd_sof_pcm, list); + spcm->pcm.dai_id = last->pcm.dai_id + 1; + strncpy(spcm->pcm.pcm_name, dsp_pcm_name, + sizeof(spcm->pcm.pcm_name)); + list_add(&spcm->list, &sdev->pcm_list); + + client->reset_count = atomic_read(&sdev->reset_count); + break; + default: + break; + } + + return 0; +} + +/* Handle the "new pipeline" IPC: replace the scheduling sink ID */ +static int dsp_sof_ipc_tplg_pipe_new(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_pipe_new *pipeline = container_of(hdr, + struct sof_ipc_pipe_new, hdr); + struct snd_sof_dev *sdev = client->sdev; + struct dsp_pipeline_connect *conn; + + list_for_each_entry(conn, &sdev->connector_list, list) + if (pipeline->pipeline_id == conn->guest_pipeline_id) { + struct snd_sof_dai *dai; + + dai = snd_sof_find_dai_pipe(sdev, conn->host_pipeline_id); + if (!dai) { + dev_warn(sdev->dev, + "no DAI with pipe %u found\n", + conn->host_pipeline_id); + continue; + } + + /* Overwrite the scheduling sink ID with the DAI ID */ + pipeline->sched_id = dai->comp_dai.comp.id; + break; + } + + return 0; +} + +/* Handle the "connect components" IPC: replace the virtual component ID */ +static int dsp_sof_ipc_tplg_comp_connect(struct dsp_sof_client *client, + int vq_idx, struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_pipe_comp_connect *connect = container_of(hdr, + struct sof_ipc_pipe_comp_connect, hdr); + struct dsp_pipeline_connect *conn; + + list_for_each_entry(conn, &client->sdev->connector_list, list) { + if (conn->direction == SOF_IPC_STREAM_PLAYBACK && + connect->sink_id == conn->guest_component_id) { + /* Overwrite the sink ID with the actual mixer component ID */ + connect->sink_id = conn->host_component_id; + break; + } + + if (conn->direction == SOF_IPC_STREAM_CAPTURE && + connect->source_id == conn->guest_component_id) { + /* Overwrite the source ID with the actual demux component ID */ + connect->source_id = conn->host_component_id; + break; + } + } + + return 0; +} + +/* Read guest's topology file and send it back to the requester */ +static int dsp_sof_ipc_tplg_read(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_vfe_ipc_tplg_req *tplg = container_of(hdr, + struct sof_vfe_ipc_tplg_req, hdr); + struct sof_vfe_ipc_tplg_resp *partdata = reply_buf; + const struct firmware *fw = client->fw; + size_t to_copy, remainder; + + if (reply_sz <= sizeof(partdata->reply)) + return -ENOBUFS; + + if (!fw || fw->size <= tplg->offset) + return -EINVAL; + + remainder = fw->size - tplg->offset; + + partdata->reply.hdr.cmd = hdr->cmd; + /* + * Non-standard size use: it's the remaining firmware bytes, plus + * the header, that way the last part will contain a correct size + */ + partdata->reply.hdr.size = remainder + sizeof(partdata->reply); + + to_copy = min_t(size_t, reply_sz - sizeof(partdata->reply), + remainder); + + memcpy(partdata->data, fw->data + tplg->offset, to_copy); + + if (remainder == to_copy) { + release_firmware(fw); + client->fw = NULL; + } + + return 0; +} + +/* Send the next component ID to the guest */ +static int dsp_sof_ipc_tplg_comp_id(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_vfe_ipc_tplg_resp *partdata = reply_buf; + + client->comp_id_begin = client->sdev->next_comp_id + + client->id * SOF_VIRTIO_MAX_UOS_COMPS; + client->comp_id_end = client->comp_id_begin + SOF_VIRTIO_MAX_UOS_COMPS; + + partdata->reply.hdr.cmd = hdr->cmd; + partdata->reply.hdr.size = sizeof(partdata->reply) + sizeof(u32); + *(u32 *)partdata->data = client->comp_id_begin; + + return 0; +} + +/* Handle topology IPC */ +static int dsp_sof_ipc_tplg(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + int ret; + + switch (cmd) { + case SOF_IPC_TPLG_COMP_NEW: + return dsp_sof_ipc_tplg_comp_new(client, vq_idx, hdr, + reply_buf); + case SOF_IPC_TPLG_PIPE_NEW: + return dsp_sof_ipc_tplg_pipe_new(client, vq_idx, hdr); + case SOF_IPC_TPLG_COMP_CONNECT: + return dsp_sof_ipc_tplg_comp_connect(client, vq_idx, hdr); + case SOF_IPC_TPLG_VFE_GET: + ret = dsp_sof_ipc_tplg_read(client, hdr, reply_buf, reply_sz); + return ret < 0 ? ret : 1; + case SOF_IPC_TPLG_VFE_COMP_ID: + ret = dsp_sof_ipc_tplg_comp_id(client, hdr, reply_buf, reply_sz); + return ret < 0 ? ret : 1; + } + + return 0; +} + +/* Call SOF core to send an IPC message to the DSP */ +static void sof_virtio_send_ipc(struct snd_sof_dev *sdev, void *ipc_data, + void *reply_buf, size_t count, + size_t reply_size) +{ + struct snd_sof_ipc *ipc = sdev->ipc; + struct sof_ipc_cmd_hdr *hdr = ipc_data; + struct sof_ipc_reply *rhdr = reply_buf; + int ret = sof_ipc_tx_message(ipc, hdr->cmd, ipc_data, count, + reply_buf, reply_size); + + if (ret < 0 && !rhdr->error) + rhdr->error = ret; +} + +/* Post-process SOF_IPC_STREAM_PCM_PARAMS */ +static int dsp_sof_ipc_stream_param_post(struct snd_sof_dev *sdev, + void *reply_buf) +{ + struct sof_ipc_pcm_params_reply *reply = reply_buf; + u32 comp_id = reply->comp_id; + int direction, ret; + struct snd_sof_pcm *spcm = dsp_sof_find_spcm_comp(sdev, comp_id, + &direction); + if (!spcm) + return -ENODEV; + + ret = snd_sof_ipc_pcm_params(sdev, spcm->stream[direction].substream, + reply); + if (ret < 0) + dev_err(sdev->dev, "error: got wrong reply for PCM %d\n", + spcm->pcm.pcm_id); + + return ret; +} + +/* Handle the stream IPC post-processing */ +static int dsp_sof_ipc_stream_codec(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_stream *stream = container_of(hdr, + struct sof_ipc_stream, hdr); + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + unsigned int i; + int direction; + + if (!dsp_sof_find_spcm_comp(sdev, stream->comp_id, &direction)) + return -ENODEV; + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + const struct snd_soc_dai_ops *ops = codec_dai->driver->ops; + + /* + * Now we are ready to trigger start. + * Let's unmute the codec firstly + */ + snd_soc_dai_digital_mute(codec_dai, 0, direction); + if (ops->trigger) { + int ret = ops->trigger(substream, + SNDRV_PCM_TRIGGER_START, + codec_dai); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int dsp_sof_ipc_stream_stop(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_stream *stream = container_of(hdr, + struct sof_ipc_stream, hdr); + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + int direction, comp_id = stream->comp_id; + unsigned int i; + + if (!dsp_sof_find_spcm_comp(sdev, comp_id, &direction)) + return -ENODEV; + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + const struct snd_soc_dai_ops *ops = codec_dai->driver->ops; + + if (ops->trigger) { + int ret = ops->trigger(substream, + SNDRV_PCM_TRIGGER_STOP, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "trigger stop fails\n"); + return ret; + } + } + } + + snd_sof_pcm_platform_trigger(sdev, substream, + SNDRV_PCM_TRIGGER_STOP); + snd_soc_dpcm_runtime_update(sdev->card, + SND_SOC_DPCM_UPDATE_OLD_ONLY); + snd_soc_runtime_deactivate(rtd, direction); + + return 0; +} + +/* Handle an IPC reply */ +static int dsp_sof_ipc_post(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr, void *reply_buf) +{ + struct sof_ipc_reply *rhdr = reply_buf; + int ret; + + switch (hdr->cmd) { + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS: + if (rhdr->error < 0) + break; + return dsp_sof_ipc_stream_param_post(sdev, reply_buf); + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_TRIG_START: + if (rhdr->error < 0) + break; + /* setup the codec */ + return dsp_sof_ipc_stream_codec(sdev, hdr); + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_TRIG_STOP: + ret = dsp_sof_ipc_stream_stop(sdev, hdr); + return rhdr->error < 0 ? rhdr->error : ret; + } + + return rhdr->error; +} + +/* Forward an IPC message from a guest to the DSP */ +int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, + void *ipc_buf, void *reply_buf, + size_t count, size_t reply_sz) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_cmd_hdr *hdr = ipc_buf; + struct sof_ipc_reply *rhdr = reply_buf; + u32 type; + int ret; + + /* validate IPC */ + if (!count) { + dev_err(sdev->dev, "error: guest IPC size is 0\n"); + return -EINVAL; + } + + type = hdr->cmd & SOF_GLB_TYPE_MASK; + rhdr->error = 0; + + /* validate the ipc */ + switch (type) { + case SOF_IPC_GLB_COMP_MSG: + ret = dsp_sof_ipc_comp(client, hdr); + if (ret < 0) + goto err; + break; + case SOF_IPC_GLB_STREAM_MSG: + ret = dsp_sof_ipc_stream(sdev, hdr, reply_buf); + if (ret < 0) { + dev_err(sdev->dev, "STREAM IPC 0x%x failed %d!\n", + hdr->cmd, ret); + goto err; + } + break; + case SOF_IPC_GLB_PM_MSG: + ret = dsp_sof_ipc_pm(client, hdr, reply_buf); + if (!ret) + break; + if (ret < 0) + goto err; + return 0; + case SOF_IPC_GLB_DAI_MSG: + /* + * After we use the new topology solution for FE, + * we will not touch DAI anymore. + */ + break; + case SOF_IPC_GLB_TPLG_MSG: + ret = dsp_sof_ipc_tplg(client, vq_idx, hdr, reply_buf, + reply_sz); + if (!ret) + break; + if (ret < 0) + goto err; + return 0; + case SOF_IPC_GLB_TRACE_MSG: + /* Trace should be initialized in SOS, skip FE requirement */ + return 0; + default: + dev_warn(sdev->dev, "unhandled IPC 0x%x!\n", hdr->cmd); + break; + } + + /* now send the IPC */ + sof_virtio_send_ipc(sdev, ipc_buf, reply_buf, count, reply_sz); + + /* For some IPCs, the reply needs to be handled */ + ret = dsp_sof_ipc_post(sdev, hdr, reply_buf); + if (ret < 0) + dev_err(sdev->dev, "err: failed to send %u bytes virtio IPC 0x%x: %d\n", + hdr->size, hdr->cmd, ret); + + return ret; + +err: + return dsp_sof_error_reply(rhdr, hdr->cmd, ret); +} +EXPORT_SYMBOL_GPL(dsp_sof_ipc_fwd); + +int dsp_sof_set_tplg(struct dsp_sof_client *client, + const struct vhost_dsp_topology *tplg) +{ + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pdata *plat_data = sdev->pdata; + char *path; + int ret; + + path = kasprintf(GFP_KERNEL, "%s/%s", plat_data->tplg_filename_prefix, + tplg->name); + if (!path) + return -ENOMEM; + + ret = request_firmware(&client->fw, path, sdev->dev); + if (ret < 0) + dev_err(sdev->dev, + "error: request VFE topology %s failed: %d\n", + tplg->name, ret); + kfree(path); + + return ret; +} +EXPORT_SYMBOL_GPL(dsp_sof_set_tplg); + +void dsp_sof_suspend(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm, *next; + + list_for_each_entry_safe(spcm, next, &sdev->pcm_list, list) + if (!strcmp(dsp_pcm_name, spcm->pcm.pcm_name)) { + list_del(&spcm->list); + dsp_sof_stream_close(sdev, SNDRV_PCM_STREAM_PLAYBACK); + dsp_sof_stream_close(sdev, SNDRV_PCM_STREAM_CAPTURE); + kfree(spcm); + } +} + +/* A VM instance has closed the miscdevice */ +void dsp_sof_client_release(struct dsp_sof_client *client) +{ + bitmap_release_region(client->sdev->vfe_mask, client->id, 0); + + list_del(&client->list); + + kfree(client); +} +EXPORT_SYMBOL_GPL(dsp_sof_client_release); + +/* A new VM instance has opened the miscdevice */ +struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, + struct vhost_dsp *dsp) +{ + int id = bitmap_find_free_region(sdev->vfe_mask, SND_SOF_MAX_VFES, 0); + struct dsp_sof_client *client; + + if (id < 0) + return NULL; + + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + bitmap_release_region(sdev->vfe_mask, id, 0); + return NULL; + } + + client->sdev = sdev; + client->id = id; + client->vhost = dsp; + + /* + * link to sdev->vbe_list + * Maybe virtio_miscdev managing the list is more reasonable. + * Let's use sdev to manage the FE audios now. + * FIXME: protect the list. + */ + list_add(&client->list, &sdev->vbe_list); + + return client; +} +EXPORT_SYMBOL_GPL(dsp_sof_client_add); + +/* The struct snd_sof_dev instance, that VirtIO guests will be using */ +static struct snd_sof_dev *vhost_sof_dev; +static const struct sof_vhost_ops *vhost_ops; + +/* Find a client by component ID */ +static struct dsp_sof_client *dsp_sof_comp_to_client(struct snd_sof_dev *sdev, + int comp_id) +{ + struct dsp_sof_client *client; + + list_for_each_entry(client, &sdev->vbe_list, list) + if (comp_id < client->comp_id_end && + comp_id >= client->comp_id_begin) + return client; + + return NULL; +} + +/* Called from the position update IRQ thread */ +int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + struct dsp_sof_client *client = dsp_sof_comp_to_client(sdev, + posn->comp_id); + + if (!client || !vhost_ops) + return -ENODEV; + + return vhost_ops->update_posn(client->vhost, posn); +} + +/* The vhost driver is loaded */ +struct device *dsp_sof_dev_init(const struct sof_vhost_ops *ops) +{ + if (!vhost_sof_dev) + return NULL; + + bitmap_zero(vhost_sof_dev->vfe_mask, SND_SOF_MAX_VFES); + + vhost_ops = ops; + + return vhost_sof_dev->dev; +} +EXPORT_SYMBOL_GPL(dsp_sof_dev_init); + +/* This SOF device will be used for VirtIO */ +void dsp_sof_dev_set(struct snd_sof_dev *sdev) +{ + INIT_LIST_HEAD(&sdev->connector_list); + vhost_sof_dev = sdev; +}
The SOF DSP vhost driver consists of two parts: a sound and a vhost part. This patch implements the vhost part of the driver. It handles QEMU communication with the vhost misc device and virtual queues to any VirtIO guests.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- drivers/vhost/Kconfig | 7 + drivers/vhost/Makefile | 5 + drivers/vhost/dsp.c | 728 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 740 insertions(+) create mode 100644 drivers/vhost/dsp.c
diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig index 3d03ccb..b9f3071 100644 --- a/drivers/vhost/Kconfig +++ b/drivers/vhost/Kconfig @@ -34,6 +34,13 @@ config VHOST_VSOCK To compile this driver as a module, choose M here: the module will be called vhost_vsock.
+config VHOST_SOF + bool "Vhost SOF driver" + default n + ---help--- + SOF vhost VirtIO driver. It exports the same IPC interface, as the + one, used for DSP communication, to Linux VirtIO guests. + config VHOST tristate ---help--- diff --git a/drivers/vhost/Makefile b/drivers/vhost/Makefile index 6c6df24..1914561 100644 --- a/drivers/vhost/Makefile +++ b/drivers/vhost/Makefile @@ -10,4 +10,9 @@ vhost_vsock-y := vsock.o
obj-$(CONFIG_VHOST_RING) += vringh.o
+ifdef CONFIG_VHOST_SOF +obj-$(CONFIG_SND_SOC_SOF) += vhost_sof.o +vhost_sof-y := dsp.o +endif + obj-$(CONFIG_VHOST) += vhost.o diff --git a/drivers/vhost/dsp.c b/drivers/vhost/dsp.c new file mode 100644 index 00000000..205ae8c --- /dev/null +++ b/drivers/vhost/dsp.c @@ -0,0 +1,728 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2019-2020 Intel Corporation. All rights reserved. + * + * Author: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com + * + * vhost-SOF VirtIO interface + */ + +#include <linux/bitmap.h> +#include <linux/compat.h> +#include <linux/file.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/vhost.h> +#include <linux/workqueue.h> + +#include <sound/sof/stream.h> +#include <sound/sof/virtio.h> + +#include "vhost.h" + +#define VHOST_DSP_FEATURES VHOST_FEATURES +#define VHOST_DSP_BATCH 64 +#define VHOST_DSP_WEIGHT 0x80000 +#define VHOST_DSP_PKT_WEIGHT 256 + +struct vhost_dsp_virtqueue { + struct vhost_virtqueue vq; +}; + +struct snd_sof_dev; + +struct dsp_sof_client; +struct vhost_dsp { + struct vhost_dev dev; + struct vhost_dsp_virtqueue vqs[SOF_VIRTIO_NUM_OF_VQS]; + struct vhost_work work; + struct vhost_virtqueue *vq_p[SOF_VIRTIO_NUM_OF_VQS]; + + bool active; + + spinlock_t posn_lock; /* Protects posn_list */ + struct list_head posn_list; + struct list_head posn_buf_list; + + u8 ipc_buf[SOF_IPC_MSG_MAX_SIZE]; + u8 reply_buf[SOF_IPC_MSG_MAX_SIZE]; + + union { + struct dsp_sof_data_req data_req; + struct dsp_sof_data_resp data_resp; + }; + + struct dsp_sof_client *snd; +}; + +/* A stream position message, waiting to be sent to a guest */ +struct vhost_dsp_posn { + struct list_head list; + struct sof_ipc_stream_posn posn; +}; + +/* A guest buffer, waiting to be filled with a stream position message */ +struct vhost_dsp_iovec { + struct list_head list; + int head; +}; + +/* A guest is booting */ +static int vhost_dsp_activate(struct vhost_dsp *dsp) +{ + unsigned int i; + int ret = 0; + + mutex_lock(&dsp->dev.mutex); + + /* Wait until all the VirtQueues have been initialised */ + if (!dsp->active) { + for (i = 0; i < ARRAY_SIZE(dsp->vqs); i++) { + struct vhost_virtqueue *vq = &dsp->vqs[i].vq; + + /* .private_data is required != NULL */ + vq->private_data = dsp->vqs + i; + /* needed for re-initialisation upon guest reboot */ + ret = vhost_vq_init_access(vq); + if (ret) + vq_err(vq, + "%s(): error %d initialising vq #%d\n", + __func__, ret, i); + } + if (!ret) + dsp->active = true; + } + + mutex_unlock(&dsp->dev.mutex); + + return ret; +} + +/* A guest is powered off or reset */ +static void vhost_dsp_deactivate(struct vhost_dsp *dsp) +{ + unsigned int i; + + mutex_lock(&dsp->dev.mutex); + + if (dsp->active) { + struct vhost_dsp_iovec *buf, *next; + unsigned long flags; + + dsp->active = false; + + spin_lock_irqsave(&dsp->posn_lock, flags); + list_for_each_entry_safe(buf, next, &dsp->posn_buf_list, list) { + list_del(&buf->list); + kfree(buf); + } + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + /* signal, that we're inactive */ + for (i = 0; i < ARRAY_SIZE(dsp->vqs); i++) + dsp->vqs[i].vq.private_data = NULL; + } + + mutex_unlock(&dsp->dev.mutex); +} + +/* No special features at the moment */ +static int vhost_dsp_set_features(struct vhost_dsp *dsp, u64 features) +{ + struct vhost_virtqueue *vq; + unsigned int i; + + if (features & ~VHOST_DSP_FEATURES) + return -EOPNOTSUPP; + + mutex_lock(&dsp->dev.mutex); + + if ((features & (1 << VHOST_F_LOG_ALL)) && + !vhost_log_access_ok(&dsp->dev)) { + mutex_unlock(&dsp->dev.mutex); + return -EFAULT; + } + + for (i = 0; i < SOF_VIRTIO_NUM_OF_VQS; i++) { + vq = &dsp->vqs[i].vq; + + mutex_lock(&vq->mutex); + vq->acked_features = features; + mutex_unlock(&vq->mutex); + } + + mutex_unlock(&dsp->dev.mutex); + + return 0; +} + +/* .ioctl(): we only use VHOST_SET_RUNNING in a not-default way */ +static long vhost_dsp_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + struct vhost_dsp *dsp = filp->private_data; + void __user *argp = (void __user *)arg; + struct vhost_dsp_topology tplg; + u64 __user *featurep = argp; + u64 features; + int start; + long ret; + + switch (ioctl) { + case VHOST_GET_FEATURES: + features = VHOST_DSP_FEATURES; + if (copy_to_user(featurep, &features, sizeof features)) + return -EFAULT; + return 0; + case VHOST_SET_FEATURES: + if (copy_from_user(&features, featurep, sizeof features)) + return -EFAULT; + return vhost_dsp_set_features(dsp, features); + case VHOST_GET_BACKEND_FEATURES: + features = 0; + if (copy_to_user(featurep, &features, sizeof(features))) + return -EFAULT; + return 0; + case VHOST_SET_BACKEND_FEATURES: + if (copy_from_user(&features, featurep, sizeof(features))) + return -EFAULT; + if (features) + return -EOPNOTSUPP; + return 0; + case VHOST_RESET_OWNER: + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_check_owner(&dsp->dev); + if (!ret) { + struct vhost_umem *umem = vhost_dev_reset_owner_prepare(); + if (!umem) { + ret = -ENOMEM; + } else { + vhost_dev_stop(&dsp->dev); + vhost_dev_reset_owner(&dsp->dev, umem); + } + } + mutex_unlock(&dsp->dev.mutex); + return ret; + case VHOST_SET_OWNER: + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_set_owner(&dsp->dev); + mutex_unlock(&dsp->dev.mutex); + return ret; + case VHOST_SET_RUNNING: + if (copy_from_user(&start, argp, sizeof(start))) + return -EFAULT; + + if (start) + return vhost_dsp_activate(dsp); + + vhost_dsp_deactivate(dsp); + return 0; + case VHOST_DSP_SET_GUEST_TPLG: + if (copy_from_user(&tplg, argp, sizeof(tplg))) + return -EFAULT; + return dsp_sof_set_tplg(dsp->snd, &tplg); + } + + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_ioctl(&dsp->dev, ioctl, argp); + if (ret == -ENOIOCTLCMD) + ret = vhost_vring_ioctl(&dsp->dev, ioctl, argp); + mutex_unlock(&dsp->dev.mutex); + + return ret; +} + +#ifdef CONFIG_COMPAT +static long vhost_dsp_compat_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + return vhost_dsp_ioctl(filp, ioctl, (unsigned long)compat_ptr(arg)); +} +#endif + +static ssize_t vhost_dsp_chr_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct file *filp = iocb->ki_filp; + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + int noblock = filp->f_flags & O_NONBLOCK; + + return vhost_chr_read_iter(dev, to, noblock); +} + +static ssize_t vhost_dsp_chr_write_iter(struct kiocb *iocb, + struct iov_iter *from) +{ + struct file *filp = iocb->ki_filp; + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + + return vhost_chr_write_iter(dev, from); +} + +static __poll_t vhost_dsp_chr_poll(struct file *filp, poll_table *wait) +{ + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + + return vhost_chr_poll(filp, dev, wait); +} + +/* IPC message from a guest */ +static void handle_ipc_cmd_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + int vq_idx = SOF_VIRTIO_IPC_CMD_VQ; + size_t total_len = 0; + + /* IPC message from the guest */ + mutex_lock(&vq->mutex); + + /* notifications must be disabled while handling the queue */ + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + struct iov_iter iov_iter; + size_t len, nbytes; + unsigned int out, in, i; + size_t iov_offset, iov_count; + /* IPC command from FE to DSP */ + int head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL), ret; + if (head < 0) + break; + + /* Nothing new? Wait for eventfd to tell us they refilled. */ + if (head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + break; + } + + if (in != out) + /* We expect in == out and usually == 1 */ + continue; + + iov_offset = out; + iov_count = out; + + for (i = 0; i < iov_count; i++) { + struct sof_ipc_reply *rhdr = (struct sof_ipc_reply *)dsp->reply_buf; + size_t to_copy; + + len = vq->iov[i].iov_len; + + if (len > sizeof(dsp->ipc_buf)) { + vq_err(vq, + "%s(): head %d out %d in %d len %zd\n", + __func__, head, out, in, len); + continue; + } + + total_len += len; + + iov_iter_init(&iov_iter, WRITE, vq->iov + i, 1, len); + + nbytes = copy_from_iter(dsp->ipc_buf, len, &iov_iter); + if (nbytes != len) { + vq_err(vq, "Expected %zu bytes for IPC, got %zu bytes\n", + len, nbytes); + continue; + } + + /* Process the IPC payload */ + ret = dsp_sof_ipc_fwd(dsp->snd, vq_idx, dsp->ipc_buf, + dsp->reply_buf, len, + vq->iov[iov_offset + i].iov_len); + if (ret < 0) { + struct sof_ipc_cmd_hdr *hdr = + (struct sof_ipc_cmd_hdr *)dsp->ipc_buf; + vq_err(vq, + "%s(): IPC 0x%x failed with error %d\n", + __func__, hdr->cmd, ret); + } + + to_copy = min_t(size_t, sizeof(dsp->reply_buf), + rhdr->hdr.size); + + iov_iter_init(&iov_iter, READ, vq->iov + iov_offset + i, + 1, to_copy); + if (copy_to_iter(dsp->reply_buf, to_copy, &iov_iter) > 0) + /* Return any response */ + vhost_add_used_and_signal(vq->dev, vq, head, to_copy); + } + } + + mutex_unlock(&vq->mutex); +} + +/* Try to send a position update buffer to the guest */ +static void vhost_dsp_fill_posn_vqbuf(struct vhost_dsp *dsp) +{ + struct vhost_virtqueue *vq = &dsp->vqs[SOF_VIRTIO_POSN_VQ].vq; + struct iov_iter iov_iter; + struct vhost_dsp_iovec *buf; + struct vhost_dsp_posn *entry; + unsigned long flags; + + spin_lock_irqsave(&dsp->posn_lock, flags); + + if (list_empty(&dsp->posn_list)) { + /* + * This is the normal path, when called from + * handle_posn_kick(): usually at that time we don't have a + * position update waiting yet + */ + spin_unlock_irqrestore(&dsp->posn_lock, flags); + return; + } + + if (list_empty(&dsp->posn_buf_list)) { + vq_err(vq, "%s(): no vq descriptors\n", __func__); + spin_unlock_irqrestore(&dsp->posn_lock, flags); + return; + } + + buf = list_first_entry(&dsp->posn_buf_list, + struct vhost_dsp_iovec, list); + list_del(&buf->list); + + entry = list_first_entry(&dsp->posn_list, + struct vhost_dsp_posn, list); + list_del(&entry->list); + + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + /* Take the lock and send the buffer */ + mutex_lock(&vq->mutex); + iov_iter_init(&iov_iter, READ, vq->iov, 1, sizeof(entry->posn)); + if (copy_to_iter(&entry->posn, sizeof(entry->posn), &iov_iter) > 0) + /* + * Actually the last parameter for vhost_add_used_and_signal() + * should be "sizeof(*posn)," but that didn't work + */ + vhost_add_used_and_signal(vq->dev, vq, buf->head, 0); + mutex_unlock(&vq->mutex); + + kfree(buf); + kfree(entry); +} + +/* Handle kick on the data VirtQ */ +static void handle_data_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + + mutex_lock(&vq->mutex); + + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + struct iov_iter iov_iter; + unsigned int out, in, i; + int head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL); + if (head < 0) + break; + + /* Nothing new? Wait for eventfd to tell us they refilled. */ + if (head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + break; + } + + if (in != out) + /* We expect in == out and usually == 1 */ + continue; + + for (i = 0; i < out; i++) { + u8 _req[HDR_SIZE_REQ]; + u8 _resp[HDR_SIZE_RESP]; + struct dsp_sof_data_resp *resp; + struct dsp_sof_data_req *req; + size_t to_copy, nbytes, len = vq->iov[i].iov_len; + int ret; + + if (len > sizeof(dsp->data_req) || len < HDR_SIZE_REQ) { + vq_err(vq, + "%s(): head %d out %d in %d len %zd\n", + __func__, head, out, in, len); + continue; + } + + iov_iter_init(&iov_iter, WRITE, vq->iov + i, 1, len); + + if (len > HDR_SIZE_REQ) { + /* playback */ + req = &dsp->data_req; + resp = (struct dsp_sof_data_resp *)_resp; + } else { + /* capture */ + req = (struct dsp_sof_data_req *)_req; + resp = &dsp->data_resp; + } + + nbytes = copy_from_iter(req, len, &iov_iter); + if (nbytes != len) { + vq_err(vq, "Expected %zu bytes for IPC, got %zu bytes\n", + len, nbytes); + continue; + } + + /* Copy data to or from the audio buffer */ + ret = dsp_sof_stream_data(dsp->snd, req, resp); + if (ret < 0) { + vq_err(vq, "Error %d copying data\n", ret); + continue; + } + + to_copy = resp->size + HDR_SIZE_RESP; + + iov_iter_init(&iov_iter, READ, vq->iov + out + i, + 1, to_copy); + if (copy_to_iter(resp, to_copy, &iov_iter) > 0) + vhost_add_used_and_signal(vq->dev, vq, head, to_copy); + } + } + + mutex_unlock(&vq->mutex); +} + +/* A new position update buffer from the guest */ +static void handle_posn_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + struct vhost_dsp_iovec *buf; + unsigned int out, in; + unsigned long flags; + bool free = true, enable = true; + + /* Queue the buffer for future position updates from the DSP */ + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return; + + mutex_lock(&vq->mutex); + + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + /* A posn descriptor should have 1 "in" and 0 "out" buffers */ + buf->head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL); + + if (buf->head < 0) { + vq_err(vq, "%s(): no vq descriptors: %d\n", + __func__, buf->head); + break; + } + + if (buf->head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + enable = false; + break; + } + + if (unlikely(out)) + vq_err(vq, + "%s(): position update has %d outgoing buffers!\n", + __func__, out); + + if (unlikely(vq->iov[out].iov_len != + sizeof(struct sof_ipc_stream_posn))) + vq_err(vq, "%s(): position update has wrong size %d!\n", + __func__, out); + + if (!in) { + /* This queue should only contain "in" buffers */ + vq_err(vq, "%s(): no input buffers!\n", __func__); + break; + } + + spin_lock_irqsave(&dsp->posn_lock, flags); + list_add_tail(&buf->list, &dsp->posn_buf_list); + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + free = false; + break; + } + + if (enable) + vhost_enable_notify(&dsp->dev, vq); + + mutex_unlock(&vq->mutex); + + if (free) + kfree(buf); + else + /* Try to send immediately if a position update is pending */ + vhost_dsp_fill_posn_vqbuf(dsp); +} + +static void vhost_dsp_posn_work(struct vhost_work *work) +{ + struct vhost_dsp *dsp = container_of(work, struct vhost_dsp, work); + + /* + * If there is an available VQ buffer, notify immediately. This is the + * normal case, since the guest pre-queues position update VQ buffers. + */ + vhost_dsp_fill_posn_vqbuf(dsp); +} + +static int vhost_dsp_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *misc = filp->private_data; + struct snd_sof_dev *sdev = dev_get_drvdata(misc->parent); + struct vhost_dsp *dsp = kvmalloc(sizeof(*dsp), + GFP_KERNEL | __GFP_RETRY_MAYFAIL); + unsigned int i; + + if (!dsp) + return -ENOMEM; + + dsp->snd = dsp_sof_client_add(sdev, dsp); + if (!dsp->snd) { + kvfree(dsp); + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(dsp->vq_p); i++) + dsp->vq_p[i] = &dsp->vqs[i].vq; + + dsp->vqs[SOF_VIRTIO_IPC_CMD_VQ].vq.handle_kick = handle_ipc_cmd_kick; + dsp->vqs[SOF_VIRTIO_POSN_VQ].vq.handle_kick = handle_posn_kick; + dsp->vqs[SOF_VIRTIO_DATA_VQ].vq.handle_kick = handle_data_kick; + /* + * TODO: do we ever want to support multiple guest machines per DSP, if + * not, we might as well perform all allocations when registering the + * misc device. + */ + INIT_LIST_HEAD(&dsp->posn_list); + INIT_LIST_HEAD(&dsp->posn_buf_list); + spin_lock_init(&dsp->posn_lock); + dsp->active = false; + vhost_work_init(&dsp->work, vhost_dsp_posn_work); + + vhost_dev_init(&dsp->dev, dsp->vq_p, SOF_VIRTIO_NUM_OF_VQS, + UIO_MAXIOV + VHOST_DSP_BATCH, + VHOST_DSP_PKT_WEIGHT, VHOST_DSP_WEIGHT); + + /* Overwrite file private data */ + filp->private_data = dsp; + + return 0; +} + +/* + * The device is closed by QEMU when the client driver is unloaded or the guest + * is shut down + */ +static int vhost_dsp_release(struct inode *inode, struct file *filp) +{ + struct vhost_dsp *dsp = filp->private_data; + + vhost_work_flush(&dsp->dev, &dsp->work); + vhost_dev_cleanup(&dsp->dev); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_POSN_VQ].vq.poll); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_IPC_CMD_VQ].vq.poll); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_DATA_VQ].vq.poll); + + dsp_sof_client_release(dsp->snd); + + kvfree(dsp); + + return 0; +} + +static const struct file_operations vhost_dsp_fops = { + .owner = THIS_MODULE, + .release = vhost_dsp_release, + .read_iter = vhost_dsp_chr_read_iter, + .write_iter = vhost_dsp_chr_write_iter, + .poll = vhost_dsp_chr_poll, + .unlocked_ioctl = vhost_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vhost_dsp_compat_ioctl, +#endif + .open = vhost_dsp_open, + .llseek = noop_llseek, +}; + +static struct miscdevice vhost_dsp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "vhost-dsp", + .fops = &vhost_dsp_fops, +}; + +/* Always called from an interrupt thread context */ +static int dsp_sof_update_posn(struct vhost_dsp *dsp, + struct sof_ipc_stream_posn *posn) +{ + struct vhost_dsp_posn *entry; + + if (!dsp->active) + return 0; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + memcpy(&entry->posn, posn, sizeof(entry->posn)); + + /* + * Save the position update msg and send it when a vq buffer becomes + * available. + */ + spin_lock_irq(&dsp->posn_lock); + list_add_tail(&entry->list, &dsp->posn_list); + spin_unlock_irq(&dsp->posn_lock); + + /* posn update for guest */ + vhost_work_queue(&dsp->dev, &dsp->work); + + return 0; +} + +static struct sof_vhost_ops vhost_dsp_ops = { + .update_posn = dsp_sof_update_posn, +}; + +static int __init dsp_sof_init(void) +{ + vhost_dsp_misc.parent = dsp_sof_dev_init(&vhost_dsp_ops); + if (!vhost_dsp_misc.parent) + return -ENODEV; + + return misc_register(&vhost_dsp_misc); +} + +static void __exit dsp_sof_exit(void) +{ + misc_deregister(&vhost_dsp_misc); +} + +module_init(dsp_sof_init); +module_exit(dsp_sof_exit); + +MODULE_VERSION("0.9"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Guennadi Liakhovetski"); +MODULE_DESCRIPTION("Host kernel accelerator for virtio sound");
Currently the SOF driver switches off the DSP every time runtime or system-wide suspend is entered. After the DSP is turned off, next time it's turned on, the firmware and topology have to be re-sent to it.
When a guest SOF instance restarts it sends its topology to the host, which then forwards it to the DSP. This is correct if the DSP was suspended during that time and lost the guest's topology. However, if the DSP stayed active during that entire time, sending duplicate components to it produces errors. To prevent this from happening this patch adds freeing of components during guest shut down.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- drivers/vhost/dsp.c | 3 + include/sound/sof/virtio.h | 4 ++ sound/soc/sof/vhost-be.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+)
diff --git a/drivers/vhost/dsp.c b/drivers/vhost/dsp.c index 205ae8c..33e849a 100644 --- a/drivers/vhost/dsp.c +++ b/drivers/vhost/dsp.c @@ -114,6 +114,9 @@ static void vhost_dsp_deactivate(struct vhost_dsp *dsp)
dsp->active = false;
+ /* If a VM reboots dsp_sof_client_release() isn't called */ + dsp_sof_topology_purge(dsp->snd); + spin_lock_irqsave(&dsp->posn_lock, flags); list_for_each_entry_safe(buf, next, &dsp->posn_buf_list, list) { list_del(&buf->list); diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h index fc98664..80e174e 100644 --- a/include/sound/sof/virtio.h +++ b/include/sound/sof/virtio.h @@ -147,6 +147,9 @@ struct dsp_sof_client { struct list_head pipe_conn; /* List of vhost instances on a DSP */ struct list_head list; + /* List of widgets to free for tear-down */ + struct list_head comp_list; + struct list_head pipe_list;
/* Component ID range index in the bitmap */ unsigned int id; @@ -177,6 +180,7 @@ int dsp_sof_stream_data(struct dsp_sof_client *client, int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, void *ipc_buf, void *reply_buf, size_t count, size_t reply_sz); +void dsp_sof_topology_purge(struct dsp_sof_client *client);
/* The below functions are always referenced, they need dummy counterparts */ int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/vhost-be.c b/sound/soc/sof/vhost-be.c index 79d6f8c..1fe1f33 100644 --- a/sound/soc/sof/vhost-be.c +++ b/sound/soc/sof/vhost-be.c @@ -43,6 +43,18 @@ struct dsp_pipeline_connect { struct list_head list; };
+struct dsp_sof_comp_list { + struct list_head list; + uint32_t comp_id; + enum sof_comp_type comp_type; +}; + +struct dsp_sof_pipe_list { + struct list_head list; + uint32_t comp_id; + uint32_t pipe_id; +}; + static const char dsp_pcm_name[] = "VHost PCM";
/* @@ -446,6 +458,75 @@ static int dsp_sof_ipc_comp(struct dsp_sof_client *client, cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; }
+void dsp_sof_topology_purge(struct dsp_sof_client *client) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_free fcomp = { + .hdr = { + .size = sizeof(fcomp), + }, + }; + struct sof_ipc_reply reply; + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + int ret; + + pm_runtime_get_sync(sdev->dev); + + /* First free all pipelines */ + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + fcomp.id = pitem->comp_id; + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_PIPE_FREE; + + dev_dbg(sdev->dev, "tplg: unload component ID: %d pipe %u\n", + fcomp.id, pitem->pipe_id); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&pitem->list); + kfree(pitem); + } + + /* Then free all individual components */ + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + fcomp.id = citem->comp_id; + switch (citem->comp_type) { + case SOF_COMP_BUFFER: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_COMP_FREE; + } + + dev_dbg(sdev->dev, "tplg: unload component ID: %d type %u\n", + fcomp.id, citem->comp_type); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&citem->list); + kfree(citem); + } + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); +} +EXPORT_SYMBOL_GPL(dsp_sof_topology_purge); + /* process PM IPC */ static int dsp_sof_ipc_pm(struct dsp_sof_client *client, struct sof_ipc_cmd_hdr *hdr, struct sof_vfe_ipc_power_resp *resp) @@ -526,6 +607,47 @@ int dsp_sof_add_conn(struct snd_sof_dev *sdev, return 0; }
+static int dsp_sof_tplg_comp_add(struct dsp_sof_client *client, + struct sof_ipc_comp *comp) +{ + struct dsp_sof_comp_list *citem = kmalloc(sizeof(*citem), GFP_KERNEL); + if (!citem) + return -ENOMEM; + + citem->comp_id = comp->id; + citem->comp_type = comp->type; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d type %x\n", + __func__, citem, comp->id, comp->type); + list_add_tail(&citem->list, &client->comp_list); + + return 0; +} + +static int dsp_sof_tplg_pipe_add(struct dsp_sof_client *client, + struct sof_ipc_pipe_new *pipe) +{ + struct dsp_sof_pipe_list *pitem = kmalloc(sizeof(*pitem), GFP_KERNEL); + if (!pitem) + return -ENOMEM; + + pitem->comp_id = pipe->comp_id; + pitem->pipe_id = pipe->pipeline_id; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d pipe %x\n", + __func__, pitem, pipe->comp_id, pipe->pipeline_id); + list_add_tail(&pitem->list, &client->pipe_list); + + return 0; +} + +static int dsp_sof_ipc_tplg_buf_new(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) +{ + struct sof_ipc_comp *comp = container_of(hdr, struct sof_ipc_comp, hdr); + return dsp_sof_tplg_comp_add(client, comp); +} + /* Handle some special cases of the "new component" IPC */ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) @@ -535,6 +657,7 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct snd_sof_pcm *spcm, *last; struct sof_ipc_comp_host *host; struct dsp_pipeline_connect *conn; + int ret;
if (comp->id < client->comp_id_begin || comp->id >= client->comp_id_end) @@ -595,6 +718,10 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, break; }
+ ret = dsp_sof_tplg_comp_add(client, comp); + if (ret < 0) + return ret; + return 0; }
@@ -606,6 +733,9 @@ static int dsp_sof_ipc_tplg_pipe_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_pipe_new, hdr); struct snd_sof_dev *sdev = client->sdev; struct dsp_pipeline_connect *conn; + int ret = dsp_sof_tplg_pipe_add(client, pipeline); + if (ret < 0) + return ret;
list_for_each_entry(conn, &sdev->connector_list, list) if (pipeline->pipeline_id == conn->guest_pipeline_id) { @@ -723,6 +853,8 @@ static int dsp_sof_ipc_tplg(struct dsp_sof_client *client, int vq_idx, case SOF_IPC_TPLG_COMP_NEW: return dsp_sof_ipc_tplg_comp_new(client, vq_idx, hdr, reply_buf); + case SOF_IPC_TPLG_BUFFER_NEW: + return dsp_sof_ipc_tplg_buf_new(client, hdr, reply_buf); case SOF_IPC_TPLG_PIPE_NEW: return dsp_sof_ipc_tplg_pipe_new(client, vq_idx, hdr); case SOF_IPC_TPLG_COMP_CONNECT: @@ -987,6 +1119,23 @@ int dsp_sof_set_tplg(struct dsp_sof_client *client, void dsp_sof_suspend(struct snd_sof_dev *sdev) { struct snd_sof_pcm *spcm, *next; + struct dsp_sof_client *client; + + /* Upon resume we'll rebuild lists */ + list_for_each_entry(client, &sdev->vbe_list, list) { + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + list_del(&pitem->list); + kfree(pitem); + } + + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + list_del(&citem->list); + kfree(citem); + } + }
list_for_each_entry_safe(spcm, next, &sdev->pcm_list, list) if (!strcmp(dsp_pcm_name, spcm->pcm.pcm_name)) { @@ -1000,6 +1149,9 @@ void dsp_sof_suspend(struct snd_sof_dev *sdev) /* A VM instance has closed the miscdevice */ void dsp_sof_client_release(struct dsp_sof_client *client) { + /* If a VM crashes we don't get ioctl(VHOST_SET_RUNNING, 0) from QEMU */ + dsp_sof_topology_purge(client); + bitmap_release_region(client->sdev->vfe_mask, client->id, 0);
list_del(&client->list); @@ -1024,6 +1176,9 @@ struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, return NULL; }
+ INIT_LIST_HEAD(&client->pipe_list); + INIT_LIST_HEAD(&client->comp_list); + client->sdev = sdev; client->id = id; client->vhost = dsp;
Dynamically allocate separate playback and capture buffers to enable simultaneous playback and capture.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/virtio-fe.c | 53 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-)
diff --git a/sound/soc/sof/virtio-fe.c b/sound/soc/sof/virtio-fe.c index aa6da81..807562d 100644 --- a/sound/soc/sof/virtio-fe.c +++ b/sound/soc/sof/virtio-fe.c @@ -79,17 +79,13 @@ struct sof_vfe { struct completion completion; spinlock_t vq_lock;
- /* A shared capture / playback virtual queue data buffer */ - union { - struct dsp_sof_data_req data_req; - struct dsp_sof_data_resp data_resp; - }; + /* Pointers for virtual queue data buffers */ + struct dsp_sof_data_req *playback_buf; + struct dsp_sof_data_resp *capture_buf;
/* Headers, used as a playback response or capture request */ - union { - u8 hdr_req[HDR_SIZE_REQ]; - u8 hdr_resp[HDR_SIZE_RESP]; - }; + u8 hdr_req[HDR_SIZE_REQ]; + u8 hdr_resp[HDR_SIZE_RESP]; };
/* Firmware ready IPC. */ @@ -422,7 +418,7 @@ static int sof_vfe_pcm_read_part(struct snd_sof_dev *sdev, void __user *buf, unsigned long chunk_size) { struct sof_vfe *vfe = sdev->pdata->vfe; - struct dsp_sof_data_resp *data = &vfe->data_resp; + struct dsp_sof_data_resp *data = vfe->capture_buf; struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; struct dsp_sof_data_req *req = (struct dsp_sof_data_req *)vfe->hdr_req; unsigned int len; @@ -480,7 +476,7 @@ static int sof_vfe_pcm_write_part(struct snd_sof_dev *sdev, void __user *buf, unsigned long chunk_size) { struct sof_vfe *vfe = sdev->pdata->vfe; - struct dsp_sof_data_req *data = &vfe->data_req; + struct dsp_sof_data_req *data = vfe->playback_buf; struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; struct dsp_sof_data_resp *resp = (struct dsp_sof_data_resp *)vfe->hdr_resp; unsigned int len; @@ -596,9 +592,43 @@ static int sof_vfe_pcm_open(struct snd_sof_dev *sdev, static int sof_vfe_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { + struct sof_vfe *vfe = sdev->pdata->vfe; + pm_runtime_mark_last_busy(sdev->dev); pm_runtime_put_autosuspend(sdev->dev);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + kfree(vfe->playback_buf); + vfe->playback_buf = NULL; + } else { + kfree(vfe->capture_buf); + vfe->capture_buf = NULL; + } + + return 0; +} + +static int sof_vfe_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct sof_ipc_stream_params *ipc_params) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !vfe->playback_buf) { + vfe->playback_buf = kmalloc(sizeof(*vfe->playback_buf), + GFP_KERNEL); + if (!vfe->playback_buf) + return -ENOMEM; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && + !vfe->capture_buf) { + vfe->capture_buf = kmalloc(sizeof(*vfe->capture_buf), + GFP_KERNEL); + if (!vfe->capture_buf) + return -ENOMEM; + } + return 0; }
@@ -629,6 +659,7 @@ struct snd_sof_dsp_ops snd_sof_vfe_ops = {
.pcm_open = sof_vfe_pcm_open, .pcm_close = sof_vfe_pcm_close, + .pcm_hw_params = sof_vfe_pcm_hw_params,
.run = sof_vfe_run, .block_read = sof_vfe_block_read,
On Fri, Apr 03, 2020 at 11:13:54AM +0200, Guennadi Liakhovetski wrote:
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Hi Mark,
On Fri, Apr 03, 2020 at 10:28:42AM +0100, Mark Brown wrote:
On Fri, Apr 03, 2020 at 11:13:54AM +0200, Guennadi Liakhovetski wrote:
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Sorry, we were thinking about the best way to reply. I think Liam will provide more procise information. The API we are using is the same as what is already used by SOF to communicate with the DSP firmware. With VirtIO we re-use the same IPC messages as those, used with the DSP, they are just transmitted over Virtual Queues. Additionally to existing IPC messages the SOF VirtIO implementation defines 3 more message types for topology loading and DSP power management. IPC message types are defined in include/sound/sof/header.h, IPC overview is available in https://thesofproject.github.io/latest/developer_guides/linux_driver/archite... but I'm not aware of a formal SOF IPC protocol documentation.
Thanks Guennadi
On Fri, Apr 03, 2020 at 01:04:05PM +0200, Guennadi Liakhovetski wrote:
On Fri, Apr 03, 2020 at 10:28:42AM +0100, Mark Brown wrote:
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Sorry, we were thinking about the best way to reply. I think Liam will provide more procise information. The API we are using is the same as what is already used by SOF to communicate with the DSP firmware. With VirtIO we re-use the same IPC messages as those, used with the DSP, they are just transmitted over Virtual Queues. Additionally to existing
Quickly scanning through the code it's explicitly talking about things like back ends and front ends... In any case talking to virtualization people they strongly recommend that any new virtio stuff should be documented up front.
On Fri, 2020-04-03 at 13:04 +0200, Guennadi Liakhovetski wrote:
Hi Mark,
On Fri, Apr 03, 2020 at 10:28:42AM +0100, Mark Brown wrote:
On Fri, Apr 03, 2020 at 11:13:54AM +0200, Guennadi Liakhovetski wrote:
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Sorry Mark, the whole series should not have been sent since we are still pending on some OASIS standards being ratified. Guennadi, please just send the patches that add the protocol independent dependencies to ASoC and SOF driver only for review atm (patches 1,2,3,5,6 & 7).
The full series is blocking on
1) the virto-snd patches being merged. This will then allow the code (when modified) to run HDA like audio on SOF DSPs.
2) virtio DMA buffer sharing being concluded for "zero copy" usage.
3) rpmsg integration. The SOF IPC will use rpmsg virtio transport between host and guests.
This series does not mandate a DSP IPC standard, since this may differ between DSP vendors, but it will use OASIS standards for virtio-snd, DMA buffers and rpmsg.
I would say that parts of the "SOF protocol" may be useful for other vendors in the DAPM/topology areas where users want to connect guest topologies to host topologies (connected internally with DAPM).
Thanks
Liam
On Fri, Apr 03, 2020 at 05:10:20PM +0100, Liam Girdwood wrote:
- rpmsg integration. The SOF IPC will use rpmsg virtio transport
between host and guests.
This series does not mandate a DSP IPC standard, since this may differ between DSP vendors, but it will use OASIS standards for virtio-snd, DMA buffers and rpmsg.
This sounds good, I think rpmsg bit was the initial part of this that I was missing (but that will then lead into requiring the others) - this is essentially VFIO for a software defined component so it's clear that there's value in standardization on the discovery and transport parts of things but I agree that the actual messages are just a vendor thing and don't need an OASIS standard.
Hi Liam,
Thanks for the clarifications.
On Fri, Apr 03, 2020 at 05:10:20PM +0100, Liam Girdwood wrote:
On Fri, 2020-04-03 at 13:04 +0200, Guennadi Liakhovetski wrote:
Hi Mark,
On Fri, Apr 03, 2020 at 10:28:42AM +0100, Mark Brown wrote:
On Fri, Apr 03, 2020 at 11:13:54AM +0200, Guennadi Liakhovetski wrote:
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Sorry Mark, the whole series should not have been sent since we are still pending on some OASIS standards being ratified. Guennadi, please just send the patches that add the protocol independent dependencies to ASoC and SOF driver only for review atm (patches 1,2,3,5,6 & 7).
The full series is blocking on
- the virto-snd patches being merged. This will then allow the code
(when modified) to run HDA like audio on SOF DSPs.
- virtio DMA buffer sharing being concluded for "zero copy" usage.
I don't think we're blocked by this. If I understand correctly it is our intention to first upstream the present copying solution and then implement zero-copy as a next step.
- rpmsg integration. The SOF IPC will use rpmsg virtio transport
between host and guests.
We started discussing this on github, unfortunately this didn't come to a conclusion. From what I've read in the kernel, RPMSG is currencly used there in scenarios, that are very different from ours. Typically you have a Linux host, that uses RPMSG to communicate with an "embedded" counterpart, where that communication includes boooting ELF firmware on that counterpart, and then using RPMSG on top of Virtual Queues to communicate with it. Our case is quite different. I'm not saying, that it is impossible to use the Linux RPMSG subsystem for use-cases like ours, but it seems to me, that this would require a significant effort on the Linux RPMSG core implementation, and we would be the first use-case for this.
The rest has to be discussed.
Thanks Guennadi
This series does not mandate a DSP IPC standard, since this may differ between DSP vendors, but it will use OASIS standards for virtio-snd, DMA buffers and rpmsg.
I would say that parts of the "SOF protocol" may be useful for other vendors in the DAPM/topology areas where users want to connect guest topologies to host topologies (connected internally with DAPM).
Thanks
Liam
On Fri, Apr 03, 2020 at 08:09:19PM +0200, Guennadi Liakhovetski wrote:
Hi Liam,
Thanks for the clarifications.
On Fri, Apr 03, 2020 at 05:10:20PM +0100, Liam Girdwood wrote:
On Fri, 2020-04-03 at 13:04 +0200, Guennadi Liakhovetski wrote:
Hi Mark,
On Fri, Apr 03, 2020 at 10:28:42AM +0100, Mark Brown wrote:
On Fri, Apr 03, 2020 at 11:13:54AM +0200, Guennadi Liakhovetski wrote:
This patch series extends the SOF driver to add support for DSP virtualisation to ASoC. It is built on top of VirtIO, contains a guest driver and a vhost in-kernel guest driver. This version supports a single playback and a single capture interface on the guest. The specific guest audio topology is supplied by the host
I've asked a couple of times for documentation of the protocol here but don't think I've seen anything yet?
Sorry Mark, the whole series should not have been sent since we are still pending on some OASIS standards being ratified. Guennadi, please just send the patches that add the protocol independent dependencies to ASoC and SOF driver only for review atm (patches 1,2,3,5,6 & 7).
The full series is blocking on
- the virto-snd patches being merged. This will then allow the code
(when modified) to run HDA like audio on SOF DSPs.
- virtio DMA buffer sharing being concluded for "zero copy" usage.
I don't think we're blocked by this. If I understand correctly it is our intention to first upstream the present copying solution and then implement zero-copy as a next step.
- rpmsg integration. The SOF IPC will use rpmsg virtio transport
between host and guests.
We started discussing this on github, unfortunately this didn't come to a conclusion. From what I've read in the kernel, RPMSG is currencly used there in scenarios, that are very different from ours. Typically you have a Linux host, that uses RPMSG to communicate with an "embedded" counterpart, where that communication includes boooting ELF firmware on that counterpart, and then using RPMSG on top of Virtual Queues to communicate with it. Our case is quite different. I'm not saying, that it is impossible to use the Linux RPMSG subsystem for use-cases like ours, but it seems to me, that this would require a significant effort on the Linux RPMSG core implementation, and we would be the first use-case for this.
After a discussion we agreed, that we shall try to port SOF VirtIO support on top of RPMSG, even though this would be the first ever such use of RPMSG. And since this likely will take a while and be a big change we suspend this review process for now.
We'll be back.
Thanks Guennadi
The rest has to be discussed.
Thanks Guennadi
This series does not mandate a DSP IPC standard, since this may differ between DSP vendors, but it will use OASIS standards for virtio-snd, DMA buffers and rpmsg.
I would say that parts of the "SOF protocol" may be useful for other vendors in the DAPM/topology areas where users want to connect guest topologies to host topologies (connected internally with DAPM).
Thanks
Liam
On Tue, Apr 07, 2020 at 01:24:11PM +0200, Guennadi Liakhovetski wrote:
After a discussion we agreed, that we shall try to port SOF VirtIO support on top of RPMSG, even though this would be the first ever such use of RPMSG. And since this likely will take a while and be a big change we suspend this review process for now.
OK... the generic patches (IIRC it was about the list that Liam identified) looked OK so if you want to send them by themselves then I can apply them and it's less diff for you to carry.
Hi Mark,
On Tue, Apr 07, 2020 at 12:30:26PM +0100, Mark Brown wrote:
On Tue, Apr 07, 2020 at 01:24:11PM +0200, Guennadi Liakhovetski wrote:
After a discussion we agreed, that we shall try to port SOF VirtIO support on top of RPMSG, even though this would be the first ever such use of RPMSG. And since this likely will take a while and be a big change we suspend this review process for now.
OK... the generic patches (IIRC it was about the list that Liam identified) looked OK so if you want to send them by themselves then I can apply them and it's less diff for you to carry.
Ok, I'll route them via Pierre then, because they include a new SOF IPC, which probably requires an ABI version update, even though it will never be sent to the DSP.
Thanks Guennadi
participants (3)
-
Guennadi Liakhovetski
-
Liam Girdwood
-
Mark Brown