From patchwork Thu Mar 12 14:44:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193319 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 49654C10DCE for ; Thu, 12 Mar 2020 14:47:17 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C89BE2072F for ; Thu, 12 Mar 2020 14:47:16 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="c8+Is28B" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org C89BE2072F Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 1778F1711; Thu, 12 Mar 2020 15:46:25 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 1778F1711 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024435; bh=ypn/OERoUpG1c3RInrBL42GgO22kfXAkU3XbwZw77e0=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=c8+Is28BWzG1IbU7qg4co8f96Vw/+thn0XNgHcT/3+vsZll6yRIO/IXqmJsURBhbz fMB1RFc42t6oYupNKRzqwKV1SB4Q75J80DhfB4jwqn2buGUh4r0WiitgRmXKPMlglL epXYj8GdV3BLVpnmqJQAip/UtRRwTxQLT9fkQZwI= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 3E870F80086; Thu, 12 Mar 2020 15:44:55 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 6FA3EF80292; Thu, 12 Mar 2020 15:44:53 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id A9975F8028D; Thu, 12 Mar 2020 15:44:48 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz A9975F8028D X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957451" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:34 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 02/14] ASoC: SOF: extract firmware-related operation into a function Date: Thu, 12 Mar 2020 15:44:17 +0100 Message-Id: <20200312144429.17959-3-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" 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 --- sound/soc/sof/core.c | 85 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 91acfae..ca30d67 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -135,6 +135,53 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, * (System Suspend/Runtime Suspend) */ +static int sof_load_and_run_firmware(struct snd_sof_dev *sdev) +{ + /* load the firmware */ + int ret = snd_sof_load_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", + ret); + return ret; + } + + sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; + + /* + * Boot the firmware. The FW boot status will be modified + * in snd_sof_run_firmware() depending on the outcome. + */ + ret = snd_sof_run_firmware(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", + ret); + goto fw_run_err; + } + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || + (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { + sdev->dtrace_is_supported = true; + + /* init DMA trace */ + ret = snd_sof_init_trace(sdev); + if (ret < 0) { + /* non fatal */ + dev_warn(sdev->dev, + "warning: failed to initialize trace %d\n", + ret); + } + } else { + dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); + } + + return 0; + +fw_run_err: + snd_sof_fw_unload(sdev); + + return ret; +} + static int sof_probe_continue(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; @@ -180,42 +227,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; } - /* load the firmware */ - ret = snd_sof_load_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", - ret); + ret = sof_load_and_run_firmware(sdev); + if (ret < 0) goto fw_load_err; - } - - sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; - - /* - * Boot the firmware. The FW boot status will be modified - * in snd_sof_run_firmware() depending on the outcome. - */ - ret = snd_sof_run_firmware(sdev); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", - ret); - goto fw_run_err; - } - - if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || - (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { - sdev->dtrace_is_supported = true; - - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); - if (ret < 0) { - /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", - ret); - } - } else { - dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); - } /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -249,7 +263,6 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) fw_trace_err: snd_sof_free_trace(sdev); -fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: snd_sof_ipc_free(sdev); From patchwork Thu Mar 12 14:44:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193318 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1F178C10DCE for ; Thu, 12 Mar 2020 14:48:24 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 9C2582071C for ; Thu, 12 Mar 2020 14:48:23 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="uNj6dT6D" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 9C2582071C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id E905E1711; Thu, 12 Mar 2020 15:47:31 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz E905E1711 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024502; bh=CzDuwL0kjtKcimHd1XbGl/22092bJljLNeREghNsiYI=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=uNj6dT6DkEEJ3NxqGWKa5aUvlSQGMBpWK2x2FNN2LWUdfovfM2mBs6dri6ycNMqeV fV7EmTToVuhHgZSvA+/y5/XxbNmfCe8gTOSSs1Qb1YWbU0UhEJDpkFRhQh2d/jgKij oKtnx1vOrYBsVtDunWBD+2PO3joZl/5JOGSETN8Q= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id CAC82F802E0; Thu, 12 Mar 2020 15:45:07 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 34A87F80290; Thu, 12 Mar 2020 15:44:55 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id E8B20F80290; Thu, 12 Mar 2020 15:44:50 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz E8B20F80290 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:39 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957503" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:37 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 04/14] vhost: convert VHOST_VSOCK_SET_RUNNING to a generic ioctl Date: Thu, 12 Mar 2020 15:44:19 +0100 Message-Id: <20200312144429.17959-5-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" VHOST_VSOCK_SET_RUNNING is used by the vhost vsock driver to perform crucial VirtQueue initialisation, like assigning .private fields and calling vhost_vq_init_access(), and clean up. However, this ioctl is actually extremely useful for any vhost driver, that doesn't have a side channel to inform it of a status change, e.g. upon a guest reboot. This patch makes that ioctl generic, while preserving its numeric value and also keeping the original alias. Signed-off-by: Guennadi Liakhovetski --- include/uapi/linux/vhost.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index 40d028ee..c628103 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -93,6 +93,8 @@ #define VHOST_SET_BACKEND_FEATURES _IOW(VHOST_VIRTIO, 0x25, __u64) #define VHOST_GET_BACKEND_FEATURES _IOR(VHOST_VIRTIO, 0x26, __u64) +#define VHOST_SET_RUNNING _IOW(VHOST_VIRTIO, 0x61, int) + /* VHOST_NET specific defines */ /* Attach virtio net ring to a raw socket, or tap device. @@ -114,6 +116,6 @@ /* VHOST_VSOCK specific defines */ #define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) -#define VHOST_VSOCK_SET_RUNNING _IOW(VHOST_VIRTIO, 0x61, int) +#define VHOST_VSOCK_SET_RUNNING VHOST_SET_RUNNING #endif From patchwork Thu Mar 12 14:44:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193317 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 92566C10DCE for ; Thu, 12 Mar 2020 14:49:50 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 1FF9B206FA for ; Thu, 12 Mar 2020 14:49:50 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="pF8jm0/t" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 1FF9B206FA Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 79353171F; Thu, 12 Mar 2020 15:48:58 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 79353171F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024588; bh=8e2S/U+kp08mrCdLAThG8ojTPA4hK6yC2BAzeJqLfOc=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=pF8jm0/tUzkfDBYodTzaw8FW9SCANO3HxAsVugPdqZkeFhGYpZfN5fqb2OT8TkxUh lg1dlGWfCvnJNfqeHlY5OKhAhG0HuQxdZxMAOIAYb/uGZin6+0Uhr1nosymaNUn6mi g7KG59SoE5dTkRwaHkYAtwVwKuGdkx40eQXb37BY= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id C857CF80306; Thu, 12 Mar 2020 15:45:15 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id E0904F802BD; Thu, 12 Mar 2020 15:45:00 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 0CD82F80292; Thu, 12 Mar 2020 15:44:53 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 0CD82F80292 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:43 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957580" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:42 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 07/14] ASoC: SOF: add two helper lookup functions Date: Thu, 12 Mar 2020 15:44:22 +0100 Message-Id: <20200312144429.17959-8-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" 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 --- sound/soc/sof/sof-audio.c | 24 ++++++++++++++++++++++++ sound/soc/sof/sof-audio.h | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index fc4ed2a..b4c5fe2 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -395,6 +395,30 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, return NULL; } +struct snd_sof_widget *snd_sof_find_swidget_id(struct snd_sof_dev *sdev, + unsigned int comp_id) +{ + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) + if (swidget->comp_id == comp_id) + return swidget; + + return NULL; +} + +struct snd_sof_dai *snd_sof_find_dai_pipe(struct snd_sof_dev *sdev, + unsigned int pipeline_id) +{ + struct snd_sof_dai *dai; + + list_for_each_entry(dai, &sdev->dai_list, list) + if (dai->pipeline_id == pipeline_id) + return dai; + + return NULL; +} + /* * SOF Driver enumeration. */ diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index eacd10e..e9a6091 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -188,6 +188,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); /* From patchwork Thu Mar 12 14:44:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193316 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2A7AAC10DCE for ; Thu, 12 Mar 2020 14:51:06 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 9F4E420674 for ; Thu, 12 Mar 2020 14:51:05 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="ma/MpKCH" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 9F4E420674 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id DA0E41700; Thu, 12 Mar 2020 15:50:13 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz DA0E41700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024663; bh=EsUMLGp1bW4ci6OpCD9hakqz1xwHUacWL4Amx4pNY4E=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=ma/MpKCHcgIgI6bmgOcDIBg+7Kr80DaVFFsKNBRpu83Cm2/avLOh7AXq+CanZE1my CzHP3u695to8r+4+RyKKCLZqlZuMC3DylVRnFrK7M1UeSkJD8yIlCYUNsSKHE7f0vH eIShuuqgXT328ODrimZReLAWYdYbI7RschXsu1HA= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 852E1F80332; Thu, 12 Mar 2020 15:45:23 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id CD6F7F802DD; Thu, 12 Mar 2020 15:45:04 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 9A152F80290; Thu, 12 Mar 2020 15:44:55 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 9A152F80290 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:45 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957603" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:43 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 08/14] ASoC: SOF: fix uninitialised "work" with VirtIO Date: Thu, 12 Mar 2020 15:44:23 +0100 Message-Id: <20200312144429.17959-9-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" In the VirtIO case the sof_pcm_open() function isn't called on the host during guest streaming, which then leaves "work" structures uninitialised. However it is then used to handle position update messages from the DSP. Move their initialisation to immediately after allocation of the containing structure. Signed-off-by: Guennadi Liakhovetski --- sound/soc/sof/pcm.c | 2 -- sound/soc/sof/topology.c | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index f4769e1..cd77796 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -475,8 +475,6 @@ static int sof_pcm_open(struct snd_soc_component *component, dev_dbg(component->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); - INIT_WORK(&spcm->stream[substream->stream].period_elapsed_work, - sof_pcm_period_elapsed_work); caps = &spcm->pcm.caps[substream->stream]; diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 9f4f886..16ee748 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2459,6 +2459,11 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED; spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED; + INIT_WORK(&spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].period_elapsed_work, + sof_pcm_period_elapsed_work); + INIT_WORK(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].period_elapsed_work, + sof_pcm_period_elapsed_work); + spcm->pcm = *pcm; dev_dbg(scomp->dev, "tplg: load pcm %s\n", pcm->dai_name); From patchwork Thu Mar 12 14:44:25 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193313 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 779E8C10DCE for ; Thu, 12 Mar 2020 14:54:57 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id CE7B520663 for ; Thu, 12 Mar 2020 14:54:56 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="Gw/pfDXU" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org CE7B520663 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 2046F170D; Thu, 12 Mar 2020 15:54:05 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 2046F170D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024895; bh=ivmjY1lgjGSmQvfOqipE+rdkktOvt90Zwk0QsxyF03A=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=Gw/pfDXUNoSQWcRf72gq1YS4bFpd/Q/Kx1jOASuVzV/DpgJdxwJl/mfm/Z7ODKlda F5T0z87b50AdX44CxQdGVFDWxq5psjuyquhTbqf8ixqdmkavX06mAPGSXNA/fDq6qs +T3PKF8lZQuWXnJHWXqUpNd6cK9hBk9PfyCPKQ20= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 3D632F80384; Thu, 12 Mar 2020 15:45:36 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 48875F80333; Thu, 12 Mar 2020 15:45:24 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 2FC38F802A0; Thu, 12 Mar 2020 15:44:57 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 2FC38F802A0 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:50 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957664" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:47 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 10/14] ASoC: SOF: add a vhost driver: sound part Date: Thu, 12 Mar 2020 15:44:25 +0100 Message-Id: <20200312144429.17959-11-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" The SOF VirtIO driver uses a vhost driver as a counterpart to communicate with the DSP. This patch adds a sound interface of the vhost driver. Signed-off-by: Guennadi Liakhovetski --- include/sound/soc-topology.h | 3 + include/sound/sof/virtio.h | 73 +++ include/uapi/linux/vhost.h | 5 + include/uapi/linux/vhost_types.h | 7 + sound/soc/soc-pcm.c | 31 +- sound/soc/sof/Kconfig | 1 + sound/soc/sof/Makefile | 4 + sound/soc/sof/core.c | 6 + sound/soc/sof/ipc.c | 5 + sound/soc/sof/pcm.c | 6 +- sound/soc/sof/pm.c | 4 + sound/soc/sof/sof-audio.c | 9 + sound/soc/sof/sof-audio.h | 17 + sound/soc/sof/sof-priv.h | 18 + sound/soc/sof/topology.c | 55 +- sound/soc/sof/vhost-be.c | 1094 ++++++++++++++++++++++++++++++++++++++ 16 files changed, 1325 insertions(+), 13 deletions(-) create mode 100644 sound/soc/sof/vhost-be.c diff --git a/include/sound/soc-topology.h b/include/sound/soc-topology.h index 5223896..ea0c2a6 100644 --- a/include/sound/soc-topology.h +++ b/include/sound/soc-topology.h @@ -34,6 +34,9 @@ /* object scan be loaded and unloaded in groups with identfying indexes */ #define SND_SOC_TPLG_INDEX_ALL 0 /* ID that matches all FW objects */ +#define SOC_VIRT_DAI_PLAYBACK "VM FE Playback" +#define SOC_VIRT_DAI_CAPTURE "VM FE Capture" + /* dynamic object type */ enum snd_soc_dobj_type { SND_SOC_DOBJ_NONE = 0, /* object is not dynamic */ diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h index d7d94da..fc98664 100644 --- a/include/sound/sof/virtio.h +++ b/include/sound/sof/virtio.h @@ -11,6 +11,8 @@ #ifndef _SOF_VIRTIO_H #define _SOF_VIRTIO_H +#include + #include /* @@ -126,4 +128,75 @@ struct dsp_sof_data_resp { #define HDR_SIZE_REQ offsetof(struct dsp_sof_data_req, data) #define HDR_SIZE_RESP offsetof(struct dsp_sof_data_resp, data) +struct snd_sof_dev; +struct sof_ipc_stream_posn; + +#if IS_ENABLED(CONFIG_VHOST_SOF) +struct firmware; + +struct vhost_dsp; +struct sof_vhost_ops { + int (*update_posn)(struct vhost_dsp *dsp, + struct sof_ipc_stream_posn *posn); +}; + +struct dsp_sof_client { + const struct firmware *fw; + struct snd_sof_dev *sdev; + /* List of guest endpoints, connecting to the host mixer or demux */ + struct list_head pipe_conn; + /* List of vhost instances on a DSP */ + struct list_head list; + + /* Component ID range index in the bitmap */ + unsigned int id; + + /* the comp_ids for this vm audio */ + int comp_id_begin; + int comp_id_end; + + unsigned int reset_count; + + struct vhost_dsp *vhost; +}; + +/* The below functions are only referenced when VHOST_SOF is selected */ +struct device; +void dsp_sof_client_release(struct dsp_sof_client *client); +struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, + struct vhost_dsp *dsp); +struct device *dsp_sof_dev_init(const struct sof_vhost_ops *ops); +struct vhost_dsp_topology; +int dsp_sof_set_tplg(struct dsp_sof_client *client, + const struct vhost_dsp_topology *tplg); +/* Copy audio data between DMA and VirtQueue */ +int dsp_sof_stream_data(struct dsp_sof_client *client, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply); +/* Forward an IPC message from a guest to the DSP */ +int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, + void *ipc_buf, void *reply_buf, + size_t count, size_t reply_sz); + +/* The below functions are always referenced, they need dummy counterparts */ +int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn); +void dsp_sof_suspend(struct snd_sof_dev *sdev); +void dsp_sof_dev_set(struct snd_sof_dev *sdev); +#else +static inline int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + return 0; +} + +static inline void dsp_sof_suspend(struct snd_sof_dev *sdev) +{ +} + +static inline void dsp_sof_dev_set(struct snd_sof_dev *sdev) +{ +} +#endif + #endif diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index c628103..a9b0f99a 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -118,4 +118,9 @@ #define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) #define VHOST_VSOCK_SET_RUNNING VHOST_SET_RUNNING +/* VHOST_DSP specific defines */ + +#define VHOST_DSP_SET_GUEST_TPLG _IOW(VHOST_VIRTIO, 0x80, \ + struct vhost_dsp_topology) + #endif diff --git a/include/uapi/linux/vhost_types.h b/include/uapi/linux/vhost_types.h index c907290..898660d 100644 --- a/include/uapi/linux/vhost_types.h +++ b/include/uapi/linux/vhost_types.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -119,6 +120,12 @@ struct vhost_scsi_target { unsigned short reserved; }; +/* VHOST_DSP */ + +struct vhost_dsp_topology { + char name[NAME_MAX + 1]; +}; + /* Feature bits */ /* Log all write descriptors. Can be changed while device is active. */ #define VHOST_F_LOG_ALL 26 diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 6a2502e..950c817 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #define DPCM_MAX_BE_USERS 8 @@ -1589,10 +1590,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) @@ -3177,14 +3183,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; @@ -3204,6 +3202,19 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.pointer = soc_pcm_pointer; } + if (rtd->dai_link->no_pcm || rtd->dai_link->params) { + /* + * Usually in this case we also don't need to assign .ops + * callbacks, but in case of a "no PCM" pipeline, used by a VM + * we use the .prepare() hook to configure the hardware. + */ + if (playback) + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; + if (capture) + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; + goto out; + } + for_each_rtd_components(rtd, i, component) { const struct snd_soc_component_driver *drv = component->driver; diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index e96d8ff..90c772b 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -195,6 +195,7 @@ config SND_SOC_SOF tristate select SND_SOC_TOPOLOGY select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_SUPPORT + select VHOST if VHOST_SOF help This option is not user-selectable but automagically handled by 'select' statements at a higher level diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index b728d09..d947afc 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -14,6 +14,10 @@ ifdef CONFIG_SND_SOC_SOF_VIRTIO_FE snd-sof-objs += virtio-fe.o endif +ifdef CONFIG_VHOST_SOF +snd-sof-objs += vhost-be.o +endif + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index d0bf082..09f01ea 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include "sof-audio.h" #include "sof-priv.h" #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) @@ -222,6 +224,9 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto dbg_err; } + /* enable the vhost driver on this device */ + dsp_sof_dev_set(sdev); + /* init the IPC */ sdev->ipc = snd_sof_ipc_init(sdev); if (!sdev->ipc) { @@ -333,6 +338,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->vbe_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 8f52de0..fa8d3e2 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -14,6 +14,8 @@ #include #include +#include + #include "sof-priv.h" #include "sof-audio.h" #include "ops.h" @@ -451,6 +453,9 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) memcpy(&stream->posn, &posn, sizeof(posn)); + /* optionally update position for vBE */ + dsp_sof_update_guest_posn(sdev, &posn); + /* only inform ALSA for period_wakeup mode */ if (!stream->substream->runtime->no_period_wakeup) snd_sof_pcm_period_elapsed(stream->substream); diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 3005315..432e479 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -57,7 +57,7 @@ static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream /* * sof pcm period elapse work */ -static void sof_pcm_period_elapsed_work(struct work_struct *work) +void sof_pcm_period_elapsed_work(struct work_struct *work) { struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, @@ -91,7 +91,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) * To avoid sending IPC before the previous IPC is handled, we * schedule delayed work here to call the snd_pcm_period_elapsed(). */ - schedule_work(&spcm->stream[substream->stream].period_elapsed_work); + if (spcm->stream[substream->stream].substream) + schedule_work(&spcm->stream[substream->stream].period_elapsed_work); } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); @@ -748,6 +749,7 @@ static int sof_pcm_probe(struct snd_soc_component *component) /* load the default topology */ sdev->component = component; + sdev->card = component->card; tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, "%s/%s", diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index c410822..0898d17 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -8,6 +8,8 @@ // Author: Liam Girdwood // +#include + #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" @@ -247,6 +249,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* reset FW state */ sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; + dsp_sof_suspend(sdev); + return ret; } diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index b4c5fe2..20413ec 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -151,6 +151,7 @@ int sof_restore_pipelines(struct device *dev) struct snd_sof_dai *dai; struct sof_ipc_comp_dai *comp_dai; struct sof_ipc_cmd_hdr *hdr; + struct sof_ipc_buffer *buffer; int ret; /* restore pipeline components */ @@ -182,6 +183,14 @@ int sof_restore_pipelines(struct device *dev) pipeline = swidget->private; ret = sof_load_pipeline_ipc(dev, pipeline, &r); break; + + case snd_soc_dapm_buffer: + + buffer = swidget->private; + if (!buffer->size) + break; + + /* Fall through */ default: hdr = swidget->private; ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index e9a6091..0216196 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -39,6 +39,7 @@ struct snd_sof_pcm_stream { * active or not while suspending the stream */ bool suspend_ignored; + size_t guest_offset; }; /* ALSA SOF PCM device */ @@ -104,6 +105,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; @@ -213,4 +215,19 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, int sof_machine_register(struct snd_sof_dev *sdev, void *pdata); void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); +#if IS_ENABLED(CONFIG_VHOST_SOF) +int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction); +#else +static inline int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction) +{ + return 0; +} +#endif + #endif diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 861c91e..c3de463 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -57,6 +57,18 @@ /* The maximum number of components a virtio user vFE driver can use */ #define SOF_VIRTIO_MAX_UOS_COMPS 1000 +#define SOF_VIRTIO_COMP_ID_UNASSIGNED 0xffffffff + +/* + * in virtio iovec array: + * iovec[0]: the ipc message data between vFE and vBE + * iovec[1]: the ipc reply data between vFE and vBE + */ +#define SOF_VIRTIO_IPC_MSG 0 +#define SOF_VIRTIO_IPC_REPLY 1 + +/* Maximum supported number of VirtIO clients */ +#define SND_SOF_MAX_VFES BITS_PER_LONG /* DSP power state */ enum sof_dsp_power_states { @@ -368,6 +380,7 @@ struct snd_sof_dev { * can't use const */ struct snd_soc_component_driver plat_drv; + struct snd_soc_card *card; /* current DSP power state */ struct sof_dsp_power_state dsp_power_state; @@ -440,6 +453,9 @@ struct snd_sof_dev { /* VirtIO fields for host and guest */ atomic_t reset_count; + struct list_head vbe_list; + struct list_head connector_list; + unsigned long vfe_mask[DIV_ROUND_UP(SND_SOF_MAX_VFES, BITS_PER_LONG)]; /* DMA for Trace */ struct snd_dma_buffer dmatb; @@ -614,4 +630,6 @@ int intel_pcm_close(struct snd_sof_dev *sdev, int sof_machine_check(struct snd_sof_dev *sdev); +void sof_pcm_period_elapsed_work(struct work_struct *work); + #endif diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index e6e435f..d595fb6 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1328,6 +1328,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)); } @@ -1374,6 +1375,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) { @@ -1419,6 +1427,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; @@ -3138,6 +3156,15 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST; link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST; + /* + * set .no_pcm on VirtIO hosts for pseudo PCMs, used as anchors + * for guest pipeline linking + */ + if (link->stream_name && + (!strcmp(link->stream_name, "vm_fe_playback") || + !strcmp(link->stream_name, "vm_fe_capture"))) + link->no_pcm = true; + /* nothing more to do for FE dai links */ return 0; } @@ -3359,6 +3386,32 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, } /* + * In VirtIO case the host topology will contain a dummy PCM and a + * buffer at each location, where a partial guest topology will be + * attached. These dummy widgets shall not be sent to the DSP. We use + * them to identify and store VirtIO guest connection points. + */ + if (source_swidget->id == snd_soc_dapm_buffer) { + struct sof_ipc_buffer *buffer = source_swidget->private; + /* Is this a virtual playback buffer? */ + if (!buffer->size) { + ret = dsp_sof_add_conn(sdev, sink_swidget, + source_swidget, + SOF_IPC_STREAM_PLAYBACK); + goto err; + } + } else if (sink_swidget->id == snd_soc_dapm_buffer) { + struct sof_ipc_buffer *buffer = sink_swidget->private; + /* Is this a virtual capture buffer? */ + if (!buffer->size) { + ret = dsp_sof_add_conn(sdev, source_swidget, + sink_swidget, + SOF_IPC_STREAM_CAPTURE); + goto err; + } + } + + /* * Don't send routes whose sink widget is of type * output or out_drv to the DSP */ @@ -3625,7 +3678,7 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) /* VirtIO guests request topology from the host */ if (sdev->pdata->vfe) { fw = &vfe_fw; - ret = sof_ops(sdev)->request_topology(sdev, file, &vfe_fw); + ret = sof_ops(sdev)->request_topology(sdev, &vfe_fw); } else { ret = request_firmware(&fw, file, sdev->dev); } diff --git a/sound/soc/sof/vhost-be.c b/sound/soc/sof/vhost-be.c new file mode 100644 index 00000000..b3d7cfc --- /dev/null +++ b/sound/soc/sof/vhost-be.c @@ -0,0 +1,1094 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2017-2020 Intel Corporation. All rights reserved. + * + * Author: Libin Yang + * Luo Xionghu + * Liam Girdwood + * Guennadi Liakhovetski + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "sof-audio.h" +#include "sof-priv.h" +#include "ops.h" + +/* A connection of a guest pipeline into the host topology */ +struct dsp_pipeline_connect { + int host_pipeline_id; + int guest_pipeline_id; + int host_component_id; + int guest_component_id; + enum sof_ipc_stream_direction direction; + struct list_head list; +}; + +static const char dsp_pcm_name[] = "VHost PCM"; + +/* + * This function is used to find a BE substream. It uses the dai_link stream + * name for that. The current dai_link stream names are "vm_fe_playback" and + * "vm_fe_capture," which means only one Virtual Machine is supported and the VM + * only supports one playback pcm and one capture pcm. After we switch to the + * new topology, we can support multiple VMs and multiple PCM streams for each + * VM. This function may be abandoned after switching to the new topology. + * + * Note: if this function returns substream != NULL, then *rtd != NULL too (if + * rtd != NULL, of course). If it returns NULL, *rtd hasn't been changed. + */ +static struct snd_pcm_substream *dsp_sof_get_substream(struct snd_sof_dev *sdev, + struct snd_soc_pcm_runtime **rtd, int direction) +{ + struct snd_soc_card *card = sdev->card; + struct snd_soc_pcm_runtime *r; + + for_each_card_rtds(card, r) { + struct snd_pcm_substream *substream; + struct snd_pcm *pcm = r->pcm; + if (!pcm || !pcm->internal) + continue; + + /* Find a substream dedicated to the vFE. */ + substream = pcm->streams[direction].substream; + if (substream) { + struct snd_soc_dai_link *dai_link = r->dai_link; + + /* FIXME: replace hard-coded stream name */ + if (dai_link->stream_name && + (!strcmp(dai_link->stream_name, "vm_fe_playback") || + !strcmp(dai_link->stream_name, "vm_fe_capture"))) { + if (rtd) + *rtd = r; + return substream; + } + } + } + + return NULL; +} + +static struct snd_sof_pcm *dsp_sof_find_spcm_comp(struct snd_sof_dev *sdev, + unsigned int comp_id, + int *direction) +{ + return snd_sof_find_spcm_comp(sdev->component, comp_id, direction); +} + +/* + * Prepare hardware parameters, required for buffer allocation and PCM + * configuration + */ +static int dsp_sof_assemble_params(struct sof_ipc_pcm_params *pcm, + struct snd_pcm_hw_params *params) +{ + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS)->min = + pcm->params.channels; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE)->min = + pcm->params.rate; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES)->min = + pcm->params.host_period_bytes; + + hw_param_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)->min = + pcm->params.buffer.size; + + snd_mask_none(fmt); + switch (pcm->params.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S16); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S24); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_S32); + break; + case SOF_IPC_FRAME_FLOAT: + snd_mask_set(fmt, SNDRV_PCM_FORMAT_FLOAT); + break; + default: + return -EINVAL; + } + return 0; +} + +/* Handle SOF_IPC_STREAM_PCM_PARAMS IPC */ +static int dsp_sof_stream_hw_params(struct snd_sof_dev *sdev, + struct sof_ipc_pcm_params *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct snd_pcm_hw_params params; + int direction = pcm->params.direction; + int ret; + + /* find the proper substream */ + substream = dsp_sof_get_substream(sdev, NULL, direction); + if (!substream) + return -ENODEV; + + runtime = substream->runtime; + if (!runtime) { + dev_err(sdev->dev, "no runtime is available for hw_params\n"); + return -ENODEV; + } + + /* TODO: codec hw_params */ + + /* Use different stream_tag from FE. This is the real tag */ + dsp_sof_assemble_params(pcm, ¶ms); + + /* Allocate a duplicate of the guest buffer */ + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(¶ms)); + if (ret < 0) { + dev_err(sdev->dev, + "error %d: could not allocate %d bytes for PCM \"%s\"\n", + ret, params_buffer_bytes(¶ms), substream->pcm->name); + return ret; + } + + return snd_sof_pcm_platform_hw_params(sdev, substream, ¶ms, + &pcm->params); +} + +/* Allocate a runtime object and buffer pages */ +static int dsp_sof_pcm_open(struct snd_sof_dev *sdev, void *ipc_data) +{ + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + struct sof_ipc_pcm_params *pcm = ipc_data; + struct snd_pcm_runtime *runtime; + struct snd_sof_pcm *spcm; + u32 comp_id = pcm->comp_id; + size_t size; + int direction, ret; + + spcm = dsp_sof_find_spcm_comp(sdev, comp_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "%s(): no SPCM for comp %u\n", __func__, comp_id); + return -ENODEV; + } + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) { + dev_err(sdev->dev, "%s(): no substream for comp %u\n", __func__, comp_id); + return -ENODEV; + } + if (substream->ref_count > 0) + return -EBUSY; + substream->ref_count++; /* set it used */ + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) + return -ENOMEM; + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); + runtime->status = alloc_pages_exact(size, GFP_KERNEL); + if (!runtime->status) { + ret = -ENOMEM; + goto eruntime; + } + memset((void *)runtime->status, 0, size); + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); + runtime->control = alloc_pages_exact(size, GFP_KERNEL); + if (!runtime->control) { + dev_err(sdev->dev, "fail to alloc pages for runtime->control"); + ret = -ENOMEM; + goto estatus; + } + memset((void *)runtime->control, 0, size); + + init_waitqueue_head(&runtime->sleep); + init_waitqueue_head(&runtime->tsleep); + runtime->status->state = SNDRV_PCM_STATE_OPEN; + + substream->runtime = runtime; + substream->private_data = rtd; + rtd->dpcm[direction].runtime = runtime; + substream->stream = direction; + + substream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + substream->dma_buffer.dev.dev = sdev->dev; + + /* check with spcm exists or not */ + spcm->stream[direction].posn.host_posn = 0; + spcm->stream[direction].posn.dai_posn = 0; + spcm->stream[direction].substream = substream; + spcm->stream[direction].guest_offset = 0; + + /* TODO: codec open */ + + snd_sof_pcm_platform_open(sdev, substream); + + return 0; + +estatus: + free_pages_exact(runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); +eruntime: + kfree(runtime); + return ret; +} + +static void dsp_sof_stream_close(struct snd_sof_dev *sdev, int direction) +{ + struct snd_pcm_substream *substream = dsp_sof_get_substream(sdev, NULL, + direction); + if (!substream) + return; + + /* TODO: codec close */ + + substream->ref_count = 0; + if (substream->runtime) { + snd_sof_pcm_platform_close(sdev, substream); + + free_pages_exact(substream->runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + free_pages_exact(substream->runtime->control, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))); + kfree(substream->runtime); + substream->runtime = NULL; + } +} + +/* Handle the SOF_IPC_STREAM_PCM_FREE IPC */ +static int dsp_sof_pcm_close(struct snd_sof_dev *sdev, void *ipc_data) +{ + struct snd_sof_pcm *spcm; + struct sof_ipc_stream *stream; + int direction; + + stream = (struct sof_ipc_stream *)ipc_data; + + spcm = dsp_sof_find_spcm_comp(sdev, stream->comp_id, &direction); + if (!spcm) + return 0; + + dsp_sof_stream_close(sdev, direction); + + return 0; +} + +/* Copy audio data from DMA buffers for capture */ +static int dsp_sof_stream_capture(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + size_t data_size = req->size; + int ret; + + stream->guest_offset = req->offset; + + if (req->offset + data_size > runtime->dma_bytes) { + reply->size = 0; + ret = -ENOBUFS; + } else { + stream->guest_offset += data_size; + + memcpy(reply->data, runtime->dma_area + req->offset, data_size); + reply->size = data_size; + ret = 0; + } + + reply->error = ret; + + return ret; +} + +/* Copy audio data to DMA buffers for playback */ +static int dsp_sof_stream_playback(struct snd_sof_pcm_stream *stream, + struct snd_pcm_runtime *runtime, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + size_t data_size = req->size; + int ret; + + stream->guest_offset = req->offset; + + if (req->offset + data_size > runtime->dma_bytes) { + ret = -ENOBUFS; + } else { + stream->guest_offset += data_size; + + memcpy(runtime->dma_area + req->offset, req->data, data_size); + ret = 0; + } + + reply->error = ret; + reply->size = 0; + + return ret; +} + +/* Send or receive audio data */ +int dsp_sof_stream_data(struct dsp_sof_client *client, + struct dsp_sof_data_req *req, + struct dsp_sof_data_resp *reply) +{ + int direction; + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pcm *spcm = dsp_sof_find_spcm_comp(sdev, + req->comp_id, &direction); + struct snd_pcm_substream *substream = dsp_sof_get_substream(sdev, NULL, + direction); + + if (!spcm || !substream) { + reply->error = -ENODEV; + reply->size = 0; + return reply->error; + } + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + return dsp_sof_stream_playback(spcm->stream + direction, + substream->runtime, req, reply); + + return dsp_sof_stream_capture(spcm->stream + direction, + substream->runtime, req, reply); +} +EXPORT_SYMBOL_GPL(dsp_sof_stream_data); + +/* Handle the stream IPC */ +static int dsp_sof_ipc_stream(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr, void *reply_buf) +{ + struct sof_ipc_pcm_params *pcm; + struct sof_ipc_stream *stream; + struct snd_soc_pcm_runtime *rtd; + struct snd_pcm_substream *substream; + int ret = 0, direction, comp_id; + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + struct snd_soc_dpcm *dpcm; + + /* TODO validate host comp id range based on vm_id */ + + switch (cmd) { + case SOF_IPC_STREAM_PCM_PARAMS: + ret = dsp_sof_pcm_open(sdev, hdr); + if (ret < 0) + break; + pcm = container_of(hdr, struct sof_ipc_pcm_params, hdr); + ret = dsp_sof_stream_hw_params(sdev, pcm); + break; + case SOF_IPC_STREAM_TRIG_START: + stream = container_of(hdr, struct sof_ipc_stream, hdr); + comp_id = stream->comp_id; + if (!dsp_sof_find_spcm_comp(sdev, comp_id, &direction)) { + ret = -ENODEV; + break; + } + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) { + ret = -ENODEV; + break; + } + + /* Create an RTD, a CPU DAI when parsing aif_in */ + snd_soc_runtime_activate(rtd, direction); + snd_soc_dpcm_runtime_update(sdev->card, SND_SOC_DPCM_UPDATE_NEW_ONLY); + + dpcm = list_first_entry(&rtd->dpcm[direction].be_clients, + struct snd_soc_dpcm, list_be); + + if (list_empty(&rtd->dpcm[direction].be_clients)) + dev_warn(rtd->dev, "BE client list empty\n"); + else if (!dpcm->be) + dev_warn(rtd->dev, "No BE\n"); + else + dpcm->be->dpcm[direction].state = SND_SOC_DPCM_STATE_HW_PARAMS; + + ret = rtd->ops.prepare(substream); + if (ret < 0) + break; + snd_sof_pcm_platform_trigger(sdev, substream, + SNDRV_PCM_TRIGGER_START); + break; + case SOF_IPC_STREAM_PCM_FREE: + dsp_sof_pcm_close(sdev, hdr); + break; + } + + return ret; +} + +/* validate component IPC */ +static int dsp_sof_ipc_comp(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_ctrl_data *cdata = container_of(hdr, + struct sof_ipc_ctrl_data, rhdr.hdr); + + return cdata->comp_id < client->comp_id_begin || + cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; +} + +/* process PM IPC */ +static int dsp_sof_ipc_pm(struct dsp_sof_client *client, struct sof_ipc_cmd_hdr *hdr, + struct sof_vfe_ipc_power_resp *resp) +{ + struct snd_sof_dev *sdev = client->sdev; + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + struct sof_vfe_ipc_power_req *rq; + unsigned int reset_count; + int ret; + + switch (cmd) { + case SOF_IPC_PM_VFE_POWER_STATUS: + rq = container_of(hdr, struct sof_vfe_ipc_power_req, hdr); + if (rq->power) { + ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) + return ret; + } + + /* + * The DSP is runtime-PM active now for IPC processing, so + * .reset_count won't change + */ + reset_count = atomic_read(&sdev->reset_count); + resp->reply.hdr.size = sizeof(*resp); + resp->reply.hdr.cmd = SOF_IPC_GLB_PM_MSG | + SOF_IPC_PM_VFE_POWER_STATUS; + resp->reply.error = 0; + resp->reset_status = reset_count == client->reset_count ? + SOF_VIRTIO_IPC_RESET_NONE : SOF_VIRTIO_IPC_RESET_DONE; + + if (!rq->power) { + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + } + return 1; + } + + return 0; +} + +static int dsp_sof_error_reply(struct sof_ipc_reply *rhdr, unsigned int cmd, + int err) +{ + rhdr->hdr.size = sizeof(*rhdr); + rhdr->hdr.cmd = cmd; + rhdr->error = err; + + return err; +} + +int dsp_sof_add_conn(struct snd_sof_dev *sdev, + struct snd_sof_widget *w_host, + struct snd_sof_widget *w_guest, + enum sof_ipc_stream_direction direction) +{ + struct dsp_pipeline_connect *conn; + + if (w_host->pipeline_id == w_guest->pipeline_id) + return 0; + + conn = devm_kmalloc(sdev->dev, sizeof(*conn), GFP_KERNEL); + if (!conn) + return -ENOMEM; + + /* + * We'll need this mapping twice: first to overwrite a sink or source ID + * for SOF_IPC_TPLG_COMP_CONNECT, then to overwrite the scheduling + * component ID for SOF_IPC_TPLG_PIPE_NEW + */ + conn->host_pipeline_id = w_host->pipeline_id; + conn->guest_pipeline_id = w_guest->pipeline_id; + conn->host_component_id = w_host->comp_id; + conn->direction = direction; + + list_add_tail(&conn->list, &sdev->connector_list); + + return 0; +} + +/* Handle some special cases of the "new component" IPC */ +static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) +{ + struct sof_ipc_comp *comp = container_of(hdr, struct sof_ipc_comp, hdr); + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pcm *spcm, *last; + struct sof_ipc_comp_host *host; + struct dsp_pipeline_connect *conn; + + switch (comp->type) { + case SOF_COMP_VIRT_CON: + list_for_each_entry(conn, &sdev->connector_list, list) + if (conn->guest_pipeline_id == comp->pipeline_id) { + /* This ID will have to be overwritten */ + conn->guest_component_id = comp->id; + break; + } + + dsp_sof_error_reply(rhdr, hdr->cmd, 0); + + /* The firmware doesn't need this component */ + return 1; + case SOF_COMP_HOST: + /* + * TODO: below is a temporary solution. next step is + * to create a whole pcm stuff incluing substream + * based on Liam's suggestion. + */ + + /* + * let's create spcm in HOST ipc + * spcm should be created in pcm load, but there is no such ipc + * so we create it here. It is needed for the "period elapsed" + * IPC from the firmware, which will use the host ID to route + * the IPC back to the PCM. + */ + host = container_of(comp, struct sof_ipc_comp_host, comp); + spcm = kzalloc(sizeof(*spcm), GFP_KERNEL); + if (!spcm) + return -ENOMEM; + + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = + SOF_VIRTIO_COMP_ID_UNASSIGNED; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = + SOF_VIRTIO_COMP_ID_UNASSIGNED; + spcm->stream[host->direction].comp_id = host->comp.id; + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].posn.comp_id = + spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id; + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].posn.comp_id = + spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id; + INIT_WORK(&spcm->stream[host->direction].period_elapsed_work, + sof_pcm_period_elapsed_work); + last = list_last_entry(&sdev->pcm_list, struct snd_sof_pcm, list); + spcm->pcm.dai_id = last->pcm.dai_id + 1; + strncpy(spcm->pcm.pcm_name, dsp_pcm_name, + sizeof(spcm->pcm.pcm_name)); + list_add(&spcm->list, &sdev->pcm_list); + + client->reset_count = atomic_read(&sdev->reset_count); + break; + default: + break; + } + + return 0; +} + +/* Handle the "new pipeline" IPC: replace the scheduling sink ID */ +static int dsp_sof_ipc_tplg_pipe_new(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_pipe_new *pipeline = container_of(hdr, + struct sof_ipc_pipe_new, hdr); + struct snd_sof_dev *sdev = client->sdev; + struct dsp_pipeline_connect *conn; + + list_for_each_entry(conn, &sdev->connector_list, list) + if (pipeline->pipeline_id == conn->guest_pipeline_id) { + struct snd_sof_dai *dai; + + dai = snd_sof_find_dai_pipe(sdev, conn->host_pipeline_id); + if (!dai) { + dev_warn(sdev->dev, + "no DAI with pipe %u found\n", + conn->host_pipeline_id); + continue; + } + + /* Overwrite the scheduling sink ID with the DAI ID */ + pipeline->sched_id = dai->comp_dai.comp.id; + break; + } + + return 0; +} + +/* Handle the "connect components" IPC: replace the virtual component ID */ +static int dsp_sof_ipc_tplg_comp_connect(struct dsp_sof_client *client, + int vq_idx, struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_pipe_comp_connect *connect = container_of(hdr, + struct sof_ipc_pipe_comp_connect, hdr); + struct dsp_pipeline_connect *conn; + + list_for_each_entry(conn, &client->sdev->connector_list, list) { + if (conn->direction == SOF_IPC_STREAM_PLAYBACK && + connect->sink_id == conn->guest_component_id) { + /* Overwrite the sink ID with the actual mixer component ID */ + connect->sink_id = conn->host_component_id; + break; + } + + if (conn->direction == SOF_IPC_STREAM_CAPTURE && + connect->source_id == conn->guest_component_id) { + /* Overwrite the source ID with the actual demux component ID */ + connect->source_id = conn->host_component_id; + break; + } + } + + return 0; +} + +/* Read guest's topology file and send it back to the requester */ +static int dsp_sof_ipc_tplg_read(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_vfe_ipc_tplg_req *tplg = container_of(hdr, + struct sof_vfe_ipc_tplg_req, hdr); + struct sof_vfe_ipc_tplg_resp *partdata = reply_buf; + const struct firmware *fw = client->fw; + size_t to_copy, remainder; + + if (reply_sz <= sizeof(partdata->reply)) + return -ENOBUFS; + + if (!fw || fw->size <= tplg->offset) + return -EINVAL; + + remainder = fw->size - tplg->offset; + + partdata->reply.hdr.cmd = hdr->cmd; + /* + * Non-standard size use: it's the remaining firmware bytes, plus + * the header, that way the last part will contain a correct size + */ + partdata->reply.hdr.size = remainder + sizeof(partdata->reply); + + to_copy = min_t(size_t, reply_sz - sizeof(partdata->reply), + remainder); + + memcpy(partdata->data, fw->data + tplg->offset, to_copy); + + if (remainder == to_copy) { + release_firmware(fw); + client->fw = NULL; + } + + return 0; +} + +/* Send the next component ID to the guest */ +static int dsp_sof_ipc_tplg_comp_id(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + struct sof_vfe_ipc_tplg_resp *partdata = reply_buf; + + client->comp_id_begin = client->sdev->next_comp_id + + client->id * SOF_VIRTIO_MAX_UOS_COMPS; + client->comp_id_end = client->comp_id_begin + SOF_VIRTIO_MAX_UOS_COMPS; + + partdata->reply.hdr.cmd = hdr->cmd; + partdata->reply.hdr.size = sizeof(partdata->reply) + sizeof(u32); + *(u32 *)partdata->data = client->comp_id_begin; + + return 0; +} + +/* Handle topology IPC */ +static int dsp_sof_ipc_tplg(struct dsp_sof_client *client, int vq_idx, + struct sof_ipc_cmd_hdr *hdr, + void *reply_buf, size_t reply_sz) +{ + /* TODO validate host comp id range based on vm_id */ + u32 cmd = hdr->cmd & SOF_CMD_TYPE_MASK; + int ret; + + switch (cmd) { + case SOF_IPC_TPLG_COMP_NEW: + return dsp_sof_ipc_tplg_comp_new(client, vq_idx, hdr, + reply_buf); + case SOF_IPC_TPLG_PIPE_NEW: + return dsp_sof_ipc_tplg_pipe_new(client, vq_idx, hdr); + case SOF_IPC_TPLG_COMP_CONNECT: + return dsp_sof_ipc_tplg_comp_connect(client, vq_idx, hdr); + case SOF_IPC_TPLG_VFE_GET: + ret = dsp_sof_ipc_tplg_read(client, hdr, reply_buf, reply_sz); + return ret < 0 ? ret : 1; + case SOF_IPC_TPLG_VFE_COMP_ID: + ret = dsp_sof_ipc_tplg_comp_id(client, hdr, reply_buf, reply_sz); + return ret < 0 ? ret : 1; + } + + return 0; +} + +/* Call SOF core to send an IPC message to the DSP */ +static void sof_virtio_send_ipc(struct snd_sof_dev *sdev, void *ipc_data, + void *reply_buf, size_t count, + size_t reply_size) +{ + struct snd_sof_ipc *ipc = sdev->ipc; + struct sof_ipc_cmd_hdr *hdr = ipc_data; + struct sof_ipc_reply *rhdr = reply_buf; + int ret = sof_ipc_tx_message(ipc, hdr->cmd, ipc_data, count, + reply_buf, reply_size); + + if (ret < 0 && !rhdr->error) + rhdr->error = ret; +} + +/* Post-process SOF_IPC_STREAM_PCM_PARAMS */ +static int dsp_sof_ipc_stream_param_post(struct snd_sof_dev *sdev, + void *reply_buf) +{ + struct sof_ipc_pcm_params_reply *reply = reply_buf; + u32 comp_id = reply->comp_id; + int direction, ret; + struct snd_sof_pcm *spcm = dsp_sof_find_spcm_comp(sdev, comp_id, + &direction); + if (!spcm) + return -ENODEV; + + ret = snd_sof_ipc_pcm_params(sdev, spcm->stream[direction].substream, + reply); + if (ret < 0) + dev_err(sdev->dev, "error: got wrong reply for PCM %d\n", + spcm->pcm.pcm_id); + + return ret; +} + +/* Handle the stream IPC post-processing */ +static int dsp_sof_ipc_stream_codec(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_stream *stream = container_of(hdr, + struct sof_ipc_stream, hdr); + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + unsigned int i; + int direction; + + /* TODO validate host comp id range based on vm_id */ + + if (!dsp_sof_find_spcm_comp(sdev, stream->comp_id, &direction)) + return -ENODEV; + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + const struct snd_soc_dai_ops *ops = codec_dai->driver->ops; + + /* + * Now we are ready to trigger start. + * Let's unmute the codec firstly + */ + snd_soc_dai_digital_mute(codec_dai, 0, direction); + if (ops->trigger) { + int ret = ops->trigger(substream, + SNDRV_PCM_TRIGGER_START, + codec_dai); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static int dsp_sof_ipc_stream_stop(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr) +{ + struct sof_ipc_stream *stream = container_of(hdr, + struct sof_ipc_stream, hdr); + struct snd_pcm_substream *substream; + struct snd_soc_pcm_runtime *rtd; + int direction, comp_id = stream->comp_id; + unsigned int i; + + if (!dsp_sof_find_spcm_comp(sdev, comp_id, &direction)) + return -ENODEV; + + substream = dsp_sof_get_substream(sdev, &rtd, direction); + if (!substream) + return -ENODEV; + + for (i = 0; i < rtd->num_codecs; i++) { + struct snd_soc_dai *codec_dai = rtd->codec_dais[i]; + const struct snd_soc_dai_ops *ops = codec_dai->driver->ops; + + if (ops->trigger) { + int ret = ops->trigger(substream, + SNDRV_PCM_TRIGGER_STOP, + codec_dai); + if (ret < 0) { + dev_err(codec_dai->dev, + "trigger stop fails\n"); + return ret; + } + } + } + + snd_sof_pcm_platform_trigger(sdev, substream, + SNDRV_PCM_TRIGGER_STOP); + snd_soc_dpcm_runtime_update(sdev->card, + SND_SOC_DPCM_UPDATE_OLD_ONLY); + snd_soc_runtime_deactivate(rtd, direction); + + return 0; +} + +/* Handle an IPC reply */ +static int dsp_sof_ipc_post(struct snd_sof_dev *sdev, + struct sof_ipc_cmd_hdr *hdr, void *reply_buf) +{ + struct sof_ipc_reply *rhdr = reply_buf; + int ret; + + switch (hdr->cmd) { + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS: + if (rhdr->error < 0) + break; + return dsp_sof_ipc_stream_param_post(sdev, reply_buf); + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_TRIG_START: + if (rhdr->error < 0) + break; + /* setup the codec */ + return dsp_sof_ipc_stream_codec(sdev, hdr); + case SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_TRIG_STOP: + ret = dsp_sof_ipc_stream_stop(sdev, hdr); + return rhdr->error < 0 ? rhdr->error : ret; + } + + return rhdr->error; +} + +/* Forward an IPC message from a guest to the DSP */ +int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, + void *ipc_buf, void *reply_buf, + size_t count, size_t reply_sz) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_cmd_hdr *hdr = ipc_buf; + struct sof_ipc_reply *rhdr = reply_buf; + u32 type; + int ret; + + /* validate IPC */ + if (!count) { + dev_err(sdev->dev, "error: guest IPC size is 0\n"); + return -EINVAL; + } + + type = hdr->cmd & SOF_GLB_TYPE_MASK; + rhdr->error = 0; + + /* validate the ipc */ + switch (type) { + case SOF_IPC_GLB_COMP_MSG: + ret = dsp_sof_ipc_comp(client, hdr); + if (ret < 0) + goto err; + break; + case SOF_IPC_GLB_STREAM_MSG: + ret = dsp_sof_ipc_stream(sdev, hdr, reply_buf); + if (ret < 0) { + dev_err(sdev->dev, "STREAM IPC 0x%x failed %d!\n", + hdr->cmd, ret); + goto err; + } + break; + case SOF_IPC_GLB_PM_MSG: + ret = dsp_sof_ipc_pm(client, hdr, reply_buf); + if (!ret) + break; + if (ret < 0) + goto err; + return 0; + case SOF_IPC_GLB_DAI_MSG: + /* + * After we use the new topology solution for FE, + * we will not touch DAI anymore. + */ + break; + case SOF_IPC_GLB_TPLG_MSG: + ret = dsp_sof_ipc_tplg(client, vq_idx, hdr, reply_buf, + reply_sz); + if (!ret) + break; + if (ret < 0) + goto err; + return 0; + case SOF_IPC_GLB_TRACE_MSG: + /* Trace should be initialized in SOS, skip FE requirement */ + return 0; + default: + dev_warn(sdev->dev, "unhandled IPC 0x%x!\n", hdr->cmd); + break; + } + + /* now send the IPC */ + sof_virtio_send_ipc(sdev, ipc_buf, reply_buf, count, reply_sz); + + /* For some IPCs, the reply needs to be handled */ + ret = dsp_sof_ipc_post(sdev, hdr, reply_buf); + if (ret < 0) + dev_err(sdev->dev, "err: failed to send %u bytes virtio IPC 0x%x: %d\n", + hdr->size, hdr->cmd, ret); + + return ret; + +err: + return dsp_sof_error_reply(rhdr, hdr->cmd, ret); +} +EXPORT_SYMBOL_GPL(dsp_sof_ipc_fwd); + +int dsp_sof_set_tplg(struct dsp_sof_client *client, + const struct vhost_dsp_topology *tplg) +{ + struct snd_sof_dev *sdev = client->sdev; + struct snd_sof_pdata *plat_data = sdev->pdata; + char *path; + int ret; + + path = kasprintf(GFP_KERNEL, "%s/%s", plat_data->tplg_filename_prefix, + tplg->name); + if (!path) + return -ENOMEM; + + ret = request_firmware(&client->fw, path, sdev->dev); + if (ret < 0) + dev_err(sdev->dev, + "error: request VFE topology %s failed: %d\n", + tplg->name, ret); + kfree(path); + + return ret; +} +EXPORT_SYMBOL_GPL(dsp_sof_set_tplg); + +void dsp_sof_suspend(struct snd_sof_dev *sdev) +{ + struct snd_sof_pcm *spcm, *next; + + list_for_each_entry_safe(spcm, next, &sdev->pcm_list, list) + if (!strcmp(dsp_pcm_name, spcm->pcm.pcm_name)) { + list_del(&spcm->list); + dsp_sof_stream_close(sdev, SNDRV_PCM_STREAM_PLAYBACK); + dsp_sof_stream_close(sdev, SNDRV_PCM_STREAM_CAPTURE); + kfree(spcm); + } +} + +/* A VM instance has closed the miscdevice */ +void dsp_sof_client_release(struct dsp_sof_client *client) +{ + bitmap_release_region(client->sdev->vfe_mask, client->id, 0); + + list_del(&client->list); + + kfree(client); +} +EXPORT_SYMBOL_GPL(dsp_sof_client_release); + +/* A new VM instance has opened the miscdevice */ +struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, + struct vhost_dsp *dsp) +{ + int id = bitmap_find_free_region(sdev->vfe_mask, SND_SOF_MAX_VFES, 0); + struct dsp_sof_client *client; + + if (id < 0) + return NULL; + + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + bitmap_release_region(sdev->vfe_mask, id, 0); + return NULL; + } + + client->sdev = sdev; + client->id = id; + client->vhost = dsp; + + /* + * link to sdev->vbe_list + * Maybe virtio_miscdev managing the list is more reasonable. + * Let's use sdev to manage the FE audios now. + * FIXME: protect the list. + */ + list_add(&client->list, &sdev->vbe_list); + + return client; +} +EXPORT_SYMBOL_GPL(dsp_sof_client_add); + +/* The struct snd_sof_dev instance, that VirtIO guests will be using */ +static struct snd_sof_dev *vhost_sof_dev; +static const struct sof_vhost_ops *vhost_ops; + +/* Find a client by component ID */ +static struct dsp_sof_client *dsp_sof_comp_to_client(struct snd_sof_dev *sdev, + int comp_id) +{ + struct dsp_sof_client *client; + + list_for_each_entry(client, &sdev->vbe_list, list) + if (comp_id < client->comp_id_end && + comp_id >= client->comp_id_begin) + return client; + + return NULL; +} + +/* Called from the position update IRQ thread */ +int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, + struct sof_ipc_stream_posn *posn) +{ + struct dsp_sof_client *client = dsp_sof_comp_to_client(sdev, + posn->comp_id); + + if (!client || !vhost_ops) + return -ENODEV; + + return vhost_ops->update_posn(client->vhost, posn); +} + +/* The vhost driver is loaded */ +struct device *dsp_sof_dev_init(const struct sof_vhost_ops *ops) +{ + if (!vhost_sof_dev) + return NULL; + + bitmap_zero(vhost_sof_dev->vfe_mask, SND_SOF_MAX_VFES); + + vhost_ops = ops; + + return vhost_sof_dev->dev; +} +EXPORT_SYMBOL_GPL(dsp_sof_dev_init); + +/* This SOF device will be used for VirtIO */ +void dsp_sof_dev_set(struct snd_sof_dev *sdev) +{ + INIT_LIST_HEAD(&sdev->connector_list); + vhost_sof_dev = sdev; +} From patchwork Thu Mar 12 14:44:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193314 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5E6B5C10DCE for ; Thu, 12 Mar 2020 14:53:40 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id DC0CB20674 for ; Thu, 12 Mar 2020 14:53:39 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="EWfpek40" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org DC0CB20674 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 40D8E1734; Thu, 12 Mar 2020 15:52:48 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 40D8E1734 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024818; bh=r42gI+C3gHbq36IerjFijj+kBdhUVsdh64o3hccgThI=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=EWfpek407rYgWoNPtQZkutqhshEKkWdMs6eEXs+CXMwn4bUvrb0KSgvsEFAMRJW6x /2hop5e+iwxz0OzbUtW9y01kdkSrzpwn0OGuqJRuLec2XOM3CC6DbkR70RwSWbr7c/ XyUnP8IB8VebpN8rV6AeyKToEypGH1lC8C9RurSg= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id ACD6EF80362; Thu, 12 Mar 2020 15:45:32 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 63883F80323; Thu, 12 Mar 2020 15:45:21 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id B3B26F802A2; Thu, 12 Mar 2020 15:44:58 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz B3B26F802A2 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:52 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957710" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:50 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 11/14] vhost: add an SOF DSP driver Date: Thu, 12 Mar 2020 15:44:26 +0100 Message-Id: <20200312144429.17959-12-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" The SOF DSP vhost driver consists of two parts: a sound and a vhost part. This patch implements the vhost part of the driver. It handles QEMU communication with the vhost misc device and virtual queues to any VirtIO guests. Signed-off-by: Guennadi Liakhovetski --- drivers/vhost/Kconfig | 7 + drivers/vhost/Makefile | 5 + drivers/vhost/dsp.c | 728 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 740 insertions(+) create mode 100644 drivers/vhost/dsp.c diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig index 3d03ccb..b9f3071 100644 --- a/drivers/vhost/Kconfig +++ b/drivers/vhost/Kconfig @@ -34,6 +34,13 @@ config VHOST_VSOCK To compile this driver as a module, choose M here: the module will be called vhost_vsock. +config VHOST_SOF + bool "Vhost SOF driver" + default n + ---help--- + SOF vhost VirtIO driver. It exports the same IPC interface, as the + one, used for DSP communication, to Linux VirtIO guests. + config VHOST tristate ---help--- diff --git a/drivers/vhost/Makefile b/drivers/vhost/Makefile index 6c6df24..1914561 100644 --- a/drivers/vhost/Makefile +++ b/drivers/vhost/Makefile @@ -10,4 +10,9 @@ vhost_vsock-y := vsock.o obj-$(CONFIG_VHOST_RING) += vringh.o +ifdef CONFIG_VHOST_SOF +obj-$(CONFIG_SND_SOC_SOF) += vhost_sof.o +vhost_sof-y := dsp.o +endif + obj-$(CONFIG_VHOST) += vhost.o diff --git a/drivers/vhost/dsp.c b/drivers/vhost/dsp.c new file mode 100644 index 00000000..205ae8c --- /dev/null +++ b/drivers/vhost/dsp.c @@ -0,0 +1,728 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2019-2020 Intel Corporation. All rights reserved. + * + * Author: Guennadi Liakhovetski + * + * vhost-SOF VirtIO interface + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "vhost.h" + +#define VHOST_DSP_FEATURES VHOST_FEATURES +#define VHOST_DSP_BATCH 64 +#define VHOST_DSP_WEIGHT 0x80000 +#define VHOST_DSP_PKT_WEIGHT 256 + +struct vhost_dsp_virtqueue { + struct vhost_virtqueue vq; +}; + +struct snd_sof_dev; + +struct dsp_sof_client; +struct vhost_dsp { + struct vhost_dev dev; + struct vhost_dsp_virtqueue vqs[SOF_VIRTIO_NUM_OF_VQS]; + struct vhost_work work; + struct vhost_virtqueue *vq_p[SOF_VIRTIO_NUM_OF_VQS]; + + bool active; + + spinlock_t posn_lock; /* Protects posn_list */ + struct list_head posn_list; + struct list_head posn_buf_list; + + u8 ipc_buf[SOF_IPC_MSG_MAX_SIZE]; + u8 reply_buf[SOF_IPC_MSG_MAX_SIZE]; + + union { + struct dsp_sof_data_req data_req; + struct dsp_sof_data_resp data_resp; + }; + + struct dsp_sof_client *snd; +}; + +/* A stream position message, waiting to be sent to a guest */ +struct vhost_dsp_posn { + struct list_head list; + struct sof_ipc_stream_posn posn; +}; + +/* A guest buffer, waiting to be filled with a stream position message */ +struct vhost_dsp_iovec { + struct list_head list; + int head; +}; + +/* A guest is booting */ +static int vhost_dsp_activate(struct vhost_dsp *dsp) +{ + unsigned int i; + int ret = 0; + + mutex_lock(&dsp->dev.mutex); + + /* Wait until all the VirtQueues have been initialised */ + if (!dsp->active) { + for (i = 0; i < ARRAY_SIZE(dsp->vqs); i++) { + struct vhost_virtqueue *vq = &dsp->vqs[i].vq; + + /* .private_data is required != NULL */ + vq->private_data = dsp->vqs + i; + /* needed for re-initialisation upon guest reboot */ + ret = vhost_vq_init_access(vq); + if (ret) + vq_err(vq, + "%s(): error %d initialising vq #%d\n", + __func__, ret, i); + } + if (!ret) + dsp->active = true; + } + + mutex_unlock(&dsp->dev.mutex); + + return ret; +} + +/* A guest is powered off or reset */ +static void vhost_dsp_deactivate(struct vhost_dsp *dsp) +{ + unsigned int i; + + mutex_lock(&dsp->dev.mutex); + + if (dsp->active) { + struct vhost_dsp_iovec *buf, *next; + unsigned long flags; + + dsp->active = false; + + spin_lock_irqsave(&dsp->posn_lock, flags); + list_for_each_entry_safe(buf, next, &dsp->posn_buf_list, list) { + list_del(&buf->list); + kfree(buf); + } + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + /* signal, that we're inactive */ + for (i = 0; i < ARRAY_SIZE(dsp->vqs); i++) + dsp->vqs[i].vq.private_data = NULL; + } + + mutex_unlock(&dsp->dev.mutex); +} + +/* No special features at the moment */ +static int vhost_dsp_set_features(struct vhost_dsp *dsp, u64 features) +{ + struct vhost_virtqueue *vq; + unsigned int i; + + if (features & ~VHOST_DSP_FEATURES) + return -EOPNOTSUPP; + + mutex_lock(&dsp->dev.mutex); + + if ((features & (1 << VHOST_F_LOG_ALL)) && + !vhost_log_access_ok(&dsp->dev)) { + mutex_unlock(&dsp->dev.mutex); + return -EFAULT; + } + + for (i = 0; i < SOF_VIRTIO_NUM_OF_VQS; i++) { + vq = &dsp->vqs[i].vq; + + mutex_lock(&vq->mutex); + vq->acked_features = features; + mutex_unlock(&vq->mutex); + } + + mutex_unlock(&dsp->dev.mutex); + + return 0; +} + +/* .ioctl(): we only use VHOST_SET_RUNNING in a not-default way */ +static long vhost_dsp_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + struct vhost_dsp *dsp = filp->private_data; + void __user *argp = (void __user *)arg; + struct vhost_dsp_topology tplg; + u64 __user *featurep = argp; + u64 features; + int start; + long ret; + + switch (ioctl) { + case VHOST_GET_FEATURES: + features = VHOST_DSP_FEATURES; + if (copy_to_user(featurep, &features, sizeof features)) + return -EFAULT; + return 0; + case VHOST_SET_FEATURES: + if (copy_from_user(&features, featurep, sizeof features)) + return -EFAULT; + return vhost_dsp_set_features(dsp, features); + case VHOST_GET_BACKEND_FEATURES: + features = 0; + if (copy_to_user(featurep, &features, sizeof(features))) + return -EFAULT; + return 0; + case VHOST_SET_BACKEND_FEATURES: + if (copy_from_user(&features, featurep, sizeof(features))) + return -EFAULT; + if (features) + return -EOPNOTSUPP; + return 0; + case VHOST_RESET_OWNER: + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_check_owner(&dsp->dev); + if (!ret) { + struct vhost_umem *umem = vhost_dev_reset_owner_prepare(); + if (!umem) { + ret = -ENOMEM; + } else { + vhost_dev_stop(&dsp->dev); + vhost_dev_reset_owner(&dsp->dev, umem); + } + } + mutex_unlock(&dsp->dev.mutex); + return ret; + case VHOST_SET_OWNER: + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_set_owner(&dsp->dev); + mutex_unlock(&dsp->dev.mutex); + return ret; + case VHOST_SET_RUNNING: + if (copy_from_user(&start, argp, sizeof(start))) + return -EFAULT; + + if (start) + return vhost_dsp_activate(dsp); + + vhost_dsp_deactivate(dsp); + return 0; + case VHOST_DSP_SET_GUEST_TPLG: + if (copy_from_user(&tplg, argp, sizeof(tplg))) + return -EFAULT; + return dsp_sof_set_tplg(dsp->snd, &tplg); + } + + mutex_lock(&dsp->dev.mutex); + ret = vhost_dev_ioctl(&dsp->dev, ioctl, argp); + if (ret == -ENOIOCTLCMD) + ret = vhost_vring_ioctl(&dsp->dev, ioctl, argp); + mutex_unlock(&dsp->dev.mutex); + + return ret; +} + +#ifdef CONFIG_COMPAT +static long vhost_dsp_compat_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + return vhost_dsp_ioctl(filp, ioctl, (unsigned long)compat_ptr(arg)); +} +#endif + +static ssize_t vhost_dsp_chr_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct file *filp = iocb->ki_filp; + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + int noblock = filp->f_flags & O_NONBLOCK; + + return vhost_chr_read_iter(dev, to, noblock); +} + +static ssize_t vhost_dsp_chr_write_iter(struct kiocb *iocb, + struct iov_iter *from) +{ + struct file *filp = iocb->ki_filp; + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + + return vhost_chr_write_iter(dev, from); +} + +static __poll_t vhost_dsp_chr_poll(struct file *filp, poll_table *wait) +{ + struct vhost_dsp *dsp = filp->private_data; + struct vhost_dev *dev = &dsp->dev; + + return vhost_chr_poll(filp, dev, wait); +} + +/* IPC message from a guest */ +static void handle_ipc_cmd_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + int vq_idx = SOF_VIRTIO_IPC_CMD_VQ; + size_t total_len = 0; + + /* IPC message from the guest */ + mutex_lock(&vq->mutex); + + /* notifications must be disabled while handling the queue */ + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + struct iov_iter iov_iter; + size_t len, nbytes; + unsigned int out, in, i; + size_t iov_offset, iov_count; + /* IPC command from FE to DSP */ + int head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL), ret; + if (head < 0) + break; + + /* Nothing new? Wait for eventfd to tell us they refilled. */ + if (head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + break; + } + + if (in != out) + /* We expect in == out and usually == 1 */ + continue; + + iov_offset = out; + iov_count = out; + + for (i = 0; i < iov_count; i++) { + struct sof_ipc_reply *rhdr = (struct sof_ipc_reply *)dsp->reply_buf; + size_t to_copy; + + len = vq->iov[i].iov_len; + + if (len > sizeof(dsp->ipc_buf)) { + vq_err(vq, + "%s(): head %d out %d in %d len %zd\n", + __func__, head, out, in, len); + continue; + } + + total_len += len; + + iov_iter_init(&iov_iter, WRITE, vq->iov + i, 1, len); + + nbytes = copy_from_iter(dsp->ipc_buf, len, &iov_iter); + if (nbytes != len) { + vq_err(vq, "Expected %zu bytes for IPC, got %zu bytes\n", + len, nbytes); + continue; + } + + /* Process the IPC payload */ + ret = dsp_sof_ipc_fwd(dsp->snd, vq_idx, dsp->ipc_buf, + dsp->reply_buf, len, + vq->iov[iov_offset + i].iov_len); + if (ret < 0) { + struct sof_ipc_cmd_hdr *hdr = + (struct sof_ipc_cmd_hdr *)dsp->ipc_buf; + vq_err(vq, + "%s(): IPC 0x%x failed with error %d\n", + __func__, hdr->cmd, ret); + } + + to_copy = min_t(size_t, sizeof(dsp->reply_buf), + rhdr->hdr.size); + + iov_iter_init(&iov_iter, READ, vq->iov + iov_offset + i, + 1, to_copy); + if (copy_to_iter(dsp->reply_buf, to_copy, &iov_iter) > 0) + /* Return any response */ + vhost_add_used_and_signal(vq->dev, vq, head, to_copy); + } + } + + mutex_unlock(&vq->mutex); +} + +/* Try to send a position update buffer to the guest */ +static void vhost_dsp_fill_posn_vqbuf(struct vhost_dsp *dsp) +{ + struct vhost_virtqueue *vq = &dsp->vqs[SOF_VIRTIO_POSN_VQ].vq; + struct iov_iter iov_iter; + struct vhost_dsp_iovec *buf; + struct vhost_dsp_posn *entry; + unsigned long flags; + + spin_lock_irqsave(&dsp->posn_lock, flags); + + if (list_empty(&dsp->posn_list)) { + /* + * This is the normal path, when called from + * handle_posn_kick(): usually at that time we don't have a + * position update waiting yet + */ + spin_unlock_irqrestore(&dsp->posn_lock, flags); + return; + } + + if (list_empty(&dsp->posn_buf_list)) { + vq_err(vq, "%s(): no vq descriptors\n", __func__); + spin_unlock_irqrestore(&dsp->posn_lock, flags); + return; + } + + buf = list_first_entry(&dsp->posn_buf_list, + struct vhost_dsp_iovec, list); + list_del(&buf->list); + + entry = list_first_entry(&dsp->posn_list, + struct vhost_dsp_posn, list); + list_del(&entry->list); + + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + /* Take the lock and send the buffer */ + mutex_lock(&vq->mutex); + iov_iter_init(&iov_iter, READ, vq->iov, 1, sizeof(entry->posn)); + if (copy_to_iter(&entry->posn, sizeof(entry->posn), &iov_iter) > 0) + /* + * Actually the last parameter for vhost_add_used_and_signal() + * should be "sizeof(*posn)," but that didn't work + */ + vhost_add_used_and_signal(vq->dev, vq, buf->head, 0); + mutex_unlock(&vq->mutex); + + kfree(buf); + kfree(entry); +} + +/* Handle kick on the data VirtQ */ +static void handle_data_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + + mutex_lock(&vq->mutex); + + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + struct iov_iter iov_iter; + unsigned int out, in, i; + int head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL); + if (head < 0) + break; + + /* Nothing new? Wait for eventfd to tell us they refilled. */ + if (head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + break; + } + + if (in != out) + /* We expect in == out and usually == 1 */ + continue; + + for (i = 0; i < out; i++) { + u8 _req[HDR_SIZE_REQ]; + u8 _resp[HDR_SIZE_RESP]; + struct dsp_sof_data_resp *resp; + struct dsp_sof_data_req *req; + size_t to_copy, nbytes, len = vq->iov[i].iov_len; + int ret; + + if (len > sizeof(dsp->data_req) || len < HDR_SIZE_REQ) { + vq_err(vq, + "%s(): head %d out %d in %d len %zd\n", + __func__, head, out, in, len); + continue; + } + + iov_iter_init(&iov_iter, WRITE, vq->iov + i, 1, len); + + if (len > HDR_SIZE_REQ) { + /* playback */ + req = &dsp->data_req; + resp = (struct dsp_sof_data_resp *)_resp; + } else { + /* capture */ + req = (struct dsp_sof_data_req *)_req; + resp = &dsp->data_resp; + } + + nbytes = copy_from_iter(req, len, &iov_iter); + if (nbytes != len) { + vq_err(vq, "Expected %zu bytes for IPC, got %zu bytes\n", + len, nbytes); + continue; + } + + /* Copy data to or from the audio buffer */ + ret = dsp_sof_stream_data(dsp->snd, req, resp); + if (ret < 0) { + vq_err(vq, "Error %d copying data\n", ret); + continue; + } + + to_copy = resp->size + HDR_SIZE_RESP; + + iov_iter_init(&iov_iter, READ, vq->iov + out + i, + 1, to_copy); + if (copy_to_iter(resp, to_copy, &iov_iter) > 0) + vhost_add_used_and_signal(vq->dev, vq, head, to_copy); + } + } + + mutex_unlock(&vq->mutex); +} + +/* A new position update buffer from the guest */ +static void handle_posn_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_dsp *dsp = container_of(vq->dev, struct vhost_dsp, dev); + struct vhost_dsp_iovec *buf; + unsigned int out, in; + unsigned long flags; + bool free = true, enable = true; + + /* Queue the buffer for future position updates from the DSP */ + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + return; + + mutex_lock(&vq->mutex); + + vhost_disable_notify(&dsp->dev, vq); + + for (;;) { + /* A posn descriptor should have 1 "in" and 0 "out" buffers */ + buf->head = vhost_get_vq_desc(vq, vq->iov, ARRAY_SIZE(vq->iov), + &out, &in, NULL, NULL); + + if (buf->head < 0) { + vq_err(vq, "%s(): no vq descriptors: %d\n", + __func__, buf->head); + break; + } + + if (buf->head == vq->num) { + if (unlikely(vhost_enable_notify(&dsp->dev, vq))) { + vhost_disable_notify(&dsp->dev, vq); + continue; + } + enable = false; + break; + } + + if (unlikely(out)) + vq_err(vq, + "%s(): position update has %d outgoing buffers!\n", + __func__, out); + + if (unlikely(vq->iov[out].iov_len != + sizeof(struct sof_ipc_stream_posn))) + vq_err(vq, "%s(): position update has wrong size %d!\n", + __func__, out); + + if (!in) { + /* This queue should only contain "in" buffers */ + vq_err(vq, "%s(): no input buffers!\n", __func__); + break; + } + + spin_lock_irqsave(&dsp->posn_lock, flags); + list_add_tail(&buf->list, &dsp->posn_buf_list); + spin_unlock_irqrestore(&dsp->posn_lock, flags); + + free = false; + break; + } + + if (enable) + vhost_enable_notify(&dsp->dev, vq); + + mutex_unlock(&vq->mutex); + + if (free) + kfree(buf); + else + /* Try to send immediately if a position update is pending */ + vhost_dsp_fill_posn_vqbuf(dsp); +} + +static void vhost_dsp_posn_work(struct vhost_work *work) +{ + struct vhost_dsp *dsp = container_of(work, struct vhost_dsp, work); + + /* + * If there is an available VQ buffer, notify immediately. This is the + * normal case, since the guest pre-queues position update VQ buffers. + */ + vhost_dsp_fill_posn_vqbuf(dsp); +} + +static int vhost_dsp_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *misc = filp->private_data; + struct snd_sof_dev *sdev = dev_get_drvdata(misc->parent); + struct vhost_dsp *dsp = kvmalloc(sizeof(*dsp), + GFP_KERNEL | __GFP_RETRY_MAYFAIL); + unsigned int i; + + if (!dsp) + return -ENOMEM; + + dsp->snd = dsp_sof_client_add(sdev, dsp); + if (!dsp->snd) { + kvfree(dsp); + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(dsp->vq_p); i++) + dsp->vq_p[i] = &dsp->vqs[i].vq; + + dsp->vqs[SOF_VIRTIO_IPC_CMD_VQ].vq.handle_kick = handle_ipc_cmd_kick; + dsp->vqs[SOF_VIRTIO_POSN_VQ].vq.handle_kick = handle_posn_kick; + dsp->vqs[SOF_VIRTIO_DATA_VQ].vq.handle_kick = handle_data_kick; + /* + * TODO: do we ever want to support multiple guest machines per DSP, if + * not, we might as well perform all allocations when registering the + * misc device. + */ + INIT_LIST_HEAD(&dsp->posn_list); + INIT_LIST_HEAD(&dsp->posn_buf_list); + spin_lock_init(&dsp->posn_lock); + dsp->active = false; + vhost_work_init(&dsp->work, vhost_dsp_posn_work); + + vhost_dev_init(&dsp->dev, dsp->vq_p, SOF_VIRTIO_NUM_OF_VQS, + UIO_MAXIOV + VHOST_DSP_BATCH, + VHOST_DSP_PKT_WEIGHT, VHOST_DSP_WEIGHT); + + /* Overwrite file private data */ + filp->private_data = dsp; + + return 0; +} + +/* + * The device is closed by QEMU when the client driver is unloaded or the guest + * is shut down + */ +static int vhost_dsp_release(struct inode *inode, struct file *filp) +{ + struct vhost_dsp *dsp = filp->private_data; + + vhost_work_flush(&dsp->dev, &dsp->work); + vhost_dev_cleanup(&dsp->dev); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_POSN_VQ].vq.poll); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_IPC_CMD_VQ].vq.poll); + vhost_poll_flush(&dsp->vqs[SOF_VIRTIO_DATA_VQ].vq.poll); + + dsp_sof_client_release(dsp->snd); + + kvfree(dsp); + + return 0; +} + +static const struct file_operations vhost_dsp_fops = { + .owner = THIS_MODULE, + .release = vhost_dsp_release, + .read_iter = vhost_dsp_chr_read_iter, + .write_iter = vhost_dsp_chr_write_iter, + .poll = vhost_dsp_chr_poll, + .unlocked_ioctl = vhost_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vhost_dsp_compat_ioctl, +#endif + .open = vhost_dsp_open, + .llseek = noop_llseek, +}; + +static struct miscdevice vhost_dsp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "vhost-dsp", + .fops = &vhost_dsp_fops, +}; + +/* Always called from an interrupt thread context */ +static int dsp_sof_update_posn(struct vhost_dsp *dsp, + struct sof_ipc_stream_posn *posn) +{ + struct vhost_dsp_posn *entry; + + if (!dsp->active) + return 0; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + memcpy(&entry->posn, posn, sizeof(entry->posn)); + + /* + * Save the position update msg and send it when a vq buffer becomes + * available. + */ + spin_lock_irq(&dsp->posn_lock); + list_add_tail(&entry->list, &dsp->posn_list); + spin_unlock_irq(&dsp->posn_lock); + + /* posn update for guest */ + vhost_work_queue(&dsp->dev, &dsp->work); + + return 0; +} + +static struct sof_vhost_ops vhost_dsp_ops = { + .update_posn = dsp_sof_update_posn, +}; + +static int __init dsp_sof_init(void) +{ + vhost_dsp_misc.parent = dsp_sof_dev_init(&vhost_dsp_ops); + if (!vhost_dsp_misc.parent) + return -ENODEV; + + return misc_register(&vhost_dsp_misc); +} + +static void __exit dsp_sof_exit(void) +{ + misc_deregister(&vhost_dsp_misc); +} + +module_init(dsp_sof_init); +module_exit(dsp_sof_exit); + +MODULE_VERSION("0.9"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Guennadi Liakhovetski"); +MODULE_DESCRIPTION("Host kernel accelerator for virtio sound"); From patchwork Thu Mar 12 14:44:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193315 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE, SPF_PASS, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id F1A23C10DCE for ; Thu, 12 Mar 2020 14:52:21 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7B7D4206FA for ; Thu, 12 Mar 2020 14:52:21 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="HxmVH/mg" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 7B7D4206FA Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=linux.intel.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id DA1E41729; Thu, 12 Mar 2020 15:51:29 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz DA1E41729 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584024739; bh=9gxDrU3SPuO5ifFKv4Zf1xO0dXSjsuJAAyjC4CUMwME=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=HxmVH/mgNBiz4mCAlc1e8xfSVgI+ElujGADoulCFVPEPj0fkjovYPTpTmfb1aoY5l YdaU1Lra2C4S1lsAAymXz3RqAPxhbPQUcR4Q8xJKpKQalQgfFZswdCyXngkKTr72FP mFGTNcz2xXmG8Uk2AE29y3EvXRypWXS3gsTGWqpE= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 9C051F80341; Thu, 12 Mar 2020 15:45:29 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 99C0CF80328; Thu, 12 Mar 2020 15:45:18 +0100 (CET) Received: from mga06.intel.com (mga06.intel.com [134.134.136.31]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 7D562F802A8; Thu, 12 Mar 2020 15:45:00 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 7D562F802A8 X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga104.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 12 Mar 2020 07:44:55 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.70,545,1574150400"; d="scan'208";a="443957771" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.40.3]) by fmsmga006.fm.intel.com with ESMTP; 12 Mar 2020 07:44:53 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH 13/14] ASoC: SOF: VirtIO: free guest pipelines upon termination Date: Thu, 12 Mar 2020 15:44:28 +0100 Message-Id: <20200312144429.17959-14-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> References: <20200312144429.17959-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , sound-open-firmware@alsa-project.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" 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 --- drivers/vhost/dsp.c | 3 + include/sound/sof/virtio.h | 4 ++ sound/soc/sof/vhost-be.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) diff --git a/drivers/vhost/dsp.c b/drivers/vhost/dsp.c index 205ae8c..33e849a 100644 --- a/drivers/vhost/dsp.c +++ b/drivers/vhost/dsp.c @@ -114,6 +114,9 @@ static void vhost_dsp_deactivate(struct vhost_dsp *dsp) dsp->active = false; + /* If a VM reboots dsp_sof_client_release() isn't called */ + dsp_sof_topology_purge(dsp->snd); + spin_lock_irqsave(&dsp->posn_lock, flags); list_for_each_entry_safe(buf, next, &dsp->posn_buf_list, list) { list_del(&buf->list); diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h index fc98664..80e174e 100644 --- a/include/sound/sof/virtio.h +++ b/include/sound/sof/virtio.h @@ -147,6 +147,9 @@ struct dsp_sof_client { struct list_head pipe_conn; /* List of vhost instances on a DSP */ struct list_head list; + /* List of widgets to free for tear-down */ + struct list_head comp_list; + struct list_head pipe_list; /* Component ID range index in the bitmap */ unsigned int id; @@ -177,6 +180,7 @@ int dsp_sof_stream_data(struct dsp_sof_client *client, int dsp_sof_ipc_fwd(struct dsp_sof_client *client, int vq_idx, void *ipc_buf, void *reply_buf, size_t count, size_t reply_sz); +void dsp_sof_topology_purge(struct dsp_sof_client *client); /* The below functions are always referenced, they need dummy counterparts */ int dsp_sof_update_guest_posn(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/vhost-be.c b/sound/soc/sof/vhost-be.c index e1dede9..c25512b 100644 --- a/sound/soc/sof/vhost-be.c +++ b/sound/soc/sof/vhost-be.c @@ -43,6 +43,18 @@ struct dsp_pipeline_connect { struct list_head list; }; +struct dsp_sof_comp_list { + struct list_head list; + uint32_t comp_id; + enum sof_comp_type comp_type; +}; + +struct dsp_sof_pipe_list { + struct list_head list; + uint32_t comp_id; + uint32_t pipe_id; +}; + static const char dsp_pcm_name[] = "VHost PCM"; /* @@ -446,6 +458,75 @@ static int dsp_sof_ipc_comp(struct dsp_sof_client *client, cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; } +void dsp_sof_topology_purge(struct dsp_sof_client *client) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_free fcomp = { + .hdr = { + .size = sizeof(fcomp), + }, + }; + struct sof_ipc_reply reply; + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + int ret; + + pm_runtime_get_sync(sdev->dev); + + /* First free all pipelines */ + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + fcomp.id = pitem->comp_id; + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_PIPE_FREE; + + dev_dbg(sdev->dev, "tplg: unload component ID: %d pipe %u\n", + fcomp.id, pitem->pipe_id); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&pitem->list); + kfree(pitem); + } + + /* Then free all individual components */ + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + fcomp.id = citem->comp_id; + switch (citem->comp_type) { + case SOF_COMP_BUFFER: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_COMP_FREE; + } + + dev_dbg(sdev->dev, "tplg: unload component ID: %d type %u\n", + fcomp.id, citem->comp_type); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&citem->list); + kfree(citem); + } + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); +} +EXPORT_SYMBOL_GPL(dsp_sof_topology_purge); + /* process PM IPC */ static int dsp_sof_ipc_pm(struct dsp_sof_client *client, struct sof_ipc_cmd_hdr *hdr, struct sof_vfe_ipc_power_resp *resp) @@ -526,6 +607,47 @@ int dsp_sof_add_conn(struct snd_sof_dev *sdev, return 0; } +static int dsp_sof_tplg_comp_add(struct dsp_sof_client *client, + struct sof_ipc_comp *comp) +{ + struct dsp_sof_comp_list *citem = kmalloc(sizeof(*citem), GFP_KERNEL); + if (!citem) + return -ENOMEM; + + citem->comp_id = comp->id; + citem->comp_type = comp->type; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d type %x\n", + __func__, citem, comp->id, comp->type); + list_add_tail(&citem->list, &client->comp_list); + + return 0; +} + +static int dsp_sof_tplg_pipe_add(struct dsp_sof_client *client, + struct sof_ipc_pipe_new *pipe) +{ + struct dsp_sof_pipe_list *pitem = kmalloc(sizeof(*pitem), GFP_KERNEL); + if (!pitem) + return -ENOMEM; + + pitem->comp_id = pipe->comp_id; + pitem->pipe_id = pipe->pipeline_id; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d pipe %x\n", + __func__, pitem, pipe->comp_id, pipe->pipeline_id); + list_add_tail(&pitem->list, &client->pipe_list); + + return 0; +} + +static int dsp_sof_ipc_tplg_buf_new(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) +{ + struct sof_ipc_comp *comp = container_of(hdr, struct sof_ipc_comp, hdr); + return dsp_sof_tplg_comp_add(client, comp); +} + /* Handle some special cases of the "new component" IPC */ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) @@ -535,6 +657,7 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct snd_sof_pcm *spcm, *last; struct sof_ipc_comp_host *host; struct dsp_pipeline_connect *conn; + int ret; if (comp->id < client->comp_id_begin || comp->id >= client->comp_id_end) @@ -595,6 +718,10 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, break; } + ret = dsp_sof_tplg_comp_add(client, comp); + if (ret < 0) + return ret; + return 0; } @@ -606,6 +733,9 @@ static int dsp_sof_ipc_tplg_pipe_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_pipe_new, hdr); struct snd_sof_dev *sdev = client->sdev; struct dsp_pipeline_connect *conn; + int ret = dsp_sof_tplg_pipe_add(client, pipeline); + if (ret < 0) + return ret; list_for_each_entry(conn, &sdev->connector_list, list) if (pipeline->pipeline_id == conn->guest_pipeline_id) { @@ -723,6 +853,8 @@ static int dsp_sof_ipc_tplg(struct dsp_sof_client *client, int vq_idx, case SOF_IPC_TPLG_COMP_NEW: return dsp_sof_ipc_tplg_comp_new(client, vq_idx, hdr, reply_buf); + case SOF_IPC_TPLG_BUFFER_NEW: + return dsp_sof_ipc_tplg_buf_new(client, hdr, reply_buf); case SOF_IPC_TPLG_PIPE_NEW: return dsp_sof_ipc_tplg_pipe_new(client, vq_idx, hdr); case SOF_IPC_TPLG_COMP_CONNECT: @@ -987,6 +1119,23 @@ int dsp_sof_set_tplg(struct dsp_sof_client *client, void dsp_sof_suspend(struct snd_sof_dev *sdev) { struct snd_sof_pcm *spcm, *next; + struct dsp_sof_client *client; + + /* Upon resume we'll rebuild lists */ + list_for_each_entry(client, &sdev->vbe_list, list) { + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + list_del(&pitem->list); + kfree(pitem); + } + + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + list_del(&citem->list); + kfree(citem); + } + } list_for_each_entry_safe(spcm, next, &sdev->pcm_list, list) if (!strcmp(dsp_pcm_name, spcm->pcm.pcm_name)) { @@ -1000,6 +1149,9 @@ void dsp_sof_suspend(struct snd_sof_dev *sdev) /* A VM instance has closed the miscdevice */ void dsp_sof_client_release(struct dsp_sof_client *client) { + /* If a VM crashes we don't get ioctl(VHOST_SET_RUNNING, 0) from QEMU */ + dsp_sof_topology_purge(client); + bitmap_release_region(client->sdev->vfe_mask, client->id, 0); list_del(&client->list); @@ -1024,6 +1176,9 @@ struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, return NULL; } + INIT_LIST_HEAD(&client->pipe_list); + INIT_LIST_HEAD(&client->comp_list); + client->sdev = sdev; client->id = id; client->vhost = dsp;