From patchwork Fri Apr 3 09:13:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193107 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,URIBL_BLOCKED,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 BAC8BC43331 for ; Fri, 3 Apr 2020 09:18:05 +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 4547A20721 for ; Fri, 3 Apr 2020 09:18: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="iFd1ZW7C" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 4547A20721 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 A00C4168B; Fri, 3 Apr 2020 11:17:13 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz A00C4168B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905483; 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=iFd1ZW7C65vBx+ThkQCWp2EytiYbysTw3tmHPF39oVEy2mI3kO6Gk36lUX6ddKN2v 0qj3umg4NePspk7cYTwLP4szVj1HwXuPJVhwg8qHWdWXJwAxVYPrNtzbvFJ+cHRihx I1q5xu+C9pJR8tKe4ZQJ6DTLRAdJSn2VEY/7RREU= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 5B8EEF802BE; Fri, 3 Apr 2020 11:14:55 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 5B5E9F80299; Fri, 3 Apr 2020 11:14:31 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 9F9ECF800E4; Fri, 3 Apr 2020 11:14:16 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 9F9ECF800E4 IronPort-SDR: 2KlW0af+Nou8W6e72BUrmnsLZ7SVgMv3xj2Y9BF41tnu+1NpqiKEwEs/P6SSGIUwdxypb4c96G qR9yOycExaQA== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:13 -0700 IronPort-SDR: hwptRmh1wOUSp0gzRh/t7hYfPEBwQvzG4k9Lxt80ArNgIIqw/Ed+x1599IxlTix0BirPXjDO5y pJkXDtTC6jrw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451234934" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:11 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 02/12] [RESEND] ASoC: SOF: extract firmware-related operation into a function Date: Fri, 3 Apr 2020 11:13:56 +0200 Message-Id: <20200403091406.22381-3-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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 Fri Apr 3 09:13:59 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193108 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,URIBL_BLOCKED,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 4F805C43331 for ; Fri, 3 Apr 2020 09:17:04 +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 D2F092073B for ; Fri, 3 Apr 2020 09:17:03 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="aeH43lzA" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org D2F092073B 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 18FFB1680; Fri, 3 Apr 2020 11:16:12 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 18FFB1680 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905422; bh=c7SIsHAAXjU1YLHnq0zVFulXi1hfunmviwHY2Bjo2p0=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=aeH43lzAa80reQ30noAygTcirFIOZ+VlsfX9WsgHayvX0BKugPaDSWW5a0s3lbrX2 FfLbelqvc57FOBF9xbjgHepyGOvx6LN2XP8UOY/HFaK+RW/qH9eAAFpfZim6KaPuPd 2T8dy1vrLL9i9MAUoCfc7MgrGD8z6dvDqJM1927s= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id F100FF8014C; Fri, 3 Apr 2020 11:14:52 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id B6E8BF80291; Fri, 3 Apr 2020 11:14:29 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 BF34EF8015C; Fri, 3 Apr 2020 11:14:24 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz BF34EF8015C IronPort-SDR: 0t3Cqv3wqpB5sUKsIaAZ8WJEDCKo5ihHCFfnKcBHMmdnrcPNxjzV8aOgJoTJuvxH+l7AZJYmTd HDkm9rjbLE/g== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:18 -0700 IronPort-SDR: OCue0bINJGROa8N/tV+9dQU/BLsjQFjpuvsaGRWVTU8cmuItoSbzgDJGLGt0IAPpLXWDd5t4ot xLrFpTVw0+rQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451234953" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:16 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 05/12] [RESEND] ASoC: SOF: support IPC with immediate response Date: Fri, 3 Apr 2020 11:13:59 +0200 Message-Id: <20200403091406.22381-6-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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" Usually when an IPC message is sent, we have to wait for a reply from the DSP or from the host in the VirtIO case. However, sometimes in the VirtIO case a response is available immediately. Skip sleeping in such cases. Signed-off-by: Guennadi Liakhovetski --- sound/soc/sof/ipc.c | 9 +++++---- sound/soc/sof/ops.h | 10 +++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index cf57085..6fa501c1 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -262,6 +262,10 @@ int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, sdev->msg = msg; + /* + * If snd_sof_dsp_send_msg() returns a positive number it means, that a + * response is already available, no need to sleep waiting for it + */ ret = snd_sof_dsp_send_msg(sdev, msg); /* Next reply that we receive will be related to this message */ if (!ret) @@ -279,10 +283,7 @@ int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, ipc_log_header(sdev->dev, "ipc tx", msg->header); /* now wait for completion */ - if (!ret) - ret = tx_wait_done(ipc, msg, reply_data); - - return ret; + return tx_wait_done(ipc, msg, reply_data); } EXPORT_SYMBOL(sof_ipc_tx_message_unlocked); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index a771500..43bcfbf 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -274,7 +274,15 @@ static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar, sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes); } -/* ipc */ +/** + * snd_sof_dsp_send_msg - call sdev ops to send a message + * @sdev: sdev context + * @msg: message to send + * + * Returns < 0 - an error code + * 0 - the message has been sent, wait for a reply + * > 0 - the message has been sent, a reply is already available + */ static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { From patchwork Fri Apr 3 09:14:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193105 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,URIBL_BLOCKED,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 DE157C43331 for ; Fri, 3 Apr 2020 09:21: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 6A0B720721 for ; Fri, 3 Apr 2020 09:21:06 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="ndM4KFO9" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 6A0B720721 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 B557F169D; Fri, 3 Apr 2020 11:20:14 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz B557F169D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905664; bh=n+Yry3DaOvGT+CCP+YAKlBTHewR4Ot0fpbZIughT3sk=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=ndM4KFO96uu9zTs3xWDg/IkTESIS8d5kg1Wi2RnZHxUrccOlkSG0KgPY1Q96iP+av +c8ouNqe6rerOUBoqXJUlwkpT3dlJxvVo0vCgYMPtTTN8W5HeeWC2OLnPwxt6st2xc 9bx61isCTop3R68jqGumjKPrut4UovD2dbah3nbU= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id CD7A3F80346; Fri, 3 Apr 2020 11:15:03 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id D306EF8028F; Fri, 3 Apr 2020 11:14:42 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 2D7DBF80141; Fri, 3 Apr 2020 11:14:25 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 2D7DBF80141 IronPort-SDR: liStt8SelMKVdoLP2217AnPQ3fXN4X7uMLI2KQHf/dqhxBwGvae1FBLIBkdDuEorKmJFRi6ofq 1q82mMsPZVzA== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:19 -0700 IronPort-SDR: SXdd6sJIvOq2FQKMxKZXYKZYKe97lmkEoGDoD4rUKNq6qkDBqINvKqPxeoSitUtBgL3EmHu+vr bE0FYFZpog/Q== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451234959" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:18 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 06/12] [RESEND] ASoC: SOF: add a power status IPC Date: Fri, 3 Apr 2020 11:14:00 +0200 Message-Id: <20200403091406.22381-7-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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 a virtualised configuration the runtime PM of the host and any guests aren't synchronised. But guests have to be able to tell the host when they suspend and resume and know, whether the host has been runtime suspended since that guests's topology had been sent to the host last time. This is needed to decide whether to re-send the topology again. To support this we add a new PM IPC message SOF_IPC_PM_VFE_POWER_STATUS and a reset counter to track the state of the DSP. Signed-off-by: Guennadi Liakhovetski --- include/sound/sof/header.h | 1 + sound/soc/sof/core.c | 2 ++ sound/soc/sof/ipc.c | 2 ++ sound/soc/sof/loader.c | 4 ++++ sound/soc/sof/sof-priv.h | 4 ++++ 5 files changed, 13 insertions(+) diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h index b794795..1aaccb5a 100644 --- a/include/sound/sof/header.h +++ b/include/sound/sof/header.h @@ -77,6 +77,7 @@ #define SOF_IPC_PM_CLK_REQ SOF_CMD_TYPE(0x006) #define SOF_IPC_PM_CORE_ENABLE SOF_CMD_TYPE(0x007) #define SOF_IPC_PM_GATE SOF_CMD_TYPE(0x008) +#define SOF_IPC_PM_VFE_POWER_STATUS SOF_CMD_TYPE(0x010) /* component runtime config - multiple different types */ #define SOF_IPC_COMP_SET_VALUE SOF_CMD_TYPE(0x001) diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index ca30d67..42dd72e 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -8,6 +8,7 @@ // Author: Liam Girdwood // +#include #include #include #include @@ -311,6 +312,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; #endif + atomic_set(&sdev->reset_count, 0); dev_set_drvdata(dev, sdev); /* check all mandatory ops */ diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index 6fa501c1..b0b38ca 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -105,6 +105,8 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "CLK_REQ"; break; case SOF_IPC_PM_CORE_ENABLE: str2 = "CORE_ENABLE"; break; + case SOF_IPC_PM_VFE_POWER_STATUS: + str2 = "VFE_POWER_STATUS"; break; default: str2 = "unknown type"; break; } diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 1f2e0be..80854c9 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -10,6 +10,7 @@ // Generic firmware loader. // +#include #include #include #include "ops.h" @@ -611,6 +612,9 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) /* fw boot is complete. Update the active cores mask */ sdev->enabled_cores_mask = init_core_mask; + /* increment reset count */ + atomic_add(1, &sdev->reset_count); + return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 922b671..0792125 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -11,6 +11,7 @@ #ifndef __SOUND_SOC_SOF_PRIV_H #define __SOUND_SOC_SOF_PRIV_H +#include #include #include #include @@ -425,6 +426,9 @@ struct snd_sof_dev { unsigned int extractor_stream_tag; #endif + /* VirtIO fields for host and guest */ + atomic_t reset_count; + /* DMA for Trace */ struct snd_dma_buffer dmatb; struct snd_dma_buffer dmatp; From patchwork Fri Apr 3 09:14:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193106 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=-7.2 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,URIBL_BLOCKED,URIBL_DBL_SPAM,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 99BB6C43331 for ; Fri, 3 Apr 2020 09:19:47 +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 EF3B3206B8 for ; Fri, 3 Apr 2020 09:19:46 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="hPluzWkp" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org EF3B3206B8 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 4854E1693; Fri, 3 Apr 2020 11:18:55 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 4854E1693 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905585; bh=csd5L1cb+UbmFtq+fKCtXpCE32K8OdN2gai23eWww5w=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=hPluzWkp1tMi1SKrzbmmeoY7Jc2xK8WQBXkeKLpqPwe+20zd/+SauVB6M/SLN8t8D 02jRYhy8qzTCuaLe7MdKbdrBvio61vBT7OnoezhEvA0A+dt7HfuOQnyTsucIr27nBO gb+zX7j3gjDf6mbBOxgsRrajvZPZxphmqlpVU1GM= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 7D892F80321; Fri, 3 Apr 2020 11:14:59 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id E89D9F802A7; Fri, 3 Apr 2020 11:14:39 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 79AD4F8028C; Fri, 3 Apr 2020 11:14:28 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 79AD4F8028C IronPort-SDR: xWQtcOZ6JYagrOV6hyg1IMb7CV0M3Oy6oK5CdqPotJNh5/sU7XjsML0loSJOFN1NIe+IPRn7AX DkFZKCMgzWHA== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:23 -0700 IronPort-SDR: NV8WAweAktf2z6dL5kc1PpwJP7F3sm0Qle3TQITcthIHMoyWF3y9M7GFdPGAHmY/outrlRFm0n fzi4HICJtLYQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451234983" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:21 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 08/12] [RESEND] ASoC: SOF: add a VirtIO DSP driver Date: Fri, 3 Apr 2020 11:14:02 +0200 Message-Id: <20200403091406.22381-9-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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 a VirtIO driver, designed to work with the SOF vhost driver. This driver allows SOF to be used on Virtual Machines (VMs) where the host is also a Linux system, using the SOF driver natively. This driver communicates with the host using the VirtIO standard over Virtual Queues. This version uses 3 Virtual Queues: for control, for data and for position updates. The control Virtual Queue uses exactly the same IPC protocol as what is used by the SOF driver natively to communicate with the DSP. In the future a zero-copy capability should be added thus eliminating 2 out of 3 Virtual Queues and only preserving the control queue. Signed-off-by: Guennadi Liakhovetski --- include/sound/sof.h | 4 + include/sound/sof/header.h | 2 + include/sound/sof/topology.h | 1 + include/sound/sof/virtio.h | 129 ++++++ include/uapi/linux/virtio_ids.h | 1 + sound/soc/sof/Kconfig | 7 + sound/soc/sof/Makefile | 4 + sound/soc/sof/core.c | 29 +- sound/soc/sof/ipc.c | 16 +- sound/soc/sof/pcm.c | 9 + sound/soc/sof/sof-priv.h | 29 ++ sound/soc/sof/topology.c | 18 +- sound/soc/sof/virtio-fe.c | 891 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1115 insertions(+), 25 deletions(-) create mode 100644 include/sound/sof/virtio.h create mode 100644 sound/soc/sof/virtio-fe.c diff --git a/include/sound/sof.h b/include/sound/sof.h index a0cbca0..2c63d8f 100644 --- a/include/sound/sof.h +++ b/include/sound/sof.h @@ -17,6 +17,8 @@ struct snd_sof_dsp_ops; +struct sof_vfe; + /* * SOF Platform data. */ @@ -27,6 +29,8 @@ struct snd_sof_pdata { struct device *dev; + struct sof_vfe *vfe; + /* * notification callback used if the hardware initialization * can take time or is handled in a workqueue. This callback diff --git a/include/sound/sof/header.h b/include/sound/sof/header.h index 1aaccb5a..49ad3ee 100644 --- a/include/sound/sof/header.h +++ b/include/sound/sof/header.h @@ -67,6 +67,8 @@ #define SOF_IPC_TPLG_PIPE_COMPLETE SOF_CMD_TYPE(0x013) #define SOF_IPC_TPLG_BUFFER_NEW SOF_CMD_TYPE(0x020) #define SOF_IPC_TPLG_BUFFER_FREE SOF_CMD_TYPE(0x021) +#define SOF_IPC_TPLG_VFE_GET SOF_CMD_TYPE(0x030) +#define SOF_IPC_TPLG_VFE_COMP_ID SOF_CMD_TYPE(0x031) /* PM */ #define SOF_IPC_PM_CTX_SAVE SOF_CMD_TYPE(0x001) diff --git a/include/sound/sof/topology.h b/include/sound/sof/topology.h index 402e025..6de3154 100644 --- a/include/sound/sof/topology.h +++ b/include/sound/sof/topology.h @@ -37,6 +37,7 @@ enum sof_comp_type { SOF_COMP_SELECTOR, /**< channel selector component */ SOF_COMP_DEMUX, SOF_COMP_ASRC, /**< Asynchronous sample rate converter */ + SOF_COMP_VIRT_CON, /**< Virtual connection, sent by the VirtIO guest */ /* keep FILEREAD/FILEWRITE as the last ones */ SOF_COMP_FILEREAD = 10000, /**< host test based file IO */ SOF_COMP_FILEWRITE = 10001, /**< host test based file IO */ diff --git a/include/sound/sof/virtio.h b/include/sound/sof/virtio.h new file mode 100644 index 00000000..d7d94da --- /dev/null +++ b/include/sound/sof/virtio.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2018-2020 Intel Corporation. All rights reserved. + * + * Contact Information: + * Author: Luo Xionghu + * Liam Girdwood + * Guennadi Liakhovetski + */ + +#ifndef _SOF_VIRTIO_H +#define _SOF_VIRTIO_H + +#include + +/* + * Currently we define 3 vqs: one for handling of IPC commands, one for + * handling of stream position updates, and one for audio data. + */ +enum { + SOF_VIRTIO_IPC_CMD_VQ, /* IPC commands and replies */ + SOF_VIRTIO_POSN_VQ, /* Stream position updates */ + SOF_VIRTIO_DATA_VQ, /* Audio data */ + /* Keep last */ + SOF_VIRTIO_NUM_OF_VQS, +}; + +/* command messages from FE to BE, trigger/open/hw_params and so on */ +#define SOF_VIRTIO_IPC_CMD_VQ_NAME "sof-ipc-cmd" + +/* the vq to get stream position updates */ +#define SOF_VIRTIO_POSN_VQ_NAME "sof-position" + +/* the vq for audio data */ +#define SOF_VIRTIO_DATA_VQ_NAME "sof-data" + +/** + * struct sof_vfe_ipc_tplg_req - request for topology data + * @hdr: the standard SOF IPC header + * @offset: the current offset when transferring a split file + */ +struct sof_vfe_ipc_tplg_req { + struct sof_ipc_cmd_hdr hdr; + size_t offset; +} __packed; + +/** + * struct sof_vfe_ipc_tplg_resp - response to a topology file request + * @reply: the standard SOF IPC response header + * @data: the complete topology file + * + * The topology file is transferred from the host to the guest over a virtual + * queue in chunks of SOF_IPC_MSG_MAX_SIZE - sizeof(struct sof_ipc_reply), so + * for data transfer the @data array is much smaller than 64KiB. 64KiB is what + * is included in struct sof_vfe for permanent storage of the complete file. + */ +struct sof_vfe_ipc_tplg_resp { + struct sof_ipc_reply reply; + /* There exist topology files already larger than 40KiB */ + uint8_t data[64 * 1024 - sizeof(struct sof_ipc_reply)]; +} __packed; + +/** + * struct sof_vfe_ipc_power_req - power status change IPC + * @hdr: the standard SOF IPC header + * @power: 1: on, 0: off + */ +struct sof_vfe_ipc_power_req { + struct sof_ipc_cmd_hdr hdr; + uint32_t power; +} __packed; + +enum sof_virtio_ipc_reset_status { + SOF_VIRTIO_IPC_RESET_NONE, /* Host hasn't been reset */ + SOF_VIRTIO_IPC_RESET_DONE, /* Host has been reset */ +}; + +/** + * struct sof_vfe_ipc_power_resp - response to a power status request + * @reply: the standard SOF IPC response header + * @reset_status: enum sof_virtio_ipc_reset_status + */ +struct sof_vfe_ipc_power_resp { + struct sof_ipc_reply reply; + uint32_t reset_status; +} __packed; + +#define SOF_VFE_MAX_DATA_SIZE (16 * 1024) + +/** + * struct dsp_sof_data_req - Audio data request + * + * @size: the size of audio data sent or requested, excluding the header + * @offset: offset in the DMA buffer + * @comp_id: component ID, used to identify the stream + * @data: audio data + * + * When used during playback, the data array actually contains audio data, when + * used for capture, the data part isn't sent. + */ +struct dsp_sof_data_req { + u32 size; + u32 offset; + u32 comp_id; + /* Only included for playback */ + u8 data[SOF_VFE_MAX_DATA_SIZE]; +} __packed; + +/** + * struct dsp_sof_data_resp - Audio data response + * + * @size: the size of audio data sent, excluding the header + * @error: response error + * @data: audio data + * + * When used during capture, the data array actually contains audio data, when + * used for playback, the data part isn't sent. + */ +struct dsp_sof_data_resp { + u32 size; + u32 error; + /* Only included for capture */ + u8 data[SOF_VFE_MAX_DATA_SIZE]; +} __packed; + +#define HDR_SIZE_REQ offsetof(struct dsp_sof_data_req, data) +#define HDR_SIZE_RESP offsetof(struct dsp_sof_data_resp, data) + +#endif diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 585e07b..0a15aa6 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -46,5 +46,6 @@ #define VIRTIO_ID_IOMMU 23 /* virtio IOMMU */ #define VIRTIO_ID_FS 26 /* virtio filesystem */ #define VIRTIO_ID_PMEM 27 /* virtio pmem */ +#define VIRTIO_ID_ADSP 28 /* virtio AudioDSP */ #endif /* _LINUX_VIRTIO_IDS_H */ diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 4dda4b6..e96d8ff 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -96,6 +96,13 @@ config SND_SOC_SOF_STRICT_ABI_CHECKS If you are not involved in SOF releases and CI development select "N". +config SND_SOC_SOF_VIRTIO_FE + bool "SOF VirtIO guest role" + depends on SND_SOC_SOF_NOCODEC_SUPPORT + depends on VIRTIO + ---help--- + Enable SOF for a VirtIO based guest configuration. + config SND_SOC_SOF_DEBUG bool "SOF debugging features" help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 8eca2f8..b728d09 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -10,6 +10,10 @@ snd-sof-of-objs := sof-of-dev.o snd-sof-nocodec-objs := nocodec.o +ifdef CONFIG_SND_SOC_SOF_VIRTIO_FE +snd-sof-objs += virtio-fe.o +endif + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 42dd72e..d0bf082 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -178,7 +178,8 @@ static int sof_load_and_run_firmware(struct snd_sof_dev *sdev) return 0; fw_run_err: - snd_sof_fw_unload(sdev); + if (!sdev->pdata->vfe) + snd_sof_fw_unload(sdev); return ret; } @@ -228,9 +229,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto ipc_err; } - ret = sof_load_and_run_firmware(sdev); - if (ret < 0) - goto fw_load_err; + /* virtio front-end mode will not touch HW, skip fw loading */ + if (!plat_data->vfe) { + ret = sof_load_and_run_firmware(sdev); + if (ret < 0) + goto fw_load_err; + } /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -264,7 +268,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) fw_trace_err: snd_sof_free_trace(sdev); - snd_sof_fw_unload(sdev); + if (!sdev->pdata->vfe) + snd_sof_fw_unload(sdev); fw_load_err: snd_sof_ipc_free(sdev); ipc_err: @@ -362,10 +367,12 @@ int snd_sof_device_remove(struct device *dev) cancel_work_sync(&sdev->probe_work); if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { - snd_sof_fw_unload(sdev); + if (!pdata->vfe) { + snd_sof_fw_unload(sdev); + snd_sof_free_trace(sdev); + } snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); - snd_sof_free_trace(sdev); } /* @@ -384,9 +391,11 @@ int snd_sof_device_remove(struct device *dev) if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) snd_sof_remove(sdev); - /* release firmware */ - release_firmware(pdata->fw); - pdata->fw = NULL; + if (!pdata->vfe) { + /* release firmware */ + release_firmware(pdata->fw); + pdata->fw = NULL; + } return 0; } diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index b0b38ca..8f52de0 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -25,18 +25,6 @@ * IPC message Tx/Rx message handling. */ -/* SOF generic IPC data */ -struct snd_sof_ipc { - struct snd_sof_dev *sdev; - - /* protects messages and the disable flag */ - struct mutex tx_mutex; - /* disables further sending of ipc's */ - bool disable_ipc_tx; - - struct snd_sof_ipc_msg msg; -}; - struct sof_ipc_ctrl_data_params { size_t msg_bytes; size_t hdr_bytes; @@ -84,6 +72,10 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "BUFFER_NEW"; break; case SOF_IPC_TPLG_BUFFER_FREE: str2 = "BUFFER_FREE"; break; + case SOF_IPC_TPLG_VFE_GET: + str2 = "VFE_GET"; break; + case SOF_IPC_TPLG_VFE_COMP_ID: + str2 = "VFE_COMP_ID"; break; default: str2 = "unknown type"; break; } diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 47cd741..3e05c41 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -677,6 +677,13 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, return -EINVAL; } + /* VirtIO guests have no .dai_config, DAIs are configured by the host */ + if (!dai->dai_config) { + dev_dbg(component->dev, "no DAI config for %s!\n", + rtd->dai_link->name); + return 0; + } + /* read rate and channels from topology */ switch (dai->dai_config->type) { case SOF_DAI_INTEL_SSP: @@ -783,6 +790,8 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->hw_free = sof_pcm_hw_free; pd->trigger = sof_pcm_trigger; pd->pointer = sof_pcm_pointer; + if (plat_data->vfe) + pd->copy_user = sof_vfe_pcm_copy_user; #if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) pd->compr_ops = &sof_compressed_ops; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0792125..44780c2 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -55,6 +55,9 @@ (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) +/* The maximum number of components a virtio user vFE driver can use */ +#define SOF_VIRTIO_MAX_UOS_COMPS 1000 + /* DSP power state */ enum sof_dsp_power_states { SOF_DSP_PM_D0, @@ -250,6 +253,10 @@ struct snd_sof_dsp_ops { void (*set_mach_params)(const struct snd_soc_acpi_mach *mach, struct device *dev); /* optional */ + /* VirtIO operations */ + int (*request_topology)(struct snd_sof_dev *sdev, + struct firmware *fw); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -445,6 +452,18 @@ struct snd_sof_dev { void *private; /* core does not touch this */ }; +/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* protects messages and the disable flag */ + struct mutex tx_mutex; + /* disables further sending of ipc's */ + bool disable_ipc_tx; + + struct snd_sof_ipc_msg msg; +}; + /* * Device Level. */ @@ -526,10 +545,20 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); +#if IS_ENABLED(CONFIG_SND_SOC_SOF_VIRTIO_FE) +int sof_vfe_pcm_copy_user(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int channel, + unsigned long pos, void __user *buf, + unsigned long bytes); +#else +#define sof_vfe_pcm_copy_user NULL +#endif + /* * Platform specific ops. */ extern struct snd_compr_ops sof_compressed_ops; +extern struct snd_sof_dsp_ops snd_sof_virtio_fe_ops; /* * DSP Architectures. diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index fe8ba3e..dcc897f 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1301,7 +1301,8 @@ static int sof_widget_load_dai(struct snd_soc_component *scomp, int index, comp_dai.comp.hdr.size = sizeof(comp_dai); comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; comp_dai.comp.id = swidget->comp_id; - comp_dai.comp.type = SOF_COMP_DAI; + comp_dai.comp.type = sdev->pdata->vfe ? SOF_COMP_VIRT_CON : + SOF_COMP_DAI; comp_dai.comp.pipeline_id = index; comp_dai.config.hdr.size = sizeof(comp_dai.config); @@ -3620,12 +3621,21 @@ static int sof_manifest(struct snd_soc_component *scomp, int index, int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct firmware vfe_fw; const struct firmware *fw; int ret; dev_dbg(scomp->dev, "loading topology:%s\n", file); - ret = request_firmware(&fw, file, scomp->dev); + /* VirtIO guests request topology from the host */ + if (sdev->pdata->vfe) { + fw = &vfe_fw; + ret = sof_ops(sdev)->request_topology(sdev, file, &vfe_fw); + } else { + ret = request_firmware(&fw, file, sdev->dev); + } + if (ret < 0) { dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n", file, ret); @@ -3641,7 +3651,9 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) ret = -EINVAL; } - release_firmware(fw); + if (!sdev->pdata->vfe) + release_firmware(fw); + return ret; } EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/virtio-fe.c b/sound/soc/sof/virtio-fe.c new file mode 100644 index 00000000..aa6da81 --- /dev/null +++ b/sound/soc/sof/virtio-fe.c @@ -0,0 +1,891 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* + * Copyright(c) 2017-2020 Intel Corporation. All rights reserved. + * + * Author: Libin Yang + * Luo Xionghu + * Liam Girdwood + * Guennadi Liakhovetski + */ + +/* + * VirtIO Front-End (VFE) driver + * + * This driver presents a virtualised DSP interface to SOF. It implements an + * instance of SOF operations, which instead of providing means of communication + * with a physical DSP, communicate with the SOF driver, running in the host OS. + * This communication takes place over 3 VirtQueues: control, data, and position + * update. The control queue uses the SOF IPC message format, which make it + * possible for most messages to be passed 1-to-1 to the physical DSP on the + * host. The data and the position update queues should in the future be + * replaced with 0-copy buffer sharing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ops.h" +#include "sof-audio.h" +#include "sof-priv.h" + +/* 600ms for VirtQ IPC */ +#define SOF_VFE_DATA_TIMEOUT_MS 600 + +static const char *const sof_vq_names[SOF_VIRTIO_NUM_OF_VQS] = { + SOF_VIRTIO_IPC_CMD_VQ_NAME, + SOF_VIRTIO_POSN_VQ_NAME, + SOF_VIRTIO_DATA_VQ_NAME, +}; + +struct sof_vfe { + struct snd_sof_dev *sdev; + + /* IPC cmd from frontend to backend */ + struct virtqueue *ipc_cmd_vq; + + /* IPC position update from backend to frontend */ + struct virtqueue *posn_vq; + + /* audio data in both directions */ + struct virtqueue *data_vq; + + /* position update work */ + struct work_struct posn_update_work; + + /* current pending cmd message */ + struct snd_sof_ipc_msg *msg; + + /* current and pending notification */ + struct snd_sof_ipc_msg *not; + struct sof_ipc_stream_posn posn; + + /* + * IPC messages are blocked. "true" if the DSP hasn't been reset and + * therefore we don't have to re-send our topology. + */ + bool block_ipc; + struct sof_vfe_ipc_tplg_resp tplg; + + struct completion completion; + spinlock_t vq_lock; + + /* A shared capture / playback virtual queue data buffer */ + union { + struct dsp_sof_data_req data_req; + struct dsp_sof_data_resp data_resp; + }; + + /* Headers, used as a playback response or capture request */ + union { + u8 hdr_req[HDR_SIZE_REQ]; + u8 hdr_resp[HDR_SIZE_RESP]; + }; +}; + +/* Firmware ready IPC. */ +static int sof_vfe_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) +{ + return 0; +}; + +/* Send IPC to vBE */ +static int sof_vfe_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct scatterlist sg_out, sg_in; + struct scatterlist *sgs[] = {&sg_out, &sg_in}; + size_t msg_size = msg->msg_size; + void *msg_data = msg->msg_data; + struct device *dev = sdev->dev; + int ret; + + if (vfe->block_ipc) { + struct sof_ipc_reply *reply = msg->reply_data; + + reply->error = 0; + msg->reply_error = reply->error; + /* + * No need to take .ipc_lock: we return > 0, so + * sof_ipc_tx_message_unlocked() won't overwrite .ipc_complete + */ + msg->ipc_complete = true; + wake_up(&msg->waitq); + + return 1; + } + + /* Prepare and add 1 SG array for each of request and response */ + sg_init_one(&sg_out, msg_data, msg_size); + sg_init_one(&sg_in, msg->reply_data, msg->reply_size); + + /* Called under spin_lock_irq() in sof_ipc_tx_message_unlocked() */ + ret = virtqueue_add_sgs(vfe->ipc_cmd_vq, sgs, 1, 1, msg_data, + GFP_ATOMIC); + if (ret < 0) { + dev_err(dev, "error: could not send IPC %d\n", ret); + return ret; + } + + vfe->msg = msg; + + /* Notify the host */ + if (!virtqueue_kick(vfe->ipc_cmd_vq)) { + dev_err(dev, "error: IPC failed to kick host\n"); + return -EIO; + } + + return 0; +} + +/* Handle playback or capture data */ +static void sof_vfe_handle_data(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + + complete(&vfe->completion); +} + +/* IPC message sending completed. This means vBE has received the cmd */ +static void sof_vfe_cmd_tx_done(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + struct snd_sof_dev *sdev = vfe->sdev; + + do { + struct snd_sof_ipc_msg *msg = vfe->msg; + struct sof_ipc_reply *reply = msg->reply_data; + unsigned int len; + + /* Callbacks must be disabled when reading from a VirtQ */ + virtqueue_disable_cb(vq); + + spin_lock(&vfe->vq_lock); + /* + * virtqueue_get_buf() returns the "token" that was provided to + * virtqueue_add_*() functions + */ + while (virtqueue_get_buf(vq, &len)) { + unsigned long flags; + msg->reply_error = reply->error; + + /* Firmware panic? */ + if (msg->reply_error == -ENODEV) + sdev->ipc->disable_ipc_tx = true; + + spin_lock_irqsave(&sdev->ipc_lock, flags); + msg->ipc_complete = true; + wake_up(&msg->waitq); + spin_unlock_irqrestore(&sdev->ipc_lock, flags); + } + spin_unlock(&vfe->vq_lock); + } while (!virtqueue_enable_cb(vq)); +} + +/* The high latency version, using VirtQueues */ +static void sof_vfe_posn_update(struct work_struct *work) +{ + struct sof_vfe *vfe = container_of(work, struct sof_vfe, + posn_update_work); + struct sof_ipc_stream_posn *posn = &vfe->posn; + struct virtqueue *vq = vfe->posn_vq; + struct snd_sof_dev *sdev = vfe->sdev; + struct snd_sof_pcm *spcm; + struct scatterlist sg; + unsigned int buflen; + int direction; + + /* virtio protects against re-entry */ + while (virtqueue_get_buf(vq, &buflen)) { + spcm = snd_sof_find_spcm_comp(sdev->component, posn->comp_id, + &direction); + if (!spcm) { + dev_err(sdev->dev, + "err: period elapsed for unused component %d\n", + posn->comp_id); + } else { + /* + * The position update requirement is valid. + * Let's update the position now. + */ + memcpy(&spcm->stream[direction].posn, posn, sizeof(*posn)); + snd_pcm_period_elapsed(spcm->stream[direction].substream); + } + + /* kick back the empty posn buffer immediately */ + sg_init_one(&sg, posn, sizeof(*posn)); + virtqueue_add_inbuf(vq, &sg, 1, posn, GFP_KERNEL); + if (!virtqueue_kick(vq)) + dev_err(sdev->dev, + "error: position update failed to kick host\n"); + } +} + +/* + * handle the position update, receive the posn and send to up layer, then + * resend the buffer to BE + */ +static void sof_vfe_posn_handle_rx(struct virtqueue *vq) +{ + struct sof_vfe *vfe = vq->vdev->priv; + + schedule_work(&vfe->posn_update_work); +} + +static int sof_vfe_register(struct snd_sof_dev *sdev) +{ + sdev->pdata->vfe->sdev = sdev; + sdev->next_comp_id = SOF_VIRTIO_MAX_UOS_COMPS; + + return 0; +} + +/* Some struct snd_sof_dsp_ops operations are compulsory, but unused by vFE */ +static int sof_vfe_deregister(struct snd_sof_dev *sdev) +{ + return 0; +} + +static int sof_vfe_run(struct snd_sof_dev *sdev) +{ + return 0; +} + +static void sof_vfe_block_read(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *dest, + size_t size) +{ +} + +static void sof_vfe_block_write(struct snd_sof_dev *sdev, u32 bar, + u32 offset, void *src, + size_t size) +{ +} + +static int sof_vfe_load_firmware(struct snd_sof_dev *sdev) +{ + return 0; +} + +static void sof_vfe_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + void *p, size_t sz) +{ +} + +static int sof_vfe_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + return 0; +} + +static int sof_vfe_sof_runtime_dummy(struct snd_sof_dev *sdev) +{ + return 0; +} + +/* Pre-queue a position update buffer. */ +static int sof_vfe_prequeue_position(struct snd_sof_dev *sdev) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct scatterlist sg_in; + int ret; + + sg_init_one(&sg_in, &vfe->posn, sizeof(vfe->posn)); + /* Only 1 input buffer, no output buffers */ + ret = virtqueue_add_inbuf(vfe->posn_vq, &sg_in, 1, &vfe->posn, + GFP_KERNEL); + if (ret < 0) { + dev_err(sdev->dev, "%s(): failed %d to add a buffer\n", + __func__, ret); + return ret; + } + + /* Send the buffer */ + if (!virtqueue_kick(vfe->posn_vq)) { + dev_err(sdev->dev, + "error: position update failed to kick host\n"); + return -EIO; + } + + return 0; +} + +static int sof_vfe_request_topology(struct snd_sof_dev *sdev, + struct firmware *fw) +{ + struct sof_vfe_ipc_tplg_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_VFE_GET, + }, + }; + struct sof_vfe_ipc_tplg_resp *partdata = kmalloc(SOF_IPC_MSG_MAX_SIZE, + GFP_KERNEL); + size_t part_size = SOF_IPC_MSG_MAX_SIZE - sizeof(partdata->reply), + data_size = 0, to_copy = 0; + struct sof_vfe *vfe = sdev->pdata->vfe; + struct device *dev = sdev->dev; + int ret; + + if (!partdata) + return -ENOMEM; + + mutex_lock(&sdev->ipc->tx_mutex); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, + "Cannot resume VFE sof-audio device. Error %d\n", ret); + return ret; + } + + do { + ret = sof_ipc_tx_message_unlocked(sdev->ipc, rq.hdr.cmd, + &rq, sizeof(rq), partdata, + SOF_IPC_MSG_MAX_SIZE); + if (ret < 0) + goto free; + + if (partdata->reply.hdr.size <= sizeof(partdata->reply)) { + ret = -EINVAL; + goto free; + } + + if (data_size && data_size - to_copy != + partdata->reply.hdr.size - sizeof(partdata->reply)) { + ret = -EINVAL; + goto free; + } + + /* + * Size is consistent and decreasing, we're guaranteed to exit + * this loop eventually + */ + data_size = partdata->reply.hdr.size - sizeof(partdata->reply); + to_copy = min_t(size_t, data_size, part_size); + memcpy(vfe->tplg.data + rq.offset, partdata->data, to_copy); + if (!rq.offset) + fw->size = data_size; + rq.offset += part_size; + } while (data_size > part_size); + + /* Get our first component ID */ + rq.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_VFE_COMP_ID; + rq.hdr.size = sizeof(rq.hdr); + ret = sof_ipc_tx_message_unlocked(sdev->ipc, rq.hdr.cmd, + &rq, rq.hdr.size, partdata, + sizeof(partdata->reply) + sizeof(u32)); + if (ret < 0) + goto free; + + sdev->next_comp_id = *(u32 *)partdata->data; + + fw->data = vfe->tplg.data; + fw->pages = NULL; + + ret = sof_vfe_prequeue_position(sdev); + +free: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(partdata); + + return ret; +} + +static unsigned long get_dma_offset(struct snd_pcm_runtime *runtime, + int channel, unsigned long hwoff) +{ + return hwoff + channel * (runtime->dma_bytes / runtime->channels); +} + +static int sof_vfe_pcm_read_part(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_pcm_substream *substream, + int channel, unsigned long posn, + void __user *buf, unsigned long chunk_size) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct dsp_sof_data_resp *data = &vfe->data_resp; + struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; + struct dsp_sof_data_req *req = (struct dsp_sof_data_req *)vfe->hdr_req; + unsigned int len; + int ret; + + /* put response size in request */ + req->size = chunk_size; + req->comp_id = spcm->stream[substream->stream].comp_id; + req->offset = get_dma_offset(substream->runtime, channel, posn); + + /* Add input and output buffers */ + sg_init_one(&sg_out, vfe->hdr_req, sizeof(vfe->hdr_req)); + sg_init_one(&sg_in, data, chunk_size + HDR_SIZE_RESP); + + ret = virtqueue_add_sgs(vfe->data_vq, sgs, 1, 1, vfe->hdr_req, + GFP_KERNEL); + if (ret < 0) + dev_err(sdev->dev, "error: could not send data %d\n", ret); + + /* Kick the host */ + if (!virtqueue_kick(vfe->data_vq)) { + dev_err(sdev->dev, "error: data failed to kick host\n"); + return -EIO; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + /* Get a response with disabled callbacks */ + virtqueue_disable_cb(vfe->data_vq); + + spin_lock(&vfe->vq_lock); + virtqueue_get_buf(vfe->data_vq, &len); + spin_unlock(&vfe->vq_lock); + + virtqueue_enable_cb(vfe->data_vq); + + if (ret < 0) + return ret; + if (data->error < 0) + return data->error; + + if (copy_to_user(buf, data->data, chunk_size)) + return -EFAULT; + + return 0; +} + +static int sof_vfe_pcm_write_part(struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm, + struct snd_pcm_substream *substream, + int channel, unsigned long posn, + void __user *buf, unsigned long chunk_size) +{ + struct sof_vfe *vfe = sdev->pdata->vfe; + struct dsp_sof_data_req *data = &vfe->data_req; + struct scatterlist sg_out, sg_in, *sgs[] = {&sg_out, &sg_in}; + struct dsp_sof_data_resp *resp = (struct dsp_sof_data_resp *)vfe->hdr_resp; + unsigned int len; + int ret; + + data->size = chunk_size; + data->comp_id = spcm->stream[substream->stream].comp_id; + data->offset = get_dma_offset(substream->runtime, channel, posn); + + if (copy_from_user(data->data, buf, chunk_size)) + return -EFAULT; + + /* Similar to the capture case above */ + sg_init_one(&sg_out, data, chunk_size + HDR_SIZE_REQ); + sg_init_one(&sg_in, vfe->hdr_resp, sizeof(vfe->hdr_resp)); + + ret = virtqueue_add_sgs(vfe->data_vq, sgs, 1, 1, vfe->hdr_resp, + GFP_KERNEL); + if (ret < 0) + dev_err(sdev->dev, "error: could not send data %d\n", ret); + + if (!virtqueue_kick(vfe->data_vq)) { + dev_err(sdev->dev, "error: data failed to kick host\n"); + return -EIO; + } + + ret = wait_for_completion_timeout(&vfe->completion, + msecs_to_jiffies(SOF_VFE_DATA_TIMEOUT_MS)); + if (!ret) + return -ETIMEDOUT; + + virtqueue_disable_cb(vfe->data_vq); + + spin_lock(&vfe->vq_lock); + virtqueue_get_buf(vfe->data_vq, &len); + spin_unlock(&vfe->vq_lock); + + virtqueue_enable_cb(vfe->data_vq); + + return ret < 0 ? ret : resp->error; +} + +/* The slow version, using VirtQueues for playback and capture data */ +int sof_vfe_pcm_copy_user(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int channel, + unsigned long posn, void __user *buf, + unsigned long bytes) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_sof_pcm *spcm = snd_sof_find_spcm_dai(sdev->component, rtd); + unsigned int i, n = (bytes + SOF_VFE_MAX_DATA_SIZE - 1) / + SOF_VFE_MAX_DATA_SIZE; + int ret = 0; + + if (!spcm || spcm->scomp != sdev->component) { + dev_err(sdev->dev, "%s(): invalid SPCM 0x%p!\n", __func__, + spcm); + return -ENODEV; + } + + mutex_lock(&sdev->ipc->tx_mutex); + + for (i = 0; i < n; i++) { + size_t n_bytes = i == n - 1 ? bytes % SOF_VFE_MAX_DATA_SIZE : + SOF_VFE_MAX_DATA_SIZE; + + reinit_completion(&sdev->pdata->vfe->completion); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = sof_vfe_pcm_write_part(sdev, spcm, substream, + channel, posn, buf, n_bytes); + else + ret = sof_vfe_pcm_read_part(sdev, spcm, substream, + channel, posn, buf, n_bytes); + + if (ret < 0) + break; + + buf += n_bytes; + posn += n_bytes; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + return ret; +} + +#define SOF_VFE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai_driver virtio_dai[] = { + { + .name = "VirtIO DAI", + .playback = SOF_DAI_STREAM("playback", 1, 8, + SNDRV_PCM_RATE_8000_192000, SOF_VFE_FORMATS), + .capture = SOF_DAI_STREAM("capture", 1, 8, + SNDRV_PCM_RATE_8000_192000, SOF_VFE_FORMATS), + }, +}; + +static int sof_vfe_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + int ret = pm_runtime_get_sync(sdev->dev); + if (ret < 0) + dev_err(sdev->dev, + "Cannot resume VFE sof-audio device. Error %d\n", ret); + + return ret; +} + +static int sof_vfe_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + + return 0; +} + +/* virtio fe ops */ +struct snd_sof_dsp_ops snd_sof_vfe_ops = { + /* device init */ + .probe = sof_vfe_register, + .remove = sof_vfe_deregister, + + /* + * PM: these are never called, they are only needed to prevent core.c + * from disabling runtime PM + */ + .runtime_suspend = sof_vfe_sof_runtime_dummy, + .runtime_resume = sof_vfe_sof_runtime_dummy, + + /* IPC */ + .send_msg = sof_vfe_send_msg, + .fw_ready = sof_vfe_fw_ready, + + /* machine driver */ + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + + /* DAI drivers */ + .drv = virtio_dai, + .num_drv = 1, + + .pcm_open = sof_vfe_pcm_open, + .pcm_close = sof_vfe_pcm_close, + + .run = sof_vfe_run, + .block_read = sof_vfe_block_read, + .block_write = sof_vfe_block_write, + .load_firmware = sof_vfe_load_firmware, + .ipc_msg_data = sof_vfe_ipc_msg_data, + .ipc_pcm_params = sof_vfe_ipc_pcm_params, + + .request_topology = sof_vfe_request_topology, + + .hw_info = SNDRV_PCM_INFO_INTERLEAVED, +}; + +static const struct sof_dev_desc virt_desc = { + .nocodec_tplg_filename = "", + .default_tplg_path = "", + .resindex_lpe_base = -1, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .resindex_dma_base = -1, + .ipc_timeout = SOF_VFE_DATA_TIMEOUT_MS, + .ops = &snd_sof_vfe_ops, +}; + +static void sof_virtio_vfe_init(struct snd_sof_dev *sdev, + struct sof_vfe *vfe) +{ + /* + * Currently we only support one VM. comp_id from 0 to + * SOF_VIRTIO_MAX_UOS_COMPS - 1 is for the host. Other comp_id numbers + * are for VM1. + * This will be overwritten during topology setup. + */ + sdev->next_comp_id = SOF_VIRTIO_MAX_UOS_COMPS; + vfe->sdev = sdev; +} + +static int sof_vfe_init(struct virtio_device *vdev) +{ + struct device *dev = &vdev->dev; + struct snd_soc_acpi_mach *mach; + struct snd_sof_pdata *sof_pdata; + int ret; + + sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL); + if (!sof_pdata) + return -ENOMEM; + + mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL); + if (!mach) + return -ENOMEM; + + mach->drv_name = "sof-nocodec"; + mach->mach_params.platform = dev_name(dev); + sof_pdata->tplg_filename = virt_desc.nocodec_tplg_filename; + + ret = sof_nocodec_setup(dev, &snd_sof_vfe_ops); + if (ret < 0) + return ret; + + mach->pdata = &snd_sof_vfe_ops; + + sof_pdata->name = dev_name(&vdev->dev); + sof_pdata->machine = mach; + sof_pdata->desc = &virt_desc; + sof_pdata->dev = dev; + sof_pdata->vfe = vdev->priv; + sof_pdata->tplg_filename_prefix = virt_desc.default_tplg_path; + + /* allow runtime_pm */ + pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + ret = snd_sof_device_probe(dev, sof_pdata); + if (ret < 0) { + dev_err(dev, "Cannot register VFE sof-audio device. Error %d\n", + ret); + } else { + sof_virtio_vfe_init(dev_get_drvdata(dev), vdev->priv); + dev_dbg(dev, "created VFE machine %s\n", + dev_name(&sof_pdata->pdev_mach->dev)); + } + + return ret; +} + +static int sof_vfe_runtime_suspend(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct sof_vfe_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 0, + }; + struct sof_vfe_ipc_power_resp resp = {.reply.error = 0}; + + return sof_ipc_tx_message(sdev->ipc, rq.hdr.cmd, &rq, sizeof(rq), + &resp, sizeof(resp)); +} + +static int sof_vfe_runtime_resume(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct sof_vfe *vfe = sdev->pdata->vfe; + struct sof_vfe_ipc_power_req rq = { + .hdr = { + .size = sizeof(rq), + .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_VFE_POWER_STATUS, + }, + .power = 1, + }; + struct sof_vfe_ipc_power_resp resp = {.reply.error = 0}; + int ret = sof_ipc_tx_message(sdev->ipc, rq.hdr.cmd, &rq, sizeof(rq), + &resp, sizeof(resp)); + if (ret < 0) + return ret; + + if (resp.reply.error < 0) + return resp.reply.error; + + /* + * We are resuming. Check if the host needs the topology. We could in + * principle skip restoring pipelines completely, but it also does + * certain additional things, e.g. setting an enabled core mask + */ + vfe->block_ipc = resp.reset_status == SOF_VIRTIO_IPC_RESET_NONE; + + /* restore pipelines */ + ret = sof_restore_pipelines(sdev->dev); + if (ret < 0) + dev_err(dev, + "error: failed to restore pipeline after resume %d\n", + ret); + + /* We're done resuming, from now all IPC have to be sent */ + vfe->block_ipc = false; + + return ret; +} + +/* Probe and remove. */ +static int sof_vfe_probe(struct virtio_device *vdev) +{ + struct virtqueue *vqs[SOF_VIRTIO_NUM_OF_VQS]; + /* the processing callback number must be the same as the vqueues.*/ + vq_callback_t *cbs[SOF_VIRTIO_NUM_OF_VQS] = { + sof_vfe_cmd_tx_done, + sof_vfe_posn_handle_rx, + sof_vfe_handle_data, + }; + struct device *dev = &vdev->dev; + struct sof_vfe *vfe; + int ret; + + /* + * The below two shouldn't be necessary, it's done in + * virtio_pci_modern_probe() by calling dma_set_mask_and_coherent() + */ + + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + if (ret < 0) + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); + if (ret < 0) + dev_warn(dev, "failed to set DMA mask: %d\n", ret); + + vfe = devm_kzalloc(dev, sizeof(*vfe), GFP_KERNEL); + if (!vfe) + return -ENOMEM; + + vdev->priv = vfe; + + INIT_WORK(&vfe->posn_update_work, sof_vfe_posn_update); + init_completion(&vfe->completion); + spin_lock_init(&vfe->vq_lock); + + /* create virt queue for vfe to send/receive IPC message. */ + ret = virtio_find_vqs(vdev, SOF_VIRTIO_NUM_OF_VQS, + vqs, cbs, sof_vq_names, NULL); + if (ret) { + dev_err(dev, "error: find vqs fail with %d\n", ret); + return ret; + } + + /* virtques */ + vfe->ipc_cmd_vq = vqs[SOF_VIRTIO_IPC_CMD_VQ]; + vfe->posn_vq = vqs[SOF_VIRTIO_POSN_VQ]; + vfe->data_vq = vqs[SOF_VIRTIO_DATA_VQ]; + + virtio_device_ready(vdev); + + ret = sof_vfe_init(vdev); + if (ret < 0) + goto free_vqs; + + return 0; + +free_vqs: + vdev->config->del_vqs(vdev); + + return ret; +} + +static void sof_vfe_remove(struct virtio_device *vdev) +{ + /* free virtio resurces and unregister device */ + struct sof_vfe *vfe = vdev->priv; + + sof_vfe_runtime_suspend(&vdev->dev); + + pm_runtime_disable(&vdev->dev); + + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + cancel_work_sync(&vfe->posn_update_work); + + /* unregister the SOF device */ + snd_sof_device_remove(&vdev->dev); + + /* Detach the possibly pre-queued position update buffers */ + virtqueue_detach_unused_buf(vfe->posn_vq); +} + +static void virtaudio_config_changed(struct virtio_device *vdev) +{ +} + +/* + * Need to patch QEMU to create a virtio audio device, e.g. per + * -device virtio-snd-pci,snd=snd0 where Device ID must be + * 0x1040 + VIRTIO_ID_ADSP and Vendor ID = PCI_VENDOR_ID_REDHAT_QUMRANET + */ +static const struct virtio_device_id id_table[] = { + {VIRTIO_ID_ADSP, VIRTIO_DEV_ANY_ID}, + {0}, +}; + +static const struct dev_pm_ops sof_vfe_pm = { + SET_RUNTIME_PM_OPS(sof_vfe_runtime_suspend, sof_vfe_runtime_resume, + NULL) +}; + +static struct virtio_driver sof_vfe_audio_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .pm = &sof_vfe_pm, + }, + .id_table = id_table, + .probe = sof_vfe_probe, + .remove = sof_vfe_remove, + .config_changed = virtaudio_config_changed, +}; + +module_virtio_driver(sof_vfe_audio_driver); + +MODULE_DEVICE_TABLE(virtio, id_table); From patchwork Fri Apr 3 09:14:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193103 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 C070AC43331 for ; Fri, 3 Apr 2020 09:23: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 480D3206B8 for ; Fri, 3 Apr 2020 09:23:24 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="YDLLqKz/" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 480D3206B8 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 9D7CF167D; Fri, 3 Apr 2020 11:22:32 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 9D7CF167D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905802; 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=YDLLqKz/V2rl+HStTZyBrNEcO1/qU7OGszeGr6my/GUtfjREfosDhDcfxTe/OA/5I EnPyK9qxxa80pzHi3Tf9yyY4/miTwamB3GXfz5sMdyLcmMUSiP950mmE8jnTfgWs+M sdp78gO8NEIKn80bvl/3kiwFoDYUeBC2WJbg3aCQ= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id 2A743F80377; Fri, 3 Apr 2020 11:15:09 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id BE250F8014C; Fri, 3 Apr 2020 11:14:45 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 C7EEDF80292; Fri, 3 Apr 2020 11:14:29 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz C7EEDF80292 IronPort-SDR: tQluDVheLqNFcTt0D7HoSrr+/8ihBvLOmjvQYVjQrME2kuHFA0J7YXp3sgPoByrlZAV/4jfSm8 DCQAzP3w3Y8A== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:28 -0700 IronPort-SDR: JPXNB1RWpWtro+54V0GFzpkeqqcJouOKDa5Ui8VqZ9Hlb9ASnTdU7Ci4kdBjwbpFpRFuLvXUxj Bly74Zu2MeLA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451235000" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:26 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 10/12] [RESEND] vhost: add an SOF DSP driver Date: Fri, 3 Apr 2020 11:14:04 +0200 Message-Id: <20200403091406.22381-11-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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 Fri Apr 3 09:14:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Guennadi Liakhovetski X-Patchwork-Id: 193104 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,URIBL_BLOCKED,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 14203C43331 for ; Fri, 3 Apr 2020 09:22: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 92CA6206B8 for ; Fri, 3 Apr 2020 09:22: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="FcTTmbnT" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 92CA6206B8 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 0324D16A4; Fri, 3 Apr 2020 11:21:32 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 0324D16A4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1585905742; bh=w+RoGX5D7zuVqfYS5o7jV0ErsVdif+rEuHmKxFhimTg=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=FcTTmbnTaB0dNP5k3SMcFg4WiBR9drfmcgkYJzFntPontZup2Fr6M4HU/tWJCIGYS dhCoQWM1HiVskZs3p2UjGOzvBngMm3NB1wJvcLKz6+R5BTsnkMEKt4iZlExddFIrY7 k3g9uzGqkC/ToOtfGXpGnSgPq5pzuRMsSUPyNkFg= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id A8A09F80351; Fri, 3 Apr 2020 11:15:06 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 98445F801A3; Fri, 3 Apr 2020 11:14:44 +0200 (CEST) Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) (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 C13ECF8014B; Fri, 3 Apr 2020 11:14:31 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz C13ECF8014B IronPort-SDR: 5xyosLBCuLIci7S+SJi+dDpcRtMs7ekh+Ogv36waR52VzBVVVfWdLfmN3905I36YsqfK0lXAtf 3f/v6prMPiqA== X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga006.fm.intel.com ([10.253.24.20]) by orsmga105.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 03 Apr 2020 02:14:30 -0700 IronPort-SDR: +iQot4nRt77xmjM4jF6FMx55YjEF2YN/6ZQkpeJoszcujeq0bkCWvJ2auapG7KS2WZAVwK6nNG 2hljyNXSbLpg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.72,339,1580803200"; d="scan'208";a="451235010" Received: from gliakhov-mobl2.ger.corp.intel.com (HELO ubuntu.ger.corp.intel.com) ([10.249.36.113]) by fmsmga006.fm.intel.com with ESMTP; 03 Apr 2020 02:14:28 -0700 From: Guennadi Liakhovetski To: alsa-devel@alsa-project.org Subject: [PATCH v2 11/12] [RESEND] ASoC: SOF: VirtIO: free guest pipelines upon termination Date: Fri, 3 Apr 2020 11:14:05 +0200 Message-Id: <20200403091406.22381-12-guennadi.liakhovetski@linux.intel.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> References: <20200403091406.22381-1-guennadi.liakhovetski@linux.intel.com> MIME-Version: 1.0 Cc: Liam Girdwood , Takashi Iwai , Mark Brown , Pierre-Louis Bossart , 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 79d6f8c..1fe1f33 100644 --- a/sound/soc/sof/vhost-be.c +++ b/sound/soc/sof/vhost-be.c @@ -43,6 +43,18 @@ struct dsp_pipeline_connect { struct list_head list; }; +struct dsp_sof_comp_list { + struct list_head list; + uint32_t comp_id; + enum sof_comp_type comp_type; +}; + +struct dsp_sof_pipe_list { + struct list_head list; + uint32_t comp_id; + uint32_t pipe_id; +}; + static const char dsp_pcm_name[] = "VHost PCM"; /* @@ -446,6 +458,75 @@ static int dsp_sof_ipc_comp(struct dsp_sof_client *client, cdata->comp_id >= client->comp_id_end ? -EINVAL : 0; } +void dsp_sof_topology_purge(struct dsp_sof_client *client) +{ + struct snd_sof_dev *sdev = client->sdev; + struct sof_ipc_free fcomp = { + .hdr = { + .size = sizeof(fcomp), + }, + }; + struct sof_ipc_reply reply; + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + int ret; + + pm_runtime_get_sync(sdev->dev); + + /* First free all pipelines */ + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + fcomp.id = pitem->comp_id; + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_PIPE_FREE; + + dev_dbg(sdev->dev, "tplg: unload component ID: %d pipe %u\n", + fcomp.id, pitem->pipe_id); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&pitem->list); + kfree(pitem); + } + + /* Then free all individual components */ + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + fcomp.id = citem->comp_id; + switch (citem->comp_type) { + case SOF_COMP_BUFFER: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | + SOF_IPC_TPLG_COMP_FREE; + } + + dev_dbg(sdev->dev, "tplg: unload component ID: %d type %u\n", + fcomp.id, citem->comp_type); + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, + fcomp.hdr.cmd, &fcomp, sizeof(fcomp), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "error: %d unloading component %d\n", + ret, fcomp.id); + + list_del(&citem->list); + kfree(citem); + } + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); +} +EXPORT_SYMBOL_GPL(dsp_sof_topology_purge); + /* process PM IPC */ static int dsp_sof_ipc_pm(struct dsp_sof_client *client, struct sof_ipc_cmd_hdr *hdr, struct sof_vfe_ipc_power_resp *resp) @@ -526,6 +607,47 @@ int dsp_sof_add_conn(struct snd_sof_dev *sdev, return 0; } +static int dsp_sof_tplg_comp_add(struct dsp_sof_client *client, + struct sof_ipc_comp *comp) +{ + struct dsp_sof_comp_list *citem = kmalloc(sizeof(*citem), GFP_KERNEL); + if (!citem) + return -ENOMEM; + + citem->comp_id = comp->id; + citem->comp_type = comp->type; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d type %x\n", + __func__, citem, comp->id, comp->type); + list_add_tail(&citem->list, &client->comp_list); + + return 0; +} + +static int dsp_sof_tplg_pipe_add(struct dsp_sof_client *client, + struct sof_ipc_pipe_new *pipe) +{ + struct dsp_sof_pipe_list *pitem = kmalloc(sizeof(*pitem), GFP_KERNEL); + if (!pitem) + return -ENOMEM; + + pitem->comp_id = pipe->comp_id; + pitem->pipe_id = pipe->pipeline_id; + + dev_dbg(client->sdev->dev, "%s(): adding %p ID %d pipe %x\n", + __func__, pitem, pipe->comp_id, pipe->pipeline_id); + list_add_tail(&pitem->list, &client->pipe_list); + + return 0; +} + +static int dsp_sof_ipc_tplg_buf_new(struct dsp_sof_client *client, + struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) +{ + struct sof_ipc_comp *comp = container_of(hdr, struct sof_ipc_comp, hdr); + return dsp_sof_tplg_comp_add(client, comp); +} + /* Handle some special cases of the "new component" IPC */ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_cmd_hdr *hdr, struct sof_ipc_reply *rhdr) @@ -535,6 +657,7 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, struct snd_sof_pcm *spcm, *last; struct sof_ipc_comp_host *host; struct dsp_pipeline_connect *conn; + int ret; if (comp->id < client->comp_id_begin || comp->id >= client->comp_id_end) @@ -595,6 +718,10 @@ static int dsp_sof_ipc_tplg_comp_new(struct dsp_sof_client *client, int vq_idx, break; } + ret = dsp_sof_tplg_comp_add(client, comp); + if (ret < 0) + return ret; + return 0; } @@ -606,6 +733,9 @@ static int dsp_sof_ipc_tplg_pipe_new(struct dsp_sof_client *client, int vq_idx, struct sof_ipc_pipe_new, hdr); struct snd_sof_dev *sdev = client->sdev; struct dsp_pipeline_connect *conn; + int ret = dsp_sof_tplg_pipe_add(client, pipeline); + if (ret < 0) + return ret; list_for_each_entry(conn, &sdev->connector_list, list) if (pipeline->pipeline_id == conn->guest_pipeline_id) { @@ -723,6 +853,8 @@ static int dsp_sof_ipc_tplg(struct dsp_sof_client *client, int vq_idx, case SOF_IPC_TPLG_COMP_NEW: return dsp_sof_ipc_tplg_comp_new(client, vq_idx, hdr, reply_buf); + case SOF_IPC_TPLG_BUFFER_NEW: + return dsp_sof_ipc_tplg_buf_new(client, hdr, reply_buf); case SOF_IPC_TPLG_PIPE_NEW: return dsp_sof_ipc_tplg_pipe_new(client, vq_idx, hdr); case SOF_IPC_TPLG_COMP_CONNECT: @@ -987,6 +1119,23 @@ int dsp_sof_set_tplg(struct dsp_sof_client *client, void dsp_sof_suspend(struct snd_sof_dev *sdev) { struct snd_sof_pcm *spcm, *next; + struct dsp_sof_client *client; + + /* Upon resume we'll rebuild lists */ + list_for_each_entry(client, &sdev->vbe_list, list) { + struct dsp_sof_comp_list *citem, *ctmp; + struct dsp_sof_pipe_list *pitem, *ptmp; + + list_for_each_entry_safe(pitem, ptmp, &client->pipe_list, list) { + list_del(&pitem->list); + kfree(pitem); + } + + list_for_each_entry_safe(citem, ctmp, &client->comp_list, list) { + list_del(&citem->list); + kfree(citem); + } + } list_for_each_entry_safe(spcm, next, &sdev->pcm_list, list) if (!strcmp(dsp_pcm_name, spcm->pcm.pcm_name)) { @@ -1000,6 +1149,9 @@ void dsp_sof_suspend(struct snd_sof_dev *sdev) /* A VM instance has closed the miscdevice */ void dsp_sof_client_release(struct dsp_sof_client *client) { + /* If a VM crashes we don't get ioctl(VHOST_SET_RUNNING, 0) from QEMU */ + dsp_sof_topology_purge(client); + bitmap_release_region(client->sdev->vfe_mask, client->id, 0); list_del(&client->list); @@ -1024,6 +1176,9 @@ struct dsp_sof_client *dsp_sof_client_add(struct snd_sof_dev *sdev, return NULL; } + INIT_LIST_HEAD(&client->pipe_list); + INIT_LIST_HEAD(&client->comp_list); + client->sdev = sdev; client->id = id; client->vhost = dsp;