[Sound-open-firmware] [RFC 00/12] Audio DSP VirtIO and vhost drivers
This patch set is a follow up to "Add a vhost RPMsg API" [1], it is marked as an RFC because firstly it depends on the RPMsg API series and secondly it is currently being reviewed on ALSA and SOF mailing lists, but any early comments from virtualisation developers would be highly appreciated too!
Thanks Guennadi
[1] https://mailman.alsa-project.org/pipermail/sound-open-firmware/2020-May/0038...
Guennadi Liakhovetski (12): ASoC: add function parameters to enable forced path pruning ASoC: SOF: extract firmware-related operation into a function ASoC: SOF: support IPC with immediate response ASoC: SOF: add a power status IPC ASoC: SOF: add two helper lookup functions ASoC: SOF: add an RPMsg VirtIO DSP driver ASoC: SOF: use a macro instead of a hard-coded value ASoC: SOF: add a vhost driver: sound part ASoC: SOF: VirtIO: free guest pipelines upon termination vhost: add an SOF Audio DSP driver rpmsg: increase buffer size and reduce buffer number rpmsg: add a device ID to also bind to the ADSP device
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + drivers/vhost/Kconfig | 10 + drivers/vhost/Makefile | 3 + drivers/vhost/adsp.c | 618 +++++++++++++++++++ include/linux/virtio_rpmsg.h | 4 +- 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/rpmsg.h | 196 ++++++ include/sound/sof/topology.h | 9 +- include/uapi/linux/vhost.h | 5 + 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 | 7 + sound/soc/sof/Makefile | 2 + sound/soc/sof/core.c | 114 ++-- sound/soc/sof/ipc.c | 34 +- sound/soc/sof/loader.c | 4 + sound/soc/sof/ops.h | 10 +- sound/soc/sof/pcm.c | 13 +- sound/soc/sof/pm.c | 6 +- sound/soc/sof/rpmsg-vfe.c | 881 ++++++++++++++++++++++++++ sound/soc/sof/sof-audio.c | 33 + sound/soc/sof/sof-audio.h | 21 + sound/soc/sof/sof-priv.h | 48 ++ sound/soc/sof/topology.c | 71 ++- sound/soc/sof/vhost-vbe.c | 1258 ++++++++++++++++++++++++++++++++++++++ 31 files changed, 3391 insertions(+), 111 deletions(-) create mode 100644 drivers/vhost/adsp.c create mode 100644 include/sound/sof/rpmsg.h create mode 100644 sound/soc/sof/rpmsg-vfe.c create mode 100644 sound/soc/sof/vhost-vbe.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 62ece72..c9539b8 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -155,7 +155,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 a4de3e4..e27d93d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2311,7 +2311,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); @@ -2376,7 +2376,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); @@ -3416,7 +3416,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; } @@ -3521,7 +3521,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 b7899da..eb19a8e 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1480,14 +1480,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", @@ -1562,12 +1562,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) @@ -2563,11 +2564,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", @@ -2599,7 +2602,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; } @@ -2692,7 +2695,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; @@ -2738,13 +2742,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); @@ -2762,25 +2766,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); @@ -2836,7 +2861,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 339c493..17f264f 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; @@ -181,42 +228,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; @@ -250,7 +264,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);
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 | 11 +++++++---- sound/soc/sof/ops.h | 10 +++++++++- 2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index f7a0353..b3e1587 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -262,6 +262,12 @@ static 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. In + * such a case msg->ipc_complete will stay true and tx_wait_done() will + * return immediately. + */ ret = snd_sof_dsp_send_msg(sdev, msg); /* Next reply that we receive will be related to this message */ if (!ret) @@ -279,10 +285,7 @@ static 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); }
/* send IPC message from host to DSP */ diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index b21632f..bf91467 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 2d35997..5ee296c 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 17f264f..61f045c 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> @@ -312,6 +313,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->dsp_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 b3e1587..e9b0347 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 4a5b57e..df95bcb 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" @@ -620,6 +621,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->dsp_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 3ed39b8..29ab6ad 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 dsp_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 | 5 +++++ sound/soc/sof/topology.c | 1 + 3 files changed, 30 insertions(+)
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 1c7698f..92fa6a8 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 9629994..8054e48 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -106,6 +106,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; @@ -190,6 +191,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);
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 6a9703e..5a65dcf 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1406,6 +1406,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)); }
Add a VirtIO driver, designed to work with the SOF vhost driver, using the RPMsg protocol layer. 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 RPMsg standard over Virtual Queues. This version uses 3 RPMsg endpoints: for control, for data and for position updates. The control endpoint 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 endpoints and only preserving the control channel.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/sound/sof.h | 4 + include/sound/sof/header.h | 2 + include/sound/sof/rpmsg.h | 120 ++++++ include/sound/sof/topology.h | 9 +- 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/rpmsg-vfe.c | 881 ++++++++++++++++++++++++++++++++++++++++ sound/soc/sof/sof-priv.h | 28 ++ sound/soc/sof/topology.c | 18 +- 13 files changed, 1099 insertions(+), 29 deletions(-) create mode 100644 include/sound/sof/rpmsg.h create mode 100644 sound/soc/sof/rpmsg-vfe.c
diff --git a/include/sound/sof.h b/include/sound/sof.h index f3e716c..761ef8c 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. */ @@ -30,6 +32,8 @@ struct snd_sof_pdata { /* indicate how many first bytes shouldn't be loaded into DSP memory. */ size_t fw_offset;
+ 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 5ee296c..9844fbe 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/rpmsg.h b/include/sound/sof/rpmsg.h new file mode 100644 index 00000000..73dc34c --- /dev/null +++ b/include/sound/sof/rpmsg.h @@ -0,0 +1,120 @@ +/* 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_RPMSG_H +#define _SOF_RPMSG_H + +#include <linux/virtio_rpmsg.h> + +#include <sound/sof/header.h> + +/* host endpoint addresses */ +enum { + SOF_RPMSG_ADDR_IPC, /* IPC commands and replies */ + SOF_RPMSG_ADDR_POSN, /* Stream position updates */ + SOF_RPMSG_ADDR_DATA, /* Audio data */ + SOF_RPMSG_ADDR_COUNT, /* Number of RPMsg endpoints */ +}; + +/** + * struct sof_rpmsg_ipc_tplg_req - request for topology data + * @hdr: the standard SOF IPC header + * @offset: the current offset when transferring a split file + */ +struct sof_rpmsg_ipc_tplg_req { + struct sof_ipc_cmd_hdr hdr; + size_t offset; +} __packed; + +/** + * struct sof_rpmsg_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_rpmsg_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_rpmsg_ipc_power_req - power status change IPC + * @hdr: the standard SOF IPC header + * @power: 1: on, 0: off + */ +struct sof_rpmsg_ipc_power_req { + struct sof_ipc_cmd_hdr hdr; + uint32_t power; +} __packed; + +enum sof_rpmsg_ipc_reset_status { + SOF_RPMSG_IPC_RESET_NONE, /* Host hasn't been reset */ + SOF_RPMSG_IPC_RESET_DONE, /* Host has been reset */ +}; + +/** + * struct sof_rpmsg_ipc_power_resp - response to a power status request + * @reply: the standard SOF IPC response header + * @reset_status: enum sof_rpmsg_ipc_reset_status + */ +struct sof_rpmsg_ipc_power_resp { + struct sof_ipc_reply reply; + uint32_t reset_status; +} __packed; + +#define SOF_RPMSG_MAX_DATA_SIZE MAX_RPMSG_BUF_SIZE + +/** + * struct sof_rpmsg_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 sof_rpmsg_data_req { + u32 size; + u32 offset; + u32 comp_id; + /* Only included for playback */ + u8 data[]; +} __packed; + +/** + * struct sof_rpmsg_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 sof_rpmsg_data_resp { + u32 size; + u32 error; + /* Only included for capture */ + u8 data[]; +} __packed; + +struct sof_rpmsg_ipc_req { + u32 reply_size; + u8 ipc_msg[SOF_IPC_MSG_MAX_SIZE]; +} __packed; + +#endif diff --git a/include/sound/sof/topology.h b/include/sound/sof/topology.h index f56e80d..74b7472 100644 --- a/include/sound/sof/topology.h +++ b/include/sound/sof/topology.h @@ -33,12 +33,13 @@ enum sof_comp_type { SOF_COMP_EQ_IIR, SOF_COMP_EQ_FIR, SOF_COMP_KEYWORD_DETECT, - SOF_COMP_KPB, /* A key phrase buffer component */ - SOF_COMP_SELECTOR, /**< channel selector component */ + SOF_COMP_KPB, /**< key phrase buffer component */ + SOF_COMP_SELECTOR, /**< channel selector component */ SOF_COMP_DEMUX, - SOF_COMP_ASRC, /**< Asynchronous sample rate converter */ + SOF_COMP_ASRC, /**< asynchronous sample rate converter */ SOF_COMP_DCBLOCK, - SOF_COMP_SMART_AMP, /**< smart amplifier component */ + SOF_COMP_SMART_AMP, /**< smart amplifier component */ + 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/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index ecc27a1..7d1a738 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -46,6 +46,7 @@ #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 */ #define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
#endif /* _LINUX_VIRTIO_IDS_H */ diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 4dda4b6..1302cea 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_RPMSG_FE + bool "SOF VirtIO guest role" + depends on SND_SOC_SOF_NOCODEC_SUPPORT + depends on VIRTIO && RPMSG_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 05718df..34142ba 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_RPMSG_FE +snd-sof-objs += rpmsg-vfe.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 61f045c..2515b57 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; } @@ -229,9 +230,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; @@ -265,7 +269,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: @@ -369,10 +374,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); }
/* @@ -391,9 +398,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 e9b0347..3e788d9 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 22fe9d5..bb8d597 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -678,6 +678,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: @@ -793,6 +800,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->compress_ops = &sof_compressed_ops; diff --git a/sound/soc/sof/rpmsg-vfe.c b/sound/soc/sof/rpmsg-vfe.c new file mode 100644 index 00000000..c1cca16 --- /dev/null +++ b/sound/soc/sof/rpmsg-vfe.c @@ -0,0 +1,881 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 Intel, Inc. + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/rpmsg.h> +#include <linux/scatterlist.h> +#include <linux/virtio_rpmsg.h> +#include <sound/sof.h> +#include <sound/sof/rpmsg.h> + +#include "ops.h" +#include "sof-audio.h" +#include "sof-priv.h" + +/* 600ms for VirtQ IPC */ +#define SOF_VFE_DATA_TIMEOUT_MS 600 + +/* endpoint indices */ +#define SOF_VFE_POSN (SOF_RPMSG_ADDR_POSN - 1) +#define SOF_VFE_DATA (SOF_RPMSG_ADDR_DATA - 1) + +struct sov_vfe_posn_stream { + struct work_struct work; + struct snd_sof_pcm_stream *stream; +}; + +struct sof_vfe { + struct snd_sof_dev *sdev; + + /* current pending cmd message */ + struct snd_sof_ipc_msg *msg; + + struct rpmsg_device *rpdev; + /* One endpoint is embedded in rpdev */ + struct rpmsg_endpoint *ept[SOF_RPMSG_ADDR_COUNT - 1]; + + /* + * 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_rpmsg_ipc_tplg_resp tplg; + + struct completion completion; + + /* Pointers for virtual queue data buffers */ + struct sof_rpmsg_data_req *playback_buf; + struct sof_rpmsg_data_resp *capture_buf; + + /* Headers, used as a playback response or capture request */ + struct sof_rpmsg_data_req hdr_req; + struct sof_rpmsg_data_resp hdr_resp; + struct sof_rpmsg_ipc_req ipc_buf; + + void __user *capture; + size_t capture_size; + + struct workqueue_struct *posn_wq; + struct sov_vfe_posn_stream posn_stream[2]; +}; + +/* 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_ipc_reply *reply = msg->reply_data; + struct sof_vfe *vfe = sdev->pdata->vfe; + size_t msg_size = msg->msg_size; + void *msg_data = msg->msg_data; + int ret; + + if (vfe->block_ipc) { + 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; + } + + vfe->ipc_buf.reply_size = msg->reply_size; + memcpy(vfe->ipc_buf.ipc_msg, msg_data, msg_size); + + ret = rpmsg_sendto(vfe->rpdev->ept, &vfe->ipc_buf, + msg_size + offsetof(struct sof_rpmsg_ipc_req, ipc_msg), + SOF_RPMSG_ADDR_IPC); + if (ret < 0) { + dev_err(sdev->dev, "%s(): error: sending IPC: %d\n", + __func__, ret); + return ret; + } + + vfe->msg = msg; + + return 0; +} + +static int sof_vfe_register(struct snd_sof_dev *sdev) +{ + sdev->pdata->vfe->sdev = sdev; + sdev->next_comp_id = SOF_RPMSG_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; +} + +/* Send the position queue address. */ +static int sof_vfe_position_addr(struct snd_sof_dev *sdev) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + int ret = rpmsg_sendto(vfe->ept[SOF_VFE_POSN], + &vfe->ept[SOF_VFE_POSN]->addr, + sizeof(vfe->ept[SOF_VFE_POSN]->addr), + SOF_RPMSG_ADDR_POSN); + if (ret < 0) + dev_err(sdev->dev, "%s(): failed %d to send address\n", + __func__, ret); + + return ret; +} + +static int sof_vfe_request_topology(struct snd_sof_dev *sdev, + struct firmware *fw) +{ + struct sof_rpmsg_ipc_tplg_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_VFE_GET, + }, + }; + struct sof_vfe *vfe = sdev->pdata->vfe; + struct sof_rpmsg_ipc_tplg_resp *partdata = + (struct sof_rpmsg_ipc_tplg_resp *)vfe->ipc_buf.ipc_msg; + size_t data_size; + struct device *dev = sdev->dev; + int ret; + + if (!partdata) + return -ENOMEM; + + 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_no_pm(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; + } + + /* + * Size is consistent and decreasing, we're guaranteed to exit + * this loop eventually + */ + data_size = partdata->reply.hdr.size - sizeof(partdata->reply); + if (rq.offset + data_size > sizeof(vfe->tplg.data)) { + ret = -ENOBUFS; + goto free; + } + + memcpy(vfe->tplg.data + rq.offset, partdata->data, data_size/*to_copy*/); + rq.offset += data_size; + } while (partdata->reply.hdr.size == SOF_IPC_MSG_MAX_SIZE); + + fw->size = rq.offset; + fw->data = vfe->tplg.data; + + /* 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_no_pm(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; + + ret = sof_vfe_position_addr(sdev); + +free: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + 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); +} + +/* playback and capture are serialised by ipc->tx_mutex */ +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 sof_rpmsg_data_resp *data = vfe->capture_buf; + long remain; + int ret; + + /* put response size in request */ + vfe->hdr_req.size = chunk_size; + vfe->hdr_req.comp_id = spcm->stream[substream->stream].comp_id; + vfe->hdr_req.offset = get_dma_offset(substream->runtime, channel, posn); + + vfe->capture = buf; + vfe->capture_size = chunk_size; + + ret = rpmsg_sendto(vfe->ept[SOF_VFE_DATA], &vfe->hdr_req, + sizeof(vfe->hdr_req), SOF_RPMSG_ADDR_DATA); + + if (ret < 0) { + dev_err(sdev->dev, "%s(): error: sending capture command %d\n", + __func__, ret); + return ret; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) { + dev_err(sdev->dev, "%s(): error: data read timeout\n", __func__); + return -ETIMEDOUT; + } + if (ret < 0) + return ret; + + remain = copy_to_user(vfe->capture, data->data, data->size); + if (remain) { + dev_err(sdev->dev, "%s(): copy_to_user() failed %ld\n", + __func__, remain); + return -EFAULT; + } + + if (data->error < 0) + return data->error; + + return 0; +} + +/* playback and capture are serialised by ipc->tx_mutex */ +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 sof_rpmsg_data_req *data = vfe->playback_buf; + 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; + + vfe->capture = NULL; + + ret = rpmsg_sendto(vfe->ept[SOF_VFE_DATA], data, + chunk_size + sizeof(*data), SOF_RPMSG_ADDR_DATA); + if (ret < 0) { + dev_err(sdev->dev, "%s(): error: sending playback data: %d\n", + __func__, ret); + return ret; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + return ret < 0 ? ret : vfe->hdr_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_RPMSG_MAX_DATA_SIZE - 1) / + SOF_RPMSG_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; + } + + /* TODO: is locking really needed here? */ + mutex_lock(&sdev->ipc->tx_mutex); + + for (i = 0; i < n; i++) { + size_t n_bytes = i == n - 1 ? bytes % SOF_RPMSG_MAX_DATA_SIZE : + SOF_RPMSG_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", + /* + * non-NULL "stream" parameter interferes in + * snd_soc_dapm_new_dai_widgets() + */ + .playback = SOF_DAI_STREAM(NULL, 1, 8, + SNDRV_PCM_RATE_8000_192000, SOF_VFE_FORMATS), + .capture = SOF_DAI_STREAM(NULL, 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) +{ + size_t overhead; + 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); + + overhead = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ALIGN(sizeof(struct sof_rpmsg_data_req) + + sizeof(struct rpmsg_hdr), 16) : + ALIGN(sizeof(struct sof_rpmsg_data_resp) + + sizeof(struct rpmsg_hdr), 16); + + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, + SOF_RPMSG_MAX_DATA_SIZE - overhead); + + return ret; +} + +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) + + SOF_RPMSG_MAX_DATA_SIZE, 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) + + SOF_RPMSG_MAX_DATA_SIZE, GFP_KERNEL); + if (!vfe->capture_buf) + return -ENOMEM; + } + + return 0; +} + +/* IPC message sending completed. This means vBE has received the cmd */ +static int sof_vfe_ept_ipc_cb(struct rpmsg_device *rpdev, void *buf, int len, + void *priv, u32 addr) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(&rpdev->dev); + struct sof_vfe *vfe = sdev->pdata->vfe; + struct snd_sof_ipc_msg *msg = vfe->msg; + struct sof_ipc_reply *reply = msg->reply_data; + unsigned long flags; + + if (len > msg->reply_size) + return -ENOBUFS; + + memcpy(reply, buf, len); + + msg->reply_error = reply->error; + + dev_dbg(sdev->dev, "%s(): received %u bytes 0x%x error %d\n", __func__, + reply->hdr.size, reply->hdr.cmd, 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); + + return 0; +} + +static int sof_vfe_ept_data_cb(struct rpmsg_device *rpdev, void *buf, int len, + void *priv, u32 addr) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(&rpdev->dev); + struct sof_vfe *vfe = sdev->pdata->vfe; + struct sof_rpmsg_data_resp *data = buf; + + /* playback and capture are serialised by the IPC mutex */ + if (vfe->capture) { + if (addr != SOF_RPMSG_ADDR_DATA || len < sizeof(*data) || + data->size != vfe->capture_size) { + dev_err(sdev->dev, "%s(): got %u instead of %zu bytes\n", + __func__, data->size, vfe->capture_size); + return -EINVAL; + } + + memcpy(vfe->capture_buf, data, + data->size + sizeof(struct sof_rpmsg_data_resp)); + } else { + if (addr != SOF_RPMSG_ADDR_DATA || len < sizeof(*data) || + data->size) + return -EINVAL; + + memcpy(&vfe->hdr_resp, buf, sizeof(*data)); + } + + complete(&vfe->completion); + + return 0; +} + +/* The high latency version, using VirtQueues */ +static int sof_vfe_ept_posn_cb(struct rpmsg_device *rpdev, void *buf, int len, + void *priv, u32 addr) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(&rpdev->dev); + struct sof_ipc_stream_posn *posn = buf; + struct snd_sof_pcm *spcm; + int direction; + + if (addr != SOF_RPMSG_ADDR_POSN) + return -EINVAL; + + if (!len) + return 0; + + 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. + */ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct sov_vfe_posn_stream *pstream = &vfe->posn_stream[direction]; + + pstream->stream = &spcm->stream[direction]; + memcpy(&pstream->stream->posn, posn, sizeof(*posn)); + queue_work(vfe->posn_wq, &pstream->work); + } + + return 0; +} + +static int sof_vfe_runtime_suspend(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct sof_rpmsg_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 0, + }; + struct sof_rpmsg_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_rpmsg_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 1, + }; + struct sof_rpmsg_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_RPMSG_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; +} + +/* virtio fe ops */ +static 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, + .pcm_hw_params = sof_vfe_pcm_hw_params, + + .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_vfe_posn_update(struct work_struct *work) +{ + struct sov_vfe_posn_stream *pstream = container_of(work, + struct sov_vfe_posn_stream, work); + + snd_pcm_period_elapsed(pstream->stream->substream); +} + +static int sof_vfe_probe(struct rpmsg_device *rpdev) +{ + struct device *dev = &rpdev->dev; + struct rpmsg_channel_info chinfo; + struct snd_soc_acpi_mach *mach; + struct snd_sof_pdata *sof_pdata; + struct snd_sof_dev *sdev; + struct sof_vfe *vfe; + struct { + struct snd_soc_acpi_mach mach; + struct snd_sof_pdata pdata; + struct sof_vfe vfe; + } *drvdata; + int ret, dir; + + /* + * 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); + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + sof_pdata = &drvdata->pdata; + mach = &drvdata->mach; + vfe = &drvdata->vfe; + vfe->rpdev = rpdev; + init_completion(&vfe->completion); + + for_each_pcm_streams(dir) + INIT_WORK(&vfe->posn_stream[dir].work, sof_vfe_posn_update); + + vfe->posn_wq = alloc_workqueue("dsp-vfe-%d", 0, current->pid); + if (!vfe->posn_wq) + 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(&rpdev->dev); + sof_pdata->machine = mach; + sof_pdata->desc = &virt_desc; + sof_pdata->dev = dev; + sof_pdata->vfe = vfe; + 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); + + /* + * The RPMSG device name comes with the namespace announcement from the + * remote. That will also be used as a name of the first automatically- + * created channel + */ + + strlcpy(chinfo.name, "ADSP audio data", RPMSG_NAME_SIZE); + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = SOF_RPMSG_ADDR_DATA; + + get_device(dev); + + vfe->ept[SOF_VFE_DATA] = rpmsg_create_ept(rpdev, sof_vfe_ept_data_cb, + vfe, chinfo); + if (!vfe->ept[SOF_VFE_DATA]) { + dev_err(dev, "failed to create %s\n", chinfo.name); + put_device(dev); + return -EINVAL; + } + + strlcpy(chinfo.name, "ADSP position update", RPMSG_NAME_SIZE); + /* RPMSG_ADDR_ANY: automatically allocated from RPMSG_RESERVED_ADDRESSES... */ + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = SOF_RPMSG_ADDR_POSN; + + vfe->ept[SOF_VFE_POSN] = rpmsg_create_ept(rpdev, sof_vfe_ept_posn_cb, + vfe, chinfo); + if (!vfe->ept[SOF_VFE_POSN]) { + dev_err(dev, "failed to create %s\n", chinfo.name); + + ret = -EINVAL; + goto e_posn; + } + + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret < 0) { + dev_err(dev, "Cannot register VFE sof-audio device. Error %d\n", + ret); + + goto e_sof; + } + + sdev = dev_get_drvdata(dev); + vfe->sdev = sdev; + + /* + * Currently we only support one VM. comp_id from 0 to + * SOF_RPMSG_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_RPMSG_MAX_UOS_COMPS; + + dev_dbg(dev, "created VFE machine %s\n", + dev_name(&sof_pdata->pdev_mach->dev)); + + return 0; + +e_sof: + rpmsg_destroy_ept(vfe->ept[SOF_VFE_POSN]); +e_posn: + rpmsg_destroy_ept(vfe->ept[SOF_VFE_DATA]); + put_device(dev); + + return ret; +} + +static void sof_vfe_remove(struct rpmsg_device *rpdev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(&rpdev->dev); + struct sof_vfe *vfe = sdev->pdata->vfe; + + /* free rpmsg resurces and unregister device */ + sof_vfe_runtime_suspend(&rpdev->dev); + + pm_runtime_disable(&rpdev->dev); + + if (vfe->ept[SOF_VFE_DATA]) { + rpmsg_destroy_ept(vfe->ept[SOF_VFE_DATA]); + vfe->ept[SOF_VFE_DATA] = NULL; + } + + if (vfe->ept[SOF_VFE_POSN]) { + rpmsg_destroy_ept(vfe->ept[SOF_VFE_POSN]); + vfe->ept[SOF_VFE_POSN] = NULL; + } + + /* unregister the SOF device */ + snd_sof_device_remove(&rpdev->dev); + + put_device(&rpdev->dev); +} + +static const struct rpmsg_device_id sof_vfe_match[] = { + { "sof_rpmsg" }, + {} +}; + +static const struct dev_pm_ops sof_vfe_pm = { + SET_RUNTIME_PM_OPS(sof_vfe_runtime_suspend, sof_vfe_runtime_resume, + NULL) +}; + +static struct rpmsg_driver sof_vfe_driver = { + .probe = sof_vfe_probe, + .remove = sof_vfe_remove, + .callback = sof_vfe_ept_ipc_cb, + .id_table = sof_vfe_match, + .drv = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .pm = &sof_vfe_pm, + }, +}; + +module_rpmsg_driver(sof_vfe_driver); + +MODULE_AUTHOR("Intel, Inc."); +MODULE_DESCRIPTION("SOF RPMSG driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 29ab6ad..2da2469 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_RPMSG_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. */ @@ -524,6 +543,15 @@ 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_RPMSG_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. */ diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 5a65dcf..bb9fcb6 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1375,7 +1375,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);
@@ -3604,12 +3605,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); @@ -3625,7 +3635,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);
target_stats in sof_suspend should contain one of SOF_DSP_PM_* power state macros, not a hard-coded value 0.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- sound/soc/sof/pm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 5e804a7..c7aa2cf 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -174,7 +174,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) static int sof_suspend(struct device *dev, bool runtime_suspend) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); - u32 target_state = 0; + u32 target_state = SOF_DSP_PM_D0; int ret;
/* do nothing if dsp suspend callback is not set */
The SOF VirtIO driver uses a vhost RPMsg 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/rpmsg.h | 72 +++ include/uapi/linux/vhost.h | 5 + include/uapi/linux/vhost_types.h | 7 + sound/soc/soc-pcm.c | 31 +- sound/soc/sof/Makefile | 6 +- 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 | 16 + sound/soc/sof/sof-priv.h | 16 + sound/soc/sof/topology.c | 54 +- sound/soc/sof/vhost-vbe.c | 1102 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 1324 insertions(+), 16 deletions(-) create mode 100644 sound/soc/sof/vhost-vbe.c
diff --git a/include/sound/soc-topology.h b/include/sound/soc-topology.h index 5223896..ea0c2a64 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/rpmsg.h b/include/sound/sof/rpmsg.h index 73dc34c..ce522c6 100644 --- a/include/sound/sof/rpmsg.h +++ b/include/sound/sof/rpmsg.h @@ -11,6 +11,7 @@ #ifndef _SOF_RPMSG_H #define _SOF_RPMSG_H
+#include <linux/list.h> #include <linux/virtio_rpmsg.h>
#include <sound/sof/header.h> @@ -117,4 +118,75 @@ struct sof_rpmsg_ipc_req { u8 ipc_msg[SOF_IPC_MSG_MAX_SIZE]; } __packed;
+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 sof_vhost_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 sof_vhost_client_release(struct sof_vhost_client *client); +struct sof_vhost_client *sof_vhost_client_add(struct snd_sof_dev *sdev, + struct vhost_dsp *dsp); +struct device *sof_vhost_dev_init(const struct sof_vhost_ops *ops); +struct vhost_adsp_topology; +int sof_vhost_set_tplg(struct sof_vhost_client *client, + const struct vhost_adsp_topology *tplg); +/* Copy audio data between DMA and VirtQueue */ +void *sof_vhost_stream_data(struct sof_vhost_client *client, + const struct sof_rpmsg_data_req *req, + struct sof_rpmsg_data_resp *resp); +/* Forward an IPC message from a guest to the DSP */ +int sof_vhost_ipc_fwd(struct sof_vhost_client *client, + void *ipc_buf, void *reply_buf, + size_t count, size_t reply_sz); + +/* The below functions are always referenced, they need dummy counterparts */ +int sof_vhost_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn); +void sof_vhost_suspend(struct snd_sof_dev *sdev); +void sof_vhost_dev_set(struct snd_sof_dev *sdev); +#else +static inline int sof_vhost_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + return 0; +} + +static inline void sof_vhost_suspend(struct snd_sof_dev *sdev) +{ +} + +static inline void sof_vhost_dev_set(struct snd_sof_dev *sdev) +{ +} +#endif + #endif diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index b54af9d..aa258e2 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -142,4 +142,9 @@ /* Get the max ring size. */ #define VHOST_VDPA_GET_VRING_NUM _IOR(VHOST_VIRTIO, 0x76, __u16)
+/* VHOST_ADSP specific defines */ + +#define VHOST_ADSP_SET_GUEST_TPLG _IOW(VHOST_VIRTIO, 0x80, \ + struct vhost_adsp_topology) + #endif diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h index 669457c..6364bc8 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>
@@ -127,6 +128,12 @@ struct vhost_vdpa_config { __u8 buf[0]; };
+/* VHOST_ADSP */ + +struct vhost_adsp_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 eb19a8e..57165769 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 @@ -1418,10 +1419,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) @@ -2977,14 +2983,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; @@ -3004,6 +3002,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/Makefile b/sound/soc/sof/Makefile index 34142ba..872457c 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -3,6 +3,8 @@ snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ control.o trace.o utils.o sof-audio.o snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o +snd-sof-$(CONFIG_SND_SOC_SOF_RPMSG_FE) += rpmsg-vfe.o +snd-sof-$(CONFIG_VHOST_SOF) += vhost-vbe.o
snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o @@ -10,10 +12,6 @@ snd-sof-of-objs := sof-of-dev.o
snd-sof-nocodec-objs := nocodec.o
-ifdef CONFIG_SND_SOC_SOF_RPMSG_FE -snd-sof-objs += rpmsg-vfe.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 2515b57..c74c6ec 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/rpmsg.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 */ + sof_vhost_dev_set(sdev); + /* init the IPC */ sdev->ipc = snd_sof_ipc_init(sdev); if (!sdev->ipc) { @@ -334,6 +339,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 3e788d9..d3e54cf 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/rpmsg.h> + #include "sof-priv.h" #include "sof-audio.h" #include "ops.h" @@ -452,6 +454,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 */ + sof_vhost_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 bb8d597..1cd0082 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);
@@ -758,6 +759,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 c7aa2cf..3584787 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/rpmsg.h> + #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" @@ -253,6 +255,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* reset FW state */ sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
+ sof_vhost_suspend(sdev); + return ret; }
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 92fa6a8..2a8b8dc 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 8054e48..5e95a6c 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 */ @@ -217,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 sof_vhost_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 sof_vhost_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 2da2469..c11ef1a 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_RPMSG_MAX_UOS_COMPS 1000 +#define SOF_RPMSG_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_RPMSG_IPC_MSG 0 +#define SOF_RPMSG_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 dsp_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 bb9fcb6..ed05381 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1454,6 +1454,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) { @@ -1499,6 +1506,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; @@ -3128,6 +3145,15 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, link->trigger[SNDRV_PCM_STREAM_CAPTURE] = 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; } @@ -3349,6 +3375,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 = sof_vhost_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 = sof_vhost_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 */ @@ -3615,7 +3667,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-vbe.c b/sound/soc/sof/vhost-vbe.c new file mode 100644 index 00000000..8056e25 --- /dev/null +++ b/sound/soc/sof/vhost-vbe.c @@ -0,0 +1,1102 @@ +/* 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/rpmsg.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 *sof_vhost_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 *sof_vhost_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 sof_vhost_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 sof_vhost_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 = sof_vhost_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 */ + sof_vhost_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 sof_vhost_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 = sof_vhost_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 = sof_vhost_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 sof_vhost_stream_close(struct snd_sof_dev *sdev, int direction) +{ + struct snd_pcm_substream *substream = sof_vhost_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 sof_vhost_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 = sof_vhost_find_spcm_comp(sdev, stream->comp_id, &direction); + if (!spcm) + return 0; + + sof_vhost_stream_close(sdev, direction); + + return 0; +} + +/* Copy audio data from DMA buffers for capture */ +static void *sof_vhost_stream_capture(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + const struct sof_rpmsg_data_req *req, + struct sof_rpmsg_data_resp *resp) +{ + size_t data_size = req->size; + + stream->guest_offset = req->offset; + + if (req->offset + data_size > runtime->dma_bytes) { + resp->size = 0; + resp->error = -ENOBUFS; + return ERR_PTR(resp->error); + } + + stream->guest_offset += data_size; + + resp->size = data_size; + resp->error = 0; + + return runtime->dma_area + req->offset; +} + +/* Copy audio data to DMA buffers for playback */ +static void *sof_vhost_stream_playback(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + const struct sof_rpmsg_data_req *req, + struct sof_rpmsg_data_resp *resp) +{ + size_t data_size = req->size; + + stream->guest_offset = req->offset; + + resp->size = 0; + + if (req->offset + data_size > runtime->dma_bytes) { + resp->error = -ENOBUFS; + return ERR_PTR(resp->error); + } + + stream->guest_offset += data_size; + + resp->error = 0; + + return runtime->dma_area + req->offset; +} + +/* Send or receive audio data */ +void *sof_vhost_stream_data(struct sof_vhost_client *client, + const struct sof_rpmsg_data_req *req, + struct sof_rpmsg_data_resp *resp) +{ + int direction; + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pcm *spcm = sof_vhost_find_spcm_comp(sdev, + req->comp_id, &direction); + struct snd_pcm_substream *substream = sof_vhost_get_substream(sdev, + NULL, direction); + + if (!spcm || !substream) { + resp->error = -ENODEV; + resp->size = 0; + return ERR_PTR(resp->error); + } + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + return sof_vhost_stream_playback(spcm->stream + direction, + substream->runtime, req, resp); + + return sof_vhost_stream_capture(spcm->stream + direction, + substream->runtime, req, resp); +} +EXPORT_SYMBOL_GPL(sof_vhost_stream_data); + +/* Handle the stream IPC */ +static int sof_vhost_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 = sof_vhost_pcm_open(sdev, hdr); + if (ret < 0) + break; + pcm = container_of(hdr, struct sof_ipc_pcm_params, hdr); + ret = sof_vhost_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 (!sof_vhost_find_spcm_comp(sdev, comp_id, &direction)) { + ret = -ENODEV; + break; + } + substream = sof_vhost_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: + sof_vhost_pcm_close(sdev, hdr); + break; + } + + return ret; +} + +/* validate component IPC */ +static int sof_vhost_ipc_comp(struct sof_vhost_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 sof_vhost_ipc_pm(struct sof_vhost_client *client, + struct sof_ipc_cmd_hdr *hdr, + struct sof_rpmsg_ipc_power_resp *resp) +{ + struct snd_sof_dev *sdev = client->sdev; + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + struct sof_rpmsg_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_rpmsg_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->dsp_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_RPMSG_IPC_RESET_NONE : SOF_RPMSG_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 sof_vhost_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 sof_vhost_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 sof_vhost_ipc_tplg_comp_new(struct sof_vhost_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); + 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; + } + + sof_vhost_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_RPMSG_COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = + SOF_RPMSG_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->dsp_reset_count); + break; + default: + break; + } + + return 0; +} + +/* Handle the "new pipeline" IPC: replace the scheduling sink ID */ +static int sof_vhost_ipc_tplg_pipe_new(struct sof_vhost_client *client, + 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 sof_vhost_ipc_tplg_comp_connect(struct sof_vhost_client *client, + 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 sof_vhost_ipc_tplg_read(struct sof_vhost_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_rpmsg_ipc_tplg_req *tplg = container_of(hdr, + struct sof_rpmsg_ipc_tplg_req, hdr); + struct sof_rpmsg_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; + + to_copy = min_t(size_t, reply_sz - sizeof(partdata->reply), + remainder); + partdata->reply.hdr.size = to_copy + sizeof(partdata->reply); + partdata->reply.hdr.cmd = hdr->cmd; + + memcpy(partdata->data, fw->data + tplg->offset, to_copy); + + dev_dbg(client->sdev->dev, "%s(): copy %zu, %zu remain\n", + __func__, to_copy, remainder); + + if (remainder == to_copy) { + release_firmware(fw); + client->fw = NULL; + } + + return 0; +} + +/* Send the next component ID to the guest */ +static int sof_vhost_ipc_tplg_comp_id(struct sof_vhost_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_rpmsg_ipc_tplg_resp *partdata = reply_buf; + + client->comp_id_begin = client->sdev->next_comp_id + + client->id * SOF_RPMSG_MAX_UOS_COMPS; + client->comp_id_end = client->comp_id_begin + SOF_RPMSG_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 sof_vhost_ipc_tplg(struct sof_vhost_client *client, + 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 sof_vhost_ipc_tplg_comp_new(client, hdr, reply_buf); + case SOF_IPC_TPLG_PIPE_NEW: + return sof_vhost_ipc_tplg_pipe_new(client, hdr); + case SOF_IPC_TPLG_COMP_CONNECT: + return sof_vhost_ipc_tplg_comp_connect(client, hdr); + case SOF_IPC_TPLG_VFE_GET: + ret = sof_vhost_ipc_tplg_read(client, hdr, reply_buf, reply_sz); + return ret < 0 ? ret : 1; + case SOF_IPC_TPLG_VFE_COMP_ID: + ret = sof_vhost_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_vhost_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 sof_vhost_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 = sof_vhost_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 start trigger IPC */ +static int sof_vhost_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; + struct snd_soc_dai *codec_dai; + unsigned int i; + int direction; + + if (!sof_vhost_find_spcm_comp(sdev, stream->comp_id, &direction)) + return -ENODEV; + + substream = sof_vhost_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + 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 && ops->trigger) { + int ret = ops->trigger(substream, + SNDRV_PCM_TRIGGER_START, + codec_dai); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int sof_vhost_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; + struct snd_soc_dai *codec_dai; + int direction, comp_id = stream->comp_id; + unsigned int i; + + if (!sof_vhost_find_spcm_comp(sdev, comp_id, &direction)) + return -ENODEV; + + substream = sof_vhost_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + const struct snd_soc_dai_ops *ops = codec_dai->driver->ops; + + if (ops && 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 sof_vhost_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 sof_vhost_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 sof_vhost_ipc_stream_codec(sdev, hdr); + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_TRIG_STOP: + ret = sof_vhost_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 sof_vhost_ipc_fwd(struct sof_vhost_client *client, + 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 < sizeof(*hdr) || count > SOF_IPC_MSG_MAX_SIZE) { + 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 = sof_vhost_ipc_comp(client, hdr); + if (ret < 0) + goto err; + break; + case SOF_IPC_GLB_STREAM_MSG: + ret = sof_vhost_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 = sof_vhost_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 = sof_vhost_ipc_tplg(client, 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_vhost_send_ipc(sdev, ipc_buf, reply_buf, count, reply_sz); + + /* For some IPCs, the reply needs to be handled */ + ret = sof_vhost_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 sof_vhost_error_reply(rhdr, hdr->cmd, ret); +} +EXPORT_SYMBOL_GPL(sof_vhost_ipc_fwd); + +int sof_vhost_set_tplg(struct sof_vhost_client *client, + const struct vhost_adsp_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(sof_vhost_set_tplg); + +void sof_vhost_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); + sof_vhost_stream_close(sdev, SNDRV_PCM_STREAM_PLAYBACK); + sof_vhost_stream_close(sdev, SNDRV_PCM_STREAM_CAPTURE); + kfree(spcm); + } +} + +/* A VM instance has closed the miscdevice */ +void sof_vhost_client_release(struct sof_vhost_client *client) +{ + bitmap_release_region(client->sdev->vfe_mask, client->id, 0); + + list_del(&client->list); + + kfree(client); +} +EXPORT_SYMBOL_GPL(sof_vhost_client_release); + +/* A new VM instance has opened the miscdevice */ +struct sof_vhost_client *sof_vhost_client_add(struct snd_sof_dev *sdev, + struct sof_vhost *dsp) +{ + int id = bitmap_find_free_region(sdev->vfe_mask, SND_SOF_MAX_VFES, 0); + struct sof_vhost_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(sof_vhost_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 sof_vhost_client *sof_vhost_comp_to_client( + struct snd_sof_dev *sdev, + int comp_id) +{ + struct sof_vhost_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 sof_vhost_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + struct sof_vhost_client *client = sof_vhost_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 *sof_vhost_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(sof_vhost_dev_init); + +/* This SOF device will be used for VirtIO */ +void sof_vhost_dev_set(struct snd_sof_dev *sdev) +{ + INIT_LIST_HEAD(&sdev->connector_list); + vhost_sof_dev = sdev; +}
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 --- include/sound/sof/rpmsg.h | 4 ++ sound/soc/sof/vhost-vbe.c | 158 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 1 deletion(-)
diff --git a/include/sound/sof/rpmsg.h b/include/sound/sof/rpmsg.h index ce522c6..7f907e6 100644 --- a/include/sound/sof/rpmsg.h +++ b/include/sound/sof/rpmsg.h @@ -137,6 +137,9 @@ struct sof_vhost_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; @@ -167,6 +170,7 @@ void *sof_vhost_stream_data(struct sof_vhost_client *client, int sof_vhost_ipc_fwd(struct sof_vhost_client *client, void *ipc_buf, void *reply_buf, size_t count, size_t reply_sz); +void sof_vhost_topology_purge(struct sof_vhost_client *client);
/* The below functions are always referenced, they need dummy counterparts */ int sof_vhost_update_guest_posn(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/vhost-vbe.c b/sound/soc/sof/vhost-vbe.c index 8056e25..3887bba 100644 --- a/sound/soc/sof/vhost-vbe.c +++ b/sound/soc/sof/vhost-vbe.c @@ -43,6 +43,18 @@ struct dsp_pipeline_connect { struct list_head list; };
+struct sof_vhost_comp_list { + struct list_head list; + uint32_t comp_id; + enum sof_comp_type comp_type; +}; + +struct sof_vhost_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 sof_vhost_ipc_comp(struct sof_vhost_client *client, cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; }
+void sof_vhost_topology_purge(struct sof_vhost_client *client) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_free fcomp = { + .hdr = { + .size = sizeof(fcomp), + }, + }; + struct sof_ipc_reply reply; + struct sof_vhost_comp_list *citem, *ctmp; + struct sof_vhost_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(sof_vhost_topology_purge); + /* process PM IPC */ static int sof_vhost_ipc_pm(struct sof_vhost_client *client, struct sof_ipc_cmd_hdr *hdr, @@ -527,6 +608,48 @@ int sof_vhost_add_conn(struct snd_sof_dev *sdev, return 0; }
+static int sof_vhost_tplg_comp_add(struct sof_vhost_client *client, + struct sof_ipc_comp *comp) +{ + struct sof_vhost_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 sof_vhost_tplg_pipe_add(struct sof_vhost_client *client, + struct sof_ipc_pipe_new *pipe) +{ + struct sof_vhost_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 sof_vhost_ipc_tplg_buf_new(struct sof_vhost_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 sof_vhost_tplg_comp_add(client, comp); +} + /* Handle some special cases of the "new component" IPC */ static int sof_vhost_ipc_tplg_comp_new(struct sof_vhost_client *client, struct sof_ipc_cmd_hdr *hdr, @@ -537,6 +660,7 @@ static int sof_vhost_ipc_tplg_comp_new(struct sof_vhost_client *client, 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) @@ -598,6 +722,10 @@ static int sof_vhost_ipc_tplg_comp_new(struct sof_vhost_client *client, break; }
+ ret = sof_vhost_tplg_comp_add(client, comp); + if (ret < 0) + return ret; + return 0; }
@@ -609,6 +737,9 @@ static int sof_vhost_ipc_tplg_pipe_new(struct sof_vhost_client *client, struct sof_ipc_pipe_new, hdr); struct snd_sof_dev *sdev = client->sdev; struct dsp_pipeline_connect *conn; + int ret = sof_vhost_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) { @@ -730,6 +861,8 @@ static int sof_vhost_ipc_tplg(struct sof_vhost_client *client, switch (cmd) { case SOF_IPC_TPLG_COMP_NEW: return sof_vhost_ipc_tplg_comp_new(client, hdr, reply_buf); + case SOF_IPC_TPLG_BUFFER_NEW: + return sof_vhost_ipc_tplg_buf_new(client, hdr, reply_buf); case SOF_IPC_TPLG_PIPE_NEW: return sof_vhost_ipc_tplg_pipe_new(client, hdr); case SOF_IPC_TPLG_COMP_CONNECT: @@ -995,6 +1128,23 @@ int sof_vhost_set_tplg(struct sof_vhost_client *client, void sof_vhost_suspend(struct snd_sof_dev *sdev) { struct snd_sof_pcm *spcm, *next; + struct sof_vhost_client *client; + + /* Upon resume we'll rebuild lists */ + list_for_each_entry(client, &sdev->vbe_list, list) { + struct sof_vhost_comp_list *citem, *ctmp; + struct sof_vhost_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)) { @@ -1008,6 +1158,9 @@ void sof_vhost_suspend(struct snd_sof_dev *sdev) /* A VM instance has closed the miscdevice */ void sof_vhost_client_release(struct sof_vhost_client *client) { + /* If a VM crashes we don't get ioctl(VHOST_SET_RUNNING, 0) from QEMU */ + sof_vhost_topology_purge(client); + bitmap_release_region(client->sdev->vfe_mask, client->id, 0);
list_del(&client->list); @@ -1018,7 +1171,7 @@ void sof_vhost_client_release(struct sof_vhost_client *client)
/* A new VM instance has opened the miscdevice */ struct sof_vhost_client *sof_vhost_client_add(struct snd_sof_dev *sdev, - struct sof_vhost *dsp) + struct vhost_dsp *dsp) { int id = bitmap_find_free_region(sdev->vfe_mask, SND_SOF_MAX_VFES, 0); struct sof_vhost_client *client; @@ -1032,6 +1185,9 @@ struct sof_vhost_client *sof_vhost_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;
The SOF ADSP 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 RPMsg guests.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- drivers/vhost/Kconfig | 10 + drivers/vhost/Makefile | 3 + drivers/vhost/adsp.c | 618 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 631 insertions(+) create mode 100644 drivers/vhost/adsp.c
diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig index 8b91f3e..ef3e459 100644 --- a/drivers/vhost/Kconfig +++ b/drivers/vhost/Kconfig @@ -54,6 +54,16 @@ config VHOST_SCSI Say M here to enable the vhost_scsi TCM fabric module for use with virtio-scsi guests
+config VHOST_SOF + tristate "Vhost SOF driver" + depends on SND_SOC_SOF + select VHOST + select VHOST_RPMSG + default n + help + SOF vhost VirtIO driver. It exports the same IPC interface, as the + one, used for Audio DSP communication, to Linux VirtIO guests. + config VHOST_VSOCK tristate "vhost virtio-vsock driver" depends on VSOCKETS && EVENTFD diff --git a/drivers/vhost/Makefile b/drivers/vhost/Makefile index 9cf459d..86cbd12 100644 --- a/drivers/vhost/Makefile +++ b/drivers/vhost/Makefile @@ -8,6 +8,9 @@ vhost_rpmsg-y := rpmsg.o obj-$(CONFIG_VHOST_SCSI) += vhost_scsi.o vhost_scsi-y := scsi.o
+obj-$(CONFIG_VHOST_SOF) += vhost_sof.o +vhost_sof-y := adsp.o + obj-$(CONFIG_VHOST_VSOCK) += vhost_vsock.o vhost_vsock-y := vsock.o
diff --git a/drivers/vhost/adsp.c b/drivers/vhost/adsp.c new file mode 100644 index 00000000..c0a496f --- /dev/null +++ b/drivers/vhost/adsp.c @@ -0,0 +1,618 @@ +// 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/virtio_rpmsg.h> +#include <uapi/linux/rpmsg.h> + +#include <sound/sof/stream.h> +#include <sound/sof/rpmsg.h> + +#include "vhost.h" +#include "vhost_rpmsg.h" + +#define VHOST_DSP_FEATURES (VHOST_FEATURES | (1ULL << VIRTIO_RPMSG_F_NS)) + +struct snd_sof_dev; +struct sof_vhost_client; + +struct vhost_dsp { + struct vhost_rpmsg vrdev; + + struct sof_vhost_client *snd; + + bool active; + + /* RPMsg address of the position update endpoint */ + u32 posn_addr; + /* position update buffer and work */ + struct vhost_work posn_work; + struct sof_ipc_stream_posn posn; + + /* IPC request buffer */ + struct sof_rpmsg_ipc_req ipc_buf; + /* IPC response buffer */ + u8 reply_buf[SOF_IPC_MSG_MAX_SIZE]; + /* + * data response header, captured audio data is copied directly from the + * DMA buffer + */ + struct sof_rpmsg_data_resp data_resp; +}; + +/* A guest is booting */ +static int vhost_dsp_activate(struct vhost_dsp *dsp) +{ + unsigned int i; + int ret = 0; + + mutex_lock(&dsp->vrdev.dev.mutex); + + /* Wait until all the VirtQueues have been initialised */ + if (!dsp->active) { + struct vhost_virtqueue *vq; + + for (i = 0, vq = dsp->vrdev.vq; + i < ARRAY_SIZE(dsp->vrdev.vq); + i++, vq++) { + /* .private_data is required != NULL */ + vhost_vq_set_backend(vq, dsp); + /* 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); + } + + /* Send an RPMsg namespace announcement */ + if (!ret && !vhost_rpmsg_ns_announce(&dsp->vrdev, "sof_rpmsg", + SOF_RPMSG_ADDR_IPC)) + dsp->active = true; + } + + mutex_unlock(&dsp->vrdev.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->vrdev.dev.mutex); + + if (dsp->active) { + struct vhost_virtqueue *vq; + + dsp->active = false; + + /* If a VM reboots sof_vhost_client_release() isn't called */ + sof_vhost_topology_purge(dsp->snd); + + /* signal, that we're inactive */ + for (i = 0, vq = dsp->vrdev.vq; + i < ARRAY_SIZE(dsp->vrdev.vq); + i++, vq++) { + mutex_lock(&vq->mutex); + vhost_vq_set_backend(vq, NULL); + mutex_unlock(&vq->mutex); + } + } + + mutex_unlock(&dsp->vrdev.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->vrdev.dev.mutex); + + if ((features & (1 << VHOST_F_LOG_ALL)) && + !vhost_log_access_ok(&dsp->vrdev.dev)) { + mutex_unlock(&dsp->vrdev.dev.mutex); + return -EFAULT; + } + + for (i = 0, vq = dsp->vrdev.vq; + i < ARRAY_SIZE(dsp->vrdev.vq); + i++, vq++) { + mutex_lock(&vq->mutex); + vq->acked_features = features; + mutex_unlock(&vq->mutex); + } + + mutex_unlock(&dsp->vrdev.dev.mutex); + + return 0; +} + +/* .ioctl(): we only use VHOST_SET_RUNNING in a non-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_adsp_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->vrdev.dev.mutex); + ret = vhost_dev_check_owner(&dsp->vrdev.dev); + if (!ret) { + struct vhost_iotlb *iotlb = + vhost_dev_reset_owner_prepare(); + if (!iotlb) { + ret = -ENOMEM; + } else { + vhost_dev_stop(&dsp->vrdev.dev); + vhost_dev_reset_owner(&dsp->vrdev.dev, iotlb); + } + } + mutex_unlock(&dsp->vrdev.dev.mutex); + return ret; + case VHOST_SET_OWNER: + mutex_lock(&dsp->vrdev.dev.mutex); + ret = vhost_dev_set_owner(&dsp->vrdev.dev); + mutex_unlock(&dsp->vrdev.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_ADSP_SET_GUEST_TPLG: + if (copy_from_user(&tplg, argp, sizeof(tplg))) + return -EFAULT; + return sof_vhost_set_tplg(dsp->snd, &tplg); + } + + mutex_lock(&dsp->vrdev.dev.mutex); + ret = vhost_dev_ioctl(&dsp->vrdev.dev, ioctl, argp); + if (ret == -ENOIOCTLCMD) + ret = vhost_vring_ioctl(&dsp->vrdev.dev, ioctl, argp); + mutex_unlock(&dsp->vrdev.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->vrdev.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->vrdev.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->vrdev.dev; + + return vhost_chr_poll(filp, dev, wait); +} + +static ssize_t vhost_dsp_data_read(struct vhost_rpmsg *vr, + struct vhost_rpmsg_iter *iter) +{ + struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev); + struct vhost_virtqueue *vq = iter->vq; + struct sof_rpmsg_data_resp *resp = &dsp->data_resp; + struct sof_rpmsg_data_req req; + size_t len = vhost_rpmsg_iter_len(iter); + size_t nbytes; + + if (len < sizeof(req)) { + vq_err(vq, "%s(): data count %zu too small\n", + __func__, len); + return -EINVAL; + } + + /* copy_{to,from}_iter() can be called repeatedly to continue copying */ + nbytes = vhost_rpmsg_copy(vr, iter, &req, sizeof(req)); + if (nbytes != sizeof(req)) { + vq_err(vq, + "%s(): got %zu instead of %zu bytes of data header\n", + __func__, nbytes, sizeof(req)); + return -EIO; + } + + len -= nbytes; + + /* Get a pointer to copy data to or from the audio buffer */ + iter->priv = sof_vhost_stream_data(dsp->snd, &req, resp); + if (IS_ERR(iter->priv)) { + vq_err(vq, "%s(): error %ld getting data pointer\n", + __func__, PTR_ERR(iter->priv)); + return PTR_ERR(iter->priv); + } + + if (len) { + /* Data in the buffer: playback */ + if (len != req.size) { + vq_err(vq, + "%s(): inconsistent data count: %zu vs. %u bytes\n", + __func__, len, req.size); + return -EPROTO; + } + + nbytes = vhost_rpmsg_copy(vr, iter, iter->priv, len); + if (nbytes != len) { + vq_err(vq, + "%s(): copied %zu instead of %zu bytes of data\n", + __func__, nbytes, len); + return -EIO; + } + + return sizeof(*resp); + } + + return sizeof(*resp) + resp->size; +} + +static ssize_t vhost_dsp_data_write(struct vhost_rpmsg *vr, + struct vhost_rpmsg_iter *iter) +{ + struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev); + struct vhost_virtqueue *vq = iter->vq; + struct sof_rpmsg_data_resp *resp = &dsp->data_resp; + size_t len = vhost_rpmsg_iter_len(iter); + size_t nbytes; + + if (len < sizeof(*resp)) { + vq_err(vq, + "%s(): %zu bytes aren't enough for %zu bytes of header\n", + __func__, len, sizeof(*resp)); + return -ENOBUFS; + } + + nbytes = vhost_rpmsg_copy(vr, iter, resp, sizeof(*resp)); + if (nbytes != sizeof(*resp)) { + vq_err(vq, + "%s(): copied %zu instead of %zu bytes of data\n", + __func__, nbytes, sizeof(*resp)); + return -EIO; + } + + if (resp->size && !resp->error) { + /* Capture */ + len -= sizeof(*resp); + + if (len < resp->size) { + vq_err(vq, + "%s(): insufficient buffer space %zu for %u bytes\n", + __func__, len, resp->size); + return -EPROTO; + } + + nbytes = vhost_rpmsg_copy(vr, iter, iter->priv, resp->size); + if (nbytes != resp->size) { + vq_err(vq, + "%s(): copied %zu instead of %u bytes of data\n", + __func__, nbytes, resp->size); + return -EIO; + } + } + + return 0; +} + +static ssize_t vhost_dsp_ipc_read(struct vhost_rpmsg *vr, + struct vhost_rpmsg_iter *iter) +{ + struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev); + struct vhost_virtqueue *vq = iter->vq; + size_t len = vhost_rpmsg_iter_len(iter); + size_t nbytes; + int ret; + + if (len > sizeof(dsp->ipc_buf)) { + vq_err(vq, "%s(): data count %zu too large\n", + __func__, len); + return -ENOBUFS; + } + + if (len < sizeof(struct sof_ipc_cmd_hdr)) { + vq_err(vq, "%s(): data count %zu too small\n", + __func__, len); + return -EINVAL; + } + + nbytes = vhost_rpmsg_copy(vr, iter, &dsp->ipc_buf, len); + if (nbytes != len) { + vq_err(vq, "Expected %zu bytes for IPC, got %zu bytes\n", + len, nbytes); + return -EIO; + } + + /* Process the IPC payload */ + ret = sof_vhost_ipc_fwd(dsp->snd, dsp->ipc_buf.ipc_msg, + dsp->reply_buf, len - + offsetof(struct sof_rpmsg_ipc_req, ipc_msg), + dsp->ipc_buf.reply_size); + if (ret < 0) { + struct sof_ipc_cmd_hdr *cmd_hdr = + (struct sof_ipc_cmd_hdr *)dsp->ipc_buf.ipc_msg; + vq_err(vq, "%s(): IPC 0x%x failed with error %d\n", + __func__, cmd_hdr->cmd, ret); + /* continue to send an error response */ + } + + return ((struct sof_ipc_reply *)dsp->reply_buf)->hdr.size; +} + +static ssize_t vhost_dsp_ipc_write(struct vhost_rpmsg *vr, + struct vhost_rpmsg_iter *iter) +{ + struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev); + + return vhost_rpmsg_copy(vr, iter, dsp->reply_buf, + vhost_rpmsg_iter_len(iter)) == + vhost_rpmsg_iter_len(iter) ? 0 : -EIO; +} + +/* Called only once to get guest's position update endpoint address */ +static ssize_t vhost_dsp_posn_read(struct vhost_rpmsg *vr, + struct vhost_rpmsg_iter *iter) +{ + struct vhost_dsp *dsp = container_of(vr, struct vhost_dsp, vrdev); + struct vhost_virtqueue *vq = &dsp->vrdev.vq[VIRTIO_RPMSG_REQUEST]; + size_t len = vhost_rpmsg_iter_len(iter); + size_t nbytes; + + if ((int)dsp->posn_addr >= 0) { + vq_err(vq, "%s(): position queue address %u already set\n", + __func__, dsp->posn_addr); + return -EINVAL; + } + + if (len != sizeof(dsp->posn_addr)) { + vq_err(vq, "%s(): data count %zu invalid\n", + __func__, len); + return -EINVAL; + } + + nbytes = vhost_rpmsg_copy(vr, iter, &dsp->posn_addr, + sizeof(dsp->posn_addr)); + if (nbytes != sizeof(dsp->posn_addr)) { + vq_err(vq, + "%s(): got %zu instead of %zu bytes position update\n", + __func__, nbytes, sizeof(dsp->posn_addr)); + return -EIO; + } + + pr_debug("%s(): guest position endpoint address 0x%x\n", __func__, + dsp->posn_addr); + + return 0; +} + +static void vhost_dsp_send_posn(struct vhost_work *work) +{ + struct vhost_dsp *dsp = container_of(work, struct vhost_dsp, posn_work); + struct vhost_rpmsg_iter iter = VHOST_RPMSG_ITER(SOF_RPMSG_ADDR_POSN, + dsp->posn_addr); + ssize_t nbytes; + int ret; + + ret = vhost_rpmsg_start_lock(&dsp->vrdev, &iter, VIRTIO_RPMSG_RESPONSE, + sizeof(dsp->posn)); + if (ret < 0) + return; + + nbytes = vhost_rpmsg_copy(&dsp->vrdev, &iter, &dsp->posn, + sizeof(dsp->posn)); + if (nbytes != sizeof(dsp->posn)) + vq_err(iter.vq, "%s(): added %zd instead of %zu bytes\n", + __func__, nbytes, sizeof(dsp->posn)); + + ret = vhost_rpmsg_finish_unlock(&dsp->vrdev, &iter); +} + +static const struct vhost_rpmsg_ept vhost_dsp_ept[] = { + { + .addr = SOF_RPMSG_ADDR_IPC, + .read = vhost_dsp_ipc_read, + .write = vhost_dsp_ipc_write, + }, { + .addr = SOF_RPMSG_ADDR_POSN, + .read = vhost_dsp_posn_read, + .write = NULL, /* position updates are sent from a work-queue */ + }, { + .addr = SOF_RPMSG_ADDR_DATA, + .read = vhost_dsp_data_read, + .write = vhost_dsp_data_write, + } +}; + +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 = kmalloc(sizeof(*dsp), GFP_KERNEL); + + if (!dsp) + return -ENOMEM; + + dsp->vrdev.dev.mm = NULL; + dsp->snd = sof_vhost_client_add(sdev, dsp); + if (!dsp->snd) { + kfree(dsp); + return -ENOMEM; + } + + /* + * 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. + */ + dsp->active = false; + dsp->posn_addr = -EINVAL; + dsp->posn.rhdr.error = -ENODATA; + + vhost_rpmsg_init(&dsp->vrdev, vhost_dsp_ept, ARRAY_SIZE(vhost_dsp_ept)); + vhost_work_init(&dsp->posn_work, vhost_dsp_send_posn); + + /* 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->vrdev.dev, &dsp->posn_work); + + vhost_rpmsg_destroy(&dsp->vrdev); + + sof_vhost_client_release(dsp->snd); + + kfree(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 vhost_dsp_update_posn(struct vhost_dsp *dsp, + struct sof_ipc_stream_posn *posn) +{ + struct vhost_virtqueue *vq = &dsp->vrdev.vq[VIRTIO_RPMSG_RESPONSE]; + int ret; + + if (!dsp->active) + return 0; + + memcpy(&dsp->posn, posn, sizeof(dsp->posn)); + + mutex_lock(&vq->mutex); + + /* + * VirtQueues can only be processed in the context of the VMM process or + * a vhost work queue + */ + vhost_work_queue(&dsp->vrdev.dev, &dsp->posn_work); + + mutex_unlock(&vq->mutex); + + return ret; +} + +static struct sof_vhost_ops vhost_dsp_ops = { + .update_posn = vhost_dsp_update_posn, +}; + +static int __init vhost_dsp_init(void) +{ + vhost_dsp_misc.parent = sof_vhost_dev_init(&vhost_dsp_ops); + if (!vhost_dsp_misc.parent) + return -ENODEV; + + return misc_register(&vhost_dsp_misc); +} + +static void __exit vhost_dsp_exit(void) +{ + misc_deregister(&vhost_dsp_misc); +} + +module_init(vhost_dsp_init); +module_exit(vhost_dsp_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Guennadi Liakhovetski"); +MODULE_DESCRIPTION("Host kernel accelerator for virtio sound");
It is hard to imagine use-cases where 512 buffers would really be needed, whereas 512 bytes per buffer might be too little. Change this to use 16 16KiB buffers instead.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- include/linux/virtio_rpmsg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/virtio_rpmsg.h b/include/linux/virtio_rpmsg.h index 679be8b..1add468 100644 --- a/include/linux/virtio_rpmsg.h +++ b/include/linux/virtio_rpmsg.h @@ -72,8 +72,8 @@ enum rpmsg_ns_flags { * can change this without changing anything in the firmware of the remote * processor. */ -#define MAX_RPMSG_NUM_BUFS 512 -#define MAX_RPMSG_BUF_SIZE 512 +#define MAX_RPMSG_NUM_BUFS (512 / 32) +#define MAX_RPMSG_BUF_SIZE (512 * 32)
/* Address 53 is reserved for advertising remote services */ #define RPMSG_NS_ADDR 53
Hi Guennadi,
On Fri, May 29, 2020 at 09:37:21AM +0200, Guennadi Liakhovetski wrote:
It is hard to imagine use-cases where 512 buffers would really be needed, whereas 512 bytes per buffer might be too little. Change this to use 16 16KiB buffers instead.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
include/linux/virtio_rpmsg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/virtio_rpmsg.h b/include/linux/virtio_rpmsg.h index 679be8b..1add468 100644 --- a/include/linux/virtio_rpmsg.h +++ b/include/linux/virtio_rpmsg.h @@ -72,8 +72,8 @@ enum rpmsg_ns_flags {
- can change this without changing anything in the firmware of the remote
- processor.
*/ -#define MAX_RPMSG_NUM_BUFS 512 -#define MAX_RPMSG_BUF_SIZE 512 +#define MAX_RPMSG_NUM_BUFS (512 / 32) +#define MAX_RPMSG_BUF_SIZE (512 * 32)
These have been a standard in the rpmsg protocol since the inception of the subsystem 9 years ago and can't be changed without serious impact to existing implementations.
I suggest to dynamically set the number and size of the buffers to use based on the value of virtio_device_id::device. To do that please spin off a new function, something like rpmsg_get_buffer_size(), and in there use the device ID to fetch the numbers based on vdev->id->device. That way the rpmsg driver can be used by multiple clients and the specifics of the buffers adjusted without impact to other users.
Thanks, Mathieu
/* Address 53 is reserved for advertising remote services */
#define RPMSG_NS_ADDR 53
1.9.3
Hi Mathieu,
On Thu, Jun 04, 2020 at 01:58:39PM -0600, Mathieu Poirier wrote:
Hi Guennadi,
On Fri, May 29, 2020 at 09:37:21AM +0200, Guennadi Liakhovetski wrote:
It is hard to imagine use-cases where 512 buffers would really be needed, whereas 512 bytes per buffer might be too little. Change this to use 16 16KiB buffers instead.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
include/linux/virtio_rpmsg.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/virtio_rpmsg.h b/include/linux/virtio_rpmsg.h index 679be8b..1add468 100644 --- a/include/linux/virtio_rpmsg.h +++ b/include/linux/virtio_rpmsg.h @@ -72,8 +72,8 @@ enum rpmsg_ns_flags {
- can change this without changing anything in the firmware of the remote
- processor.
*/ -#define MAX_RPMSG_NUM_BUFS 512 -#define MAX_RPMSG_BUF_SIZE 512 +#define MAX_RPMSG_NUM_BUFS (512 / 32) +#define MAX_RPMSG_BUF_SIZE (512 * 32)
These have been a standard in the rpmsg protocol since the inception of the subsystem 9 years ago and can't be changed without serious impact to existing implementations.
Yes, I expected this to raise complaints. I just modified them to be able to run my code, but a better solution is needed for sure.
I suggest to dynamically set the number and size of the buffers to use based on the value of virtio_device_id::device. To do that please spin off a new function, something like rpmsg_get_buffer_size(), and in there use the device ID to fetch the numbers based on vdev->id->device. That way the rpmsg driver can be used by multiple clients and the specifics of the buffers adjusted without impact to other users.
I'll look into this!
Thanks Guennadi
Thanks, Mathieu
/* Address 53 is reserved for advertising remote services */
#define RPMSG_NS_ADDR 53
1.9.3
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com --- drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID }, + { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID }, { 0 }, };
On Fri, May 29, 2020 at 09:37:22AM +0200, Guennadi Liakhovetski wrote:
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID },
- { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID },
I am fine with this patch but won't add an RB because of the (many) checkpatch errors. Based on the comment I made on the previous set seeing those was unexpected.
Thanks, Mathieu
{ 0 }, };
-- 1.9.3
Hi Mathieu,
On Thu, Jun 04, 2020 at 02:01:56PM -0600, Mathieu Poirier wrote:
On Fri, May 29, 2020 at 09:37:22AM +0200, Guennadi Liakhovetski wrote:
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID },
- { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID },
I am fine with this patch but won't add an RB because of the (many) checkpatch errors. Based on the comment I made on the previous set seeing those was unexpected.
Are you using "--strict?" Sorry, I don't see any checkpatch errors, only warnings. Most of them are "over 80 characters" which as we now know is no more an issue, I just haven't updated my tree yet. Most others are really minor IMHO. Maybe one of them I actually would want to fix - using "help" instead of "---help---" in Kconfig. What errors are you seeing in your checks?
Thanks Guennadi
Thanks, Mathieu
{ 0 }, };
-- 1.9.3
On Fri, Jun 05, 2020 at 08:46:59AM +0200, Guennadi Liakhovetski wrote:
Hi Mathieu,
On Thu, Jun 04, 2020 at 02:01:56PM -0600, Mathieu Poirier wrote:
On Fri, May 29, 2020 at 09:37:22AM +0200, Guennadi Liakhovetski wrote:
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID },
- { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID },
I am fine with this patch but won't add an RB because of the (many) checkpatch errors. Based on the comment I made on the previous set seeing those was unexpected.
Are you using "--strict?" Sorry, I don't see any checkpatch errors, only warnings.
No, plane checkpatch on the rproc-next branch.
Most of them are "over 80 characters" which as we now know is no more an issue,
There is a thread discussing the matter but I have not seen a clear resolution yet.
I just haven't updated my tree yet. Most others are really minor IMHO. Maybe one
Minor or not, if checkpatch complains then it is important enough to address. I am willing to overlook the lines over 80 characters but everything else needs to be dealt with.
Thanks, Mathieu
of them I actually would want to fix - using "help" instead of "---help---" in Kconfig. What errors are you seeing in your checks?
Thanks Guennadi
Thanks, Mathieu
{ 0 }, };
-- 1.9.3
Hi Mathieu,
On Mon, Jun 08, 2020 at 10:17:57AM -0600, Mathieu Poirier wrote:
On Fri, Jun 05, 2020 at 08:46:59AM +0200, Guennadi Liakhovetski wrote:
Hi Mathieu,
On Thu, Jun 04, 2020 at 02:01:56PM -0600, Mathieu Poirier wrote:
On Fri, May 29, 2020 at 09:37:22AM +0200, Guennadi Liakhovetski wrote:
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID },
- { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID },
I am fine with this patch but won't add an RB because of the (many) checkpatch errors. Based on the comment I made on the previous set seeing those was unexpected.
Are you using "--strict?" Sorry, I don't see any checkpatch errors, only warnings.
No, plane checkpatch on the rproc-next branch.
Most of them are "over 80 characters" which as we now know is no more an issue,
There is a thread discussing the matter but I have not seen a clear resolution yet.
I think the resolution is pretty clear as defined by Linus, but maybe it has changed again since I last checked.
I just haven't updated my tree yet. Most others are really minor IMHO. Maybe one
Minor or not, if checkpatch complains then it is important enough to address. I am willing to overlook the lines over 80 characters but everything else needs to be dealt with.
Sure, checkpatch should be run before each patch submission and whatever it reports should be considered. As Documentation/process/submitting-patches.rst clearly states:
"Check your patches with the patch style checker prior to submission (scripts/checkpatch.pl). Note, though, that the style checker should be viewed as a guide, not as a replacement for human judgment. If your code looks better with a violation then its probably best left alone."
So, yes, I checked all what checkepatch reported and used my judgement to decide which recommendations to take and which to ignore.
Thanks Guennadi
Thanks, Mathieu
of them I actually would want to fix - using "help" instead of "---help---" in Kconfig. What errors are you seeing in your checks?
Thanks Guennadi
Thanks, Mathieu
{ 0 }, };
-- 1.9.3
On Mon, Jun 08, 2020 at 07:02:27PM +0200, Guennadi Liakhovetski wrote:
Hi Mathieu,
On Mon, Jun 08, 2020 at 10:17:57AM -0600, Mathieu Poirier wrote:
On Fri, Jun 05, 2020 at 08:46:59AM +0200, Guennadi Liakhovetski wrote:
Hi Mathieu,
On Thu, Jun 04, 2020 at 02:01:56PM -0600, Mathieu Poirier wrote:
On Fri, May 29, 2020 at 09:37:22AM +0200, Guennadi Liakhovetski wrote:
The ADSP device uses the RPMsg API to connect vhost and VirtIO SOF Audio DSP drivers on KVM host and guest.
Signed-off-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com
drivers/rpmsg/virtio_rpmsg_bus.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index f3bd050..ebe3f19 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -949,6 +949,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
static struct virtio_device_id id_table[] = { { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID },
- { VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID },
I am fine with this patch but won't add an RB because of the (many) checkpatch errors. Based on the comment I made on the previous set seeing those was unexpected.
Are you using "--strict?" Sorry, I don't see any checkpatch errors, only warnings.
No, plane checkpatch on the rproc-next branch.
Most of them are "over 80 characters" which as we now know is no more an issue,
There is a thread discussing the matter but I have not seen a clear resolution yet.
I think the resolution is pretty clear as defined by Linus, but maybe it has changed again since I last checked.
I just haven't updated my tree yet. Most others are really minor IMHO. Maybe one
Minor or not, if checkpatch complains then it is important enough to address. I am willing to overlook the lines over 80 characters but everything else needs to be dealt with.
Sure, checkpatch should be run before each patch submission and whatever it reports should be considered. As Documentation/process/submitting-patches.rst clearly states:
"Check your patches with the patch style checker prior to submission (scripts/checkpatch.pl). Note, though, that the style checker should be viewed as a guide, not as a replacement for human judgment. If your code looks better with a violation then its probably best left alone."
So, yes, I checked all what checkepatch reported and used my judgement to decide which recommendations to take and which to ignore.
I will let Michael and friends decide how to handle checkpatch warnings in the vhost subsystem but as far as remoteproc/rpmsg are concerned, I will not review patches that trigger warnings.
There is a patch in linux-next that deprecates warnings for lines over 80 characters, so those are no longer a problem.
Thanks Guennadi
Thanks, Mathieu
of them I actually would want to fix - using "help" instead of "---help---" in Kconfig. What errors are you seeing in your checks?
Thanks Guennadi
Thanks, Mathieu
{ 0 }, };
-- 1.9.3
participants (2)
-
Guennadi Liakhovetski
-
Mathieu Poirier