From patchwork Sun May 21 18:32:16 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ivan Orlov X-Patchwork-Id: 684738 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org 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 smtp.lore.kernel.org (Postfix) with ESMTPS id 865A7C77B73 for ; Mon, 22 May 2023 06:06:55 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 20BC6208; Mon, 22 May 2023 08:06:03 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 20BC6208 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1684735613; bh=ij7Gdra9AEYefEesAk1rqpdQQWkBuV9QEQS0G5qHBwY=; h=From:To:Cc:Subject:Date:List-Id:List-Archive:List-Help:List-Owner: List-Post:List-Subscribe:List-Unsubscribe:From; b=SVrJeS670F5WbUuPDNvPaC9vyT0wqBAGkdd4ichiAtgt8ygbDYDBY9NPctPOzjrnp uefAMGKCYatjyrMM9H9YbFY5LrDdf8aSLIFuBnHIrNGo4sPjRn2U/f2lDXdM/3ASOO GjPvIqUMTcqc7kH898fm5saE6Wd05vLoazryw+S8= Received: by alsa1.perex.cz (Postfix, from userid 50401) id AE56DF8058C; Mon, 22 May 2023 08:04:59 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id D9506F80580; Mon, 22 May 2023 08:04:58 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 7823CF80425; Sun, 21 May 2023 20:32:49 +0200 (CEST) Received: from mail-ej1-x632.google.com (mail-ej1-x632.google.com [IPv6:2a00:1450:4864:20::632]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 62535F8016A for ; Sun, 21 May 2023 20:32:37 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 62535F8016A Authentication-Results: alsa1.perex.cz; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20221208 header.b=EWwrSP4y Received: by mail-ej1-x632.google.com with SMTP id a640c23a62f3a-96f44435d92so43976666b.0 for ; Sun, 21 May 2023 11:32:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1684693955; x=1687285955; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=XS5dP4/0kxSXRrTOxgf6uiSb5APBJTI1WcIJY11cvhE=; b=EWwrSP4yoKWj11w5mAd/U713VrzEWZrEm6KKG7VWu4iOAU99aGKyEH1sp4sP7YUgbZ AWazM/jChgRvmIHpvWGU/TY0yeXzJ3dCOZ5MQnDK3DJ+gJx5Ks54vccZ/tfdFeLlK6sW E75/wcdCsiyjNHN9TaAom6qVysZajnMZJ3KR7enqwnbwhlsXyLHmKHcRuV2o0Pvxu0j9 q8aStAINClJbbGXsFBpNEwOodkRbXSSSwt1EE98Rf651kkk4Kit/RM005ldvnGkGbjz+ IYhY1JS+QgCZiFVxhJOO9eN7B+44EnrHhEI1LPbi+Nkra1V/Llw7jlyAznweM72cPQX8 braA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1684693955; x=1687285955; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=XS5dP4/0kxSXRrTOxgf6uiSb5APBJTI1WcIJY11cvhE=; b=Pbu9TxRGfQaoZxg2sa13Pbpvy8W6ScLZGN8lg+G7ugolZw9P71l5cB4Udw9sJgOhgo 9HiImCTBAX12QQXdaD/s7MLPxmDfng7U3+AQOnJrJOmlOBXM998TekkOZvwQoVpxdeMW C9VVGyDZpqAuiMI1wCHhGg8tOCJwSe1XRJNOg8htjNiItF9KWA6zLsFoaFn5oyaqeNQ7 B8c4d79z7asgpXvl3a9pGy6XXiziJqlqQ+DmSZJm22C6pzqG8o7bUOzYM8viJia1yisA FXYlcJmPClCnnFmGhRye0StN3eCzQyppZojHSwbpLXxR1/TmVjie8uyF9eYoSHQ3oa4x 94yw== X-Gm-Message-State: AC+VfDzkLAQofI5K1Ghd7l1TIMSK09I2BDH8RsC0ZTMkZsNrQynL1nj9 J28upxN57RIZhDpY8BnFwpQ= X-Google-Smtp-Source: ACHHUZ68z3QpD3OkCVzKvW+WTgMqfEOM6PCjkujbswhOwYZhDtYiHuMNHq2r08OOQlYi70QH+4SmiQ== X-Received: by 2002:a17:906:729e:b0:96f:6590:cbdb with SMTP id b30-20020a170906729e00b0096f6590cbdbmr7686148ejl.6.1684693955078; Sun, 21 May 2023 11:32:35 -0700 (PDT) Received: from ivan-HLYL-WXX9.. ([141.136.93.87]) by smtp.gmail.com with ESMTPSA id mc20-20020a170906eb5400b0096f803afbe3sm2194252ejb.66.2023.05.21.11.32.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 21 May 2023 11:32:34 -0700 (PDT) From: Ivan Orlov To: perex@perex.cz, tiwai@suse.com, corbet@lwn.net, broonie@kernel.org, skhan@linuxfoundation.org Cc: Ivan Orlov , alsa-devel@alsa-project.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, gregkh@linuxfoundation.org, himadrispandya@gmail.com, linux-kernel-mentees@lists.linuxfoundation.org Subject: [PATCH v2 1/3] docs: sound: add 'pcmtest' driver documentation Date: Sun, 21 May 2023 22:32:16 +0400 Message-Id: <20230521183218.9641-1-ivan.orlov0322@gmail.com> X-Mailer: git-send-email 2.34.1 MIME-Version: 1.0 X-MailFrom: ivan.orlov0322@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1 Message-ID-Hash: PUP7BQFWZ75QW5ZQNVSOGFCJAS3V7KNS X-Message-ID-Hash: PUP7BQFWZ75QW5ZQNVSOGFCJAS3V7KNS X-Mailman-Approved-At: Mon, 22 May 2023 06:03:21 +0000 X-Mailman-Version: 3.3.8 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: <> List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add documentation for the new Virtual PCM Test Driver. It covers all possible usage cases: errors and delay injections, random and pattern-based data generation, playback and ioctl redefinition functionalities testing. We have a lot of different virtual media drivers, which can be used for testing of the userspace applications and media subsystem middle layer. However, all of them are aimed at testing the video functionality and simulating the video devices. For audio devices we have only snd-dummy module, which is good in simulating the correct behavior of an ALSA device. I decided to write a tool, which would help to test the userspace ALSA programs (and the PCM middle layer as well) under unusual circumstances to figure out how they would behave. So I came up with this Virtual PCM Test Driver. This new Virtual PCM Test Driver has several features which can be useful during the userspace ALSA applications testing/fuzzing, or testing/fuzzing of the PCM middle layer. Not all of them can be implemented using the existing virtual drivers (like dummy or loopback). Here is what can this driver do: - Simulate both capture and playback processes - Check the playback stream for containing the looped pattern - Generate random or pattern-based capture data - Inject delays into the playback and capturing processes - Inject errors during the PCM callbacks Also, this driver can check the playback stream for containing the predefined pattern, which is used in the corresponding selftest to check the PCM middle layer data transferring functionality. Additionally, this driver redefines the default RESET ioctl, and the selftest covers this PCM API functionality as well. Signed-off-by: Ivan Orlov --- V1 -> V2: - Rename the driver from from 'valsa' to 'pcmtest'. - Implement support for interleaved and non-interleaved access modes - Add support for 8 substreams and 4 channels - Extend supported formats - Extend and rewrite in C the selftest for the driver Documentation/sound/cards/index.rst | 1 + Documentation/sound/cards/pcmtest.rst | 119 ++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 Documentation/sound/cards/pcmtest.rst diff --git a/Documentation/sound/cards/index.rst b/Documentation/sound/cards/index.rst index c016f8c3b88b..49c1f2f688f8 100644 --- a/Documentation/sound/cards/index.rst +++ b/Documentation/sound/cards/index.rst @@ -17,3 +17,4 @@ Card-Specific Information hdspm serial-u16550 img-spdif-in + pcmtest diff --git a/Documentation/sound/cards/pcmtest.rst b/Documentation/sound/cards/pcmtest.rst new file mode 100644 index 000000000000..ea8070eaa44e --- /dev/null +++ b/Documentation/sound/cards/pcmtest.rst @@ -0,0 +1,119 @@ +.. SPDX-License-Identifier: GPL-2.0 + +The Virtual PCM Test Driver +=========================== + +The Virtual PCM Test Driver emulates a generic PCM device, and can be used for +testing/fuzzing of the userspace ALSA applications, as well as for testing/fuzzing of +the PCM middle layer. Additionally, it can be used for simulating hard to reproduce +problems with PCM devices. + +What can this driver do? +~~~~~~~~~~~~~~~~~~~~~~~~ + +At this moment the driver can do the following things: + * Simulate both capture and playback processes + * Generate random or pattern-based capturing data + * Inject delays into the playback and capturing processes + * Inject errors during the PCM callbacks + +It supports up to 8 substreams and 4 channels. Also it supports both interleaved and +non-interleaved access modes. + +Also, this driver can check the playback stream for containing the predefined pattern, +which is used in the corresponding selftest (alsa/test-pcmtest-driver.c). To check the +PCM middle layer data transferring functionality. Additionally, this driver redefines +the default RESET ioctl, and the selftest covers this PCM API functionality as well. + +Configuration +------------- + +The driver has several parameters besides the common ALSA module parameters: + + * fill_mode (bool) - Buffer fill mode (see below) + * inject_delay (int) + * inject_hwpars_err (bool) + * inject_prepare_err (bool) + * inject_trigger_err (bool) + + +Capture Data Generation +----------------------- + +The driver has two modes of data generation: the first (0 in the fill_mode parameter) +means random data generation, the second (1 in the fill_mode) - pattern-based +data generation. Let's look at the second mode. + +First of all, you may want to specify the pattern for data generation. You can do it +by writing the pattern to the debugfs file (/sys/kernel/debug/pcmtest/fill_pattern). +Like that: + +.. code-block:: bash + + echo -n mycoolpattern > /sys/kernel/debug/pcmtest/fill_pattern + +After this, every capture action performed on the 'pcmtest' device will return +'mycoolpatternmycoolpatternmycoolpatternmy...' for every channel buffer. + +In case of interleaved access, the capture buffer will contain the repeated pattern +for every channel. Otherwise, every channel buffer will contain the repeated pattern. + +The pattern itself can be up to 4096 bytes long. + +Delay injection +--------------- + +The driver has 'inject_delay' parameter, which has very self-descriptive name and +can be used for time delay/speedup simulations. The parameter has integer type, and +it means the delay added between module's internal timer ticks. + +If the 'inject_delay' value is positive, the buffer will be filled slower, if it is +negative - faster. You can try it yourself by starting a recording in any +audio recording application (like Audacity) and selecting the 'pcmtest' device as a +source. + +This parameter can be also used for generating a huge amount of sound data in a very +short period of time (with the negative 'inject_delay' value). + +Errors injection +---------------- + +This module can be used for injecting errors into the PCM communication process. This +action can help you to figure out how the userspace ALSA program behaves under unusual +circumstances. + +For example, you can make all 'hw_params' PCM callback calls return EBUSY error by +writing '1' to the 'inject_hwpars_err' module parameter: + +.. code-block:: bash + + echo 1 > /sys/module/snd_pcmtest/parameters/inject_hwpars_err + +Errors can be injected into the following PCM callbacks: + + * hw_params (EBUSY) + * prepare (EINVAL) + * trigger (EINVAL) + + +Playback test +------------- + +This driver can be also used for the playback functionality testing - every time you +write the playback data to the 'pcmtest' PCM device and close it, the driver checks the +buffer of each channel for containing the looped pattern (which is specified in the +fill_pattern debugfs file). If the playback buffer content represents the looped pattern, +'pc_test' debugfs entry is set into '1'. Otherwise, the driver sets it to '0'. + +ioctl redefinition test +----------------------- + +The driver redefines the 'reset' ioctl, which is default for all PCM devices. To test +this functionality, we can trigger the reset ioctl and check the 'ioctl_test' debugfs +entry: + +.. code-block:: bash + + cat /sys/kernel/debug/pcmtest/ioctl_test + +If the ioctl is triggered successfully, this file will contain '1', and '0' otherwise. From patchwork Sun May 21 18:32:17 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ivan Orlov X-Patchwork-Id: 684737 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org 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 smtp.lore.kernel.org (Postfix) with ESMTPS id 00262C7EE23 for ; Mon, 22 May 2023 06:07:22 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id A3A73825; Mon, 22 May 2023 08:06:30 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz A3A73825 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1684735640; bh=pRhlPD6p9Iw2XULUROhQV98Yc31JeHXEmEchJLIE3BI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=L0YHvkfZfMcOitc967LIqbYRtXdaN14KUD2QPMy4ej57xlBL6Ra4qQwFXLy3y2Msb mdrS/niEdvyFlMBwmSb626eZKGMBDApdCZC9ktEDOymWJTaWIe7hb0FfayN/jzJR1v 8UuS2rcWYVx1/L3kHstIpnRmr7qofxWLSo6QU+VY= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 5D9EAF805A0; Mon, 22 May 2023 08:05:01 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 0E721F805A0; Mon, 22 May 2023 08:05:00 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id 2A67DF8026A; Sun, 21 May 2023 20:32:58 +0200 (CEST) Received: from mail-ej1-x629.google.com (mail-ej1-x629.google.com [IPv6:2a00:1450:4864:20::629]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 19C7AF80249 for ; Sun, 21 May 2023 20:32:41 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 19C7AF80249 Authentication-Results: alsa1.perex.cz; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20221208 header.b=Yc4gl/h+ Received: by mail-ej1-x629.google.com with SMTP id a640c23a62f3a-94ea38c90ccso38271866b.1 for ; Sun, 21 May 2023 11:32:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1684693960; x=1687285960; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dAwX0QH9k1Rs1HFfmX8CB0cGrsTVWrWqbNiwVJkp9sY=; b=Yc4gl/h++75T31XbVUzwPJkinMY6f4meWxHbExdhGe59wwIBCge9DNrjL/yDyKYhC+ 549Xkzwt3ewMozDDjw3jeWM+ZGWk59xmbMhrXhG01R2cQcgxWVDvgW+Pn5daJ99gGf/t iRq3nWl4IPYbJwynu56F5LN1j38h7vfIbimzGlQeQ0FgpBRejIEyH3T6fQwgOgzktaJb OUzt00bdvkTuNl3e9LHPfkajCeQyr6k9cfn4p4Fg+Op6wvjzLlM6VLZC/CVKBauM5LK2 aCAKifKcPBsSPVqja2f2spr9MWlkvNt6/UEpMChzrN02PwGsXl8yEH17Rp26Nck7GU1N mkew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1684693960; x=1687285960; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dAwX0QH9k1Rs1HFfmX8CB0cGrsTVWrWqbNiwVJkp9sY=; b=EtgOufd58m6GwXbbBXdvUyVz+U+n0LASV651yfvlhUV2xDNuNdhGm1ydNbzQgxhl0H PQGgFqnD29qvBedmnthFRJxVhMdd32w/mjWb4t/JnR1xyWIubzEYUh6+9+L5f4P7Q/2+ ugNjlC21g5Pr3jB41H2HBAZILx4QWL5q0JrKJNHZTn/qNCZ+8CHpaU22yCB4wv69ar+Q BSb6NAB6vn2oBL5POp6SvwEoNyzCqlAd67BZtzMQP0EbtfLQjiStmDQOosi5AXUvzceC iNqkxcMH0H9HrQmCnKgB+BzbkrGYU5Yj7HZ6CQ/ja68Nxu1SqGc205M5H1by4u0CGmjK +Dbw== X-Gm-Message-State: AC+VfDzfckYL51DWN6JekWpKT2cBJCuw4yF8HgbAeQpiyn/jW3kBKz+k Mpk2ZAzYdGe6VHQVuQd+bvU= X-Google-Smtp-Source: ACHHUZ6pj/2n1eHVmCtCbDC+rhgpm6pdFARPGDbn/s2PYDhHYshM6XXjgP+rFmw3Z4FoE2+CaGcIQQ== X-Received: by 2002:a17:906:739d:b0:965:9db5:3821 with SMTP id f29-20020a170906739d00b009659db53821mr7730565ejl.7.1684693959482; Sun, 21 May 2023 11:32:39 -0700 (PDT) Received: from ivan-HLYL-WXX9.. ([141.136.93.87]) by smtp.gmail.com with ESMTPSA id mc20-20020a170906eb5400b0096f803afbe3sm2194252ejb.66.2023.05.21.11.32.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 21 May 2023 11:32:39 -0700 (PDT) From: Ivan Orlov To: perex@perex.cz, tiwai@suse.com, corbet@lwn.net, broonie@kernel.org, skhan@linuxfoundation.org Cc: Ivan Orlov , alsa-devel@alsa-project.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, gregkh@linuxfoundation.org, himadrispandya@gmail.com, linux-kernel-mentees@lists.linuxfoundation.org Subject: [PATCH v2 2/3] ALSA: Implement the new Virtual PCM Test Driver Date: Sun, 21 May 2023 22:32:17 +0400 Message-Id: <20230521183218.9641-2-ivan.orlov0322@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230521183218.9641-1-ivan.orlov0322@gmail.com> References: <20230521183218.9641-1-ivan.orlov0322@gmail.com> MIME-Version: 1.0 X-MailFrom: ivan.orlov0322@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1 Message-ID-Hash: 2RBN72AIXDCKVR4IUIRTKJYAONXIFEME X-Message-ID-Hash: 2RBN72AIXDCKVR4IUIRTKJYAONXIFEME X-Mailman-Approved-At: Mon, 22 May 2023 06:03:21 +0000 X-Mailman-Version: 3.3.8 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: We have a lot of different virtual media drivers, which can be used for testing of the userspace applications and media subsystem middle layer. However, all of them are aimed at testing the video functionality and simulating the video devices. For audio devices we have only snd-dummy module, which is good in simulating the correct behavior of an ALSA device. I decided to write a tool, which would help to test the userspace ALSA programs (and the PCM middle layer as well) under unusual circumstances to figure out how they would behave. So I came up with this Virtual PCM Test Driver. This new Virtual PCM Test Driver has several features which can be useful during the userspace ALSA applications testing/fuzzing, or testing/fuzzing of the PCM middle layer. Not all of them can be implemented using the existing virtual drivers (like dummy or loopback). Here is what can this driver do: - Simulate both capture and playback processes - Generate random or pattern-based capture data - Inject delays into the playback and capturing processes - Inject errors during the PCM callbacks Also, this driver can check the playback stream for containing the predefined pattern, which is used in the corresponding selftest to check the PCM middle layer data transferring functionality. Additionally, this driver redefines the default RESET ioctl, and the selftest covers this PCM API functionality as well. Signed-off-by: Ivan Orlov --- V1 -> V2: - Rename the driver from 'valsa' to 'pcmtest'. - Implement support for interleaved and non-interleaved access modes - Add support for 8 substreams and 4 channels - Extend supported formats - Extend and rewrite in C the selftest for the driver MAINTAINERS | 8 + sound/drivers/Kconfig | 16 + sound/drivers/Makefile | 2 + sound/drivers/pcmtest.c | 659 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 685 insertions(+) create mode 100644 sound/drivers/pcmtest.c diff --git a/MAINTAINERS b/MAINTAINERS index 27ef11624748..a16ab62ff975 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22419,6 +22419,14 @@ L: linux-fsdevel@vger.kernel.org S: Maintained F: fs/vboxsf/* +VIRTUAL PCM TEST DRIVER +M: Ivan Orlov +L: alsa-devel@alsa-project.org +S: Maintained +F: Documentation/sound/cards/pcmtest.rst +F: sound/drivers/pcmtest.c +F: tools/testing/selftests/alsa/test-pcmtest-driver.c + VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul S: Maintained diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index be3009746f3a..7a0f9f409642 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -109,6 +109,22 @@ config SND_ALOOP To compile this driver as a module, choose M here: the module will be called snd-aloop. +config SND_PCMTEST + tristate "Virtual PCM test driver" + select SND_PCM + help + Say 'Y' or 'M' to include support for the Virtual PCM test driver. + This driver is aimed at extended testing of the userspace applications + which use the ALSA API, as well as the PCM middle layer testing. + + It can generate random or pattern-based data into the capture stream, + check the playback stream for containing the selected pattern, inject + time delays during capture/playback, redefine the RESET ioctl operation + to perform the PCM middle layer testing and inject errors during the + PCM callbacks. It supports both interleaved and non-interleaved access + modes. You can find the corresponding selftest in the 'alsa' + selftests folder. + config SND_VIRMIDI tristate "Virtual MIDI soundcard" depends on SND_SEQUENCER diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index b60303180a1b..2c0c7092d396 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -8,6 +8,7 @@ snd-dummy-objs := dummy.o snd-aloop-objs := aloop.o snd-mtpav-objs := mtpav.o snd-mts64-objs := mts64.o +snd-pcmtest-objs := pcmtest.o snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o snd-serial-generic-objs := serial-generic.o @@ -17,6 +18,7 @@ snd-virmidi-objs := virmidi.o obj-$(CONFIG_SND_DUMMY) += snd-dummy.o obj-$(CONFIG_SND_ALOOP) += snd-aloop.o obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o +obj-$(CONFIG_SND_PCMTEST) += snd-pcmtest.o obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c new file mode 100644 index 000000000000..7b46eb415796 --- /dev/null +++ b/sound/drivers/pcmtest.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtual ALSA driver for PCM testing/fuzzing + * + * Copyright 2023 Ivan Orlov + * + * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer + * testing or fuzzing. + * It can: + * - Simulate 'playback' and 'capture' actions + * - Generate random or pattern-based capture data + * - Check playback buffer for containing looped template, and notify about the results + * through the debugfs entry + * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. + * - Inject errors during the PCM callbacks. + * - Register custom RESET ioctl and notify when it is called through the debugfs entry + * - Work in interleaved and non-interleaved modes + * - Support up to 8 substreams + * - Support up to 4 channels + * - Support framerates from 8 kHz to 48 kHz + * + * When driver works in the capture mode with multiple channels, it duplicates the looped + * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved + * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for + * each channel will contain abacabaabacaba... Same for the non-interleaved mode. + * + * However, it may break the capturing on the higher framerates with small period size, so it is + * better to choose larger period sizes. + * + * You can find the corresponding selftest in the 'alsa' selftests folder. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "pcmtestd" +#define CARD_NAME "pcm-test-card" +#define TIMER_PER_SEC 5 +#define TIMER_INTERVAL (HZ / TIMER_PER_SEC) +#define DELAY_JIFFIES HZ +#define PLAYBACK_SUBSTREAM_CNT 8 +#define CAPTURE_SUBSTREAM_CNT 8 +#define MAX_CHANNELS_NUM 4 + +#define FILL_MODE_RAND 0 +#define FILL_MODE_PAT 1 + +#define MAX_PATTERN_LEN 4096 + +static int index = -1; +static char *id = "pcmtest"; +static bool enable = true; +static int inject_delay; +static bool inject_hwpars_err; +static bool inject_prepare_err; +static bool inject_trigger_err; + +static short fill_mode = FILL_MODE_PAT; +static char fill_pattern[MAX_PATTERN_LEN] = "abacaba"; +static u32 pattern_len = 7; + +static u8 playback_capture_test; +static u8 ioctl_reset_test; +static struct dentry *driver_debug_dir; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard"); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard"); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param(fill_mode, short, 0600); +MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)"); +module_param(inject_delay, int, 0600); +MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)"); +module_param(inject_hwpars_err, bool, 0600); +MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback"); +module_param(inject_prepare_err, bool, 0600); +MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback"); +module_param(inject_trigger_err, bool, 0600); +MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback"); + +struct pcmtst { + struct snd_pcm *pcm; + struct snd_card *card; + struct platform_device *pdev; +}; + +struct pcmtst_buf_iter { + size_t buf_pos; // position in the DMA buffer + size_t period_pos; // period-relative position + size_t b_rw; // Bytes to write on every timer tick + unsigned int sample_bytes; // sample_bits / 8 + bool is_buf_corrupted; // playback test result indicator + size_t period_bytes; // bytes in a one period + bool interleaved; // Interleaved/Non-interleaved mode + size_t total_bytes; // Total bytes read/written + size_t chan_block; // Bytes in one channel buffer when non-interleaved + struct snd_pcm_substream *substream; + struct timer_list timer_instance; +}; + +static struct pcmtst *pcmtst; + +static struct snd_pcm_hardware snd_pcmtst_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = MAX_CHANNELS_NUM, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) +{ + v_iter->total_bytes += by; + v_iter->buf_pos += by; + v_iter->buf_pos %= bytes; +} + +/* + * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos + * every time we write a byte to any channel, so the position in the current channel buffer is + * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel + */ +static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, + unsigned int chan_num) +{ + return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; +} + +/* + * Get the count of bytes written for the current channel in the interleaved mode. + * This is (count of samples written for the current channel) * bytes_in_sample + + * (relative position in the current sample) + */ +static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) +{ + return b_total / channels / b_sample * b_sample + b_total % b_sample; +} + +static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[v_iter->buf_pos]; + if (!current_byte) + break; + if (current_byte != fill_pattern[ch_pos_i(v_iter->total_bytes, runtime->channels, + v_iter->sample_bytes) % pattern_len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + // If we broke during the loop, add remaining bytes to the buffer position. + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + size_t i; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)]; + if (!current_byte) + break; + if (current_byte != fill_pattern[(v_iter->total_bytes / channels) + % pattern_len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +/* + * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is + * necessary because we need to detect when the reading/writing ends, so we assume that the pattern + * doesn't contain zeros. + */ +static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + check_buf_block_i(v_iter, runtime); + else + check_buf_block_ni(v_iter, runtime); +} + +/* + * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... + * The channel buffers lay in the DMA buffer continuously (see default copy_user and copy_kernel + * handlers in the pcm_lib.c file). + * + * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. + * We need this to simulate the correct hardware pointer moving. + */ +static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + unsigned int channels = runtime->channels; + + for (i = 0; i < v_iter->b_rw; i++) { + runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)] = + fill_pattern[(v_iter->total_bytes / channels) % pattern_len]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } +} + +// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... +static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + size_t pos_in_ch; + + for (i = 0; i < v_iter->b_rw; i++) { + pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, + v_iter->sample_bytes); + + runtime->dma_area[v_iter->buf_pos] = fill_pattern[pos_in_ch % pattern_len]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } +} + +static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_pattern_i(v_iter, runtime); + else + fill_block_pattern_n(v_iter, runtime); +} + +static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + // Remaining space in all channel buffers + size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + unsigned int i; + + for (i = 0; i < channels; i++) { + if (v_iter->b_rw <= bytes_remain) { + //b_rw - count of bytes must be written for all channels at each timer tick + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + v_iter->b_rw / channels); + } else { + // Write to the end of buffer and start from the beginning of it + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + bytes_remain / channels); + get_random_bytes(runtime->dma_area + v_iter->chan_block * i, + (v_iter->b_rw - bytes_remain) / channels); + } + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; + + if (v_iter->b_rw <= in_cur_block) { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); + } else { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); + get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_rand_i(v_iter, runtime); + else + fill_block_rand_n(v_iter, runtime); +} + +static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + switch (fill_mode) { + case FILL_MODE_RAND: + fill_block_random(v_iter, runtime); + break; + case FILL_MODE_PAT: + fill_block_pattern(v_iter, runtime); + break; + } +} + +/* + * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. + * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer + * about period elapsed. + */ +static void timer_timeout(struct timer_list *data) +{ + struct pcmtst_buf_iter *v_iter; + struct snd_pcm_substream *substream; + + v_iter = from_timer(v_iter, data, timer_instance); + substream = v_iter->substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) + check_buf_block(v_iter, substream->runtime); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + fill_block(v_iter, substream->runtime); + else + inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes); + + v_iter->period_pos += v_iter->b_rw; + if (v_iter->period_pos >= v_iter->period_bytes) { + v_iter->period_pos %= v_iter->period_bytes; + snd_pcm_period_elapsed(substream); + } + + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay); +} + +static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter; + + v_iter = kzalloc(sizeof(*v_iter), GFP_KERNEL); + if (!v_iter) + return -ENOMEM; + + runtime->hw = snd_pcmtst_hw; + runtime->private_data = v_iter; + v_iter->substream = substream; + v_iter->buf_pos = 0; + v_iter->is_buf_corrupted = false; + v_iter->period_pos = 0; + v_iter->total_bytes = 0; + + playback_capture_test = 0; + ioctl_reset_test = 0; + + timer_setup(&v_iter->timer_instance, timer_timeout, 0); + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL); + return 0; +} + +static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + timer_shutdown_sync(&v_iter->timer_instance); + v_iter->substream = NULL; + playback_capture_test = !v_iter->is_buf_corrupted; + kfree(v_iter); + return 0; +} + +static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter = runtime->private_data; + + if (inject_trigger_err) + return -EINVAL; + + v_iter->sample_bytes = runtime->sample_bits / 8; + v_iter->period_bytes = frames_to_bytes(runtime, runtime->period_size); + if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { + v_iter->chan_block = runtime->dma_bytes / runtime->channels; + v_iter->interleaved = false; + } else { + v_iter->interleaved = true; + } + // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes + v_iter->b_rw = runtime->rate * runtime->sample_bits / 8 / TIMER_PER_SEC * runtime->channels; + + return 0; +} + +static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, v_iter->buf_pos); +} + +static int snd_pcmtst_free(struct pcmtst *pcmtst) +{ + if (!pcmtst) + return 0; + kfree(pcmtst); + return 0; +} + +// These callbacks are required, but empty - all freeing occurs in pdev_remove +static int snd_pcmtst_dev_free(struct snd_device *device) +{ + return 0; +} + +static void pcmtst_pdev_release(struct device *dev) +{ +} + +static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) +{ + if (inject_prepare_err) + return -EINVAL; + return 0; +} + +static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + if (inject_hwpars_err) + return -EBUSY; + return 0; +} + +static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + ioctl_reset_test = 1; + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static const struct snd_pcm_ops snd_pcmtst_playback_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .ioctl = snd_pcmtst_ioctl, + .hw_free = snd_pcmtst_pcm_hw_free, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_pcmtst_capture_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .hw_free = snd_pcmtst_pcm_hw_free, + .ioctl = snd_pcmtst_ioctl, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT, + CAPTURE_SUBSTREAM_CNT, &pcm); + if (err < 0) + return err; + pcm->private_data = pcmtst; + strcpy(pcm->name, "PCMTest"); + pcmtst->pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops); + + err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev, + 0, 128 * 1024); + return err; +} + +static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, + struct pcmtst **r_pcmtst) +{ + struct pcmtst *pcmtst; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_pcmtst_dev_free, + }; + + pcmtst = kzalloc(sizeof(*pcmtst), GFP_KERNEL); + if (!pcmtst) + return -ENOMEM; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); + if (err < 0) + goto _err_free_chip; + + err = snd_pcmtst_new_pcm(pcmtst); + if (err < 0) + goto _err_free_chip; + + *r_pcmtst = pcmtst; + return 0; + +_err_free_chip: + snd_pcmtst_free(pcmtst); + return err; +} + +static int pcmtst_probe(struct platform_device *pdev) +{ + struct snd_card *card; + int err; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + err = snd_pcmtst_create(card, pdev, &pcmtst); + if (err < 0) + return err; + + strcpy(card->driver, "PCM-TEST Driver"); + strcpy(card->shortname, "PCM-Test"); + strcpy(card->longname, "PCM-Test virtual driver"); + + err = snd_card_register(card); + if (err < 0) + return err; + + return 0; +} + +static int pdev_remove(struct platform_device *dev) +{ + snd_pcmtst_free(pcmtst); + return 0; +} + +static struct platform_device pcmtst_pdev = { + .name = "pcmtest", + .dev.release = pcmtst_pdev_release, +}; + +static struct platform_driver pcmtst_pdrv = { + .probe = pcmtst_probe, + .remove = pdev_remove, + .driver = { + .name = "pcmtest", + }, +}; + +static ssize_t pattern_write(struct file *file, const char __user *buff, size_t len, loff_t *off) +{ + ssize_t to_write = len; + + if (*off + to_write > MAX_PATTERN_LEN) + to_write = MAX_PATTERN_LEN - *off; + + // Crop silently everything over the buffer + if (to_write <= 0) + return len; + + if (copy_from_user(fill_pattern + *off, buff, to_write)) + return -EFAULT; + pattern_len = *off + to_write; + *off += to_write; + + return to_write; +} + +static ssize_t pattern_read(struct file *file, char __user *buff, size_t len, loff_t *off) +{ + ssize_t to_read = len; + + if (*off + to_read >= MAX_PATTERN_LEN) + to_read = MAX_PATTERN_LEN - *off; + if (to_read <= 0) + return 0; + + if (copy_to_user(buff, fill_pattern + *off, to_read)) + to_read = 0; + else + *off += to_read; + + return to_read; +} + +static const struct file_operations fill_pattern_fops = { + .read = pattern_read, + .write = pattern_write, +}; + +static int init_debug_files(void) +{ + driver_debug_dir = debugfs_create_dir("pcmtest", NULL); + if (IS_ERR(driver_debug_dir)) + return PTR_ERR(driver_debug_dir); + debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test); + debugfs_create_u32("pattern_len", 0444, driver_debug_dir, &pattern_len); + debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test); + debugfs_create_file("fill_pattern", 0600, driver_debug_dir, NULL, &fill_pattern_fops); + + return 0; +} + +static void clear_debug_files(void) +{ + debugfs_remove_recursive(driver_debug_dir); +} + +static int __init mod_init(void) +{ + int err = 0; + + err = init_debug_files(); + if (err) + return err; + err = platform_device_register(&pcmtst_pdev); + if (err) + return err; + err = platform_driver_register(&pcmtst_pdrv); + if (err) + platform_device_unregister(&pcmtst_pdev); + return err; +} + +static void __exit mod_exit(void) +{ + clear_debug_files(); + + platform_driver_unregister(&pcmtst_pdrv); + platform_device_unregister(&pcmtst_pdev); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Orlov"); +module_init(mod_init); +module_exit(mod_exit); From patchwork Sun May 21 18:32:18 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ivan Orlov X-Patchwork-Id: 685084 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org 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 smtp.lore.kernel.org (Postfix) with ESMTPS id 6BC4EC77B73 for ; Mon, 22 May 2023 06:07:36 +0000 (UTC) Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id 0FF63843; Mon, 22 May 2023 08:06:44 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz 0FF63843 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1684735654; bh=dDEcHYLyy0QTD6l3cYB5knZmpkGcESUUVqZa7/nMB0Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=hthfnqPn16TUP3I3vi3KZHMRUbECf0i6laruo8xxor30CBdmunaVQNL9QQ7Inr66Q /7hcvd7nvidB+ahNhRMb+Et7o93D4VeRrInlWK/Y2WunC6TcYD/MxYXDWPcfwclayn XVBIvduPnPuS+eLzw6OPP9UcNLHCkl5GhTvh+LP0= Received: by alsa1.perex.cz (Postfix, from userid 50401) id 320E8F805B6; Mon, 22 May 2023 08:05:02 +0200 (CEST) Received: from mailman-core.alsa-project.org (mailman-core.alsa-project.org [10.254.200.10]) by alsa1.perex.cz (Postfix) with ESMTP id 72FEBF805AE; Mon, 22 May 2023 08:05:01 +0200 (CEST) Received: by alsa1.perex.cz (Postfix, from userid 50401) id D0909F8024E; Sun, 21 May 2023 20:33:01 +0200 (CEST) Received: from mail-ej1-x636.google.com (mail-ej1-x636.google.com [IPv6:2a00:1450:4864:20::636]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 83D27F80086 for ; Sun, 21 May 2023 20:32:46 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 83D27F80086 Authentication-Results: alsa1.perex.cz; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20221208 header.b=oeRP94lm Received: by mail-ej1-x636.google.com with SMTP id a640c23a62f3a-9659dee48edso38932966b.0 for ; Sun, 21 May 2023 11:32:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1684693964; x=1687285964; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=DJyNPSTUUMs7c0Obxl2iPIkMqeFcBT3JyMI/OJTExCU=; b=oeRP94lmPulozu6xqgST0HHsh50XkoJI9n4qE1bIWCh6jzYkr0N9NXExuGmniBYAPX ZRKojyQHqCtykyyhW67rgGWuG69ji5zTpG9sXciU5W67in4bMVzGQ3+mvoP4qilldF7M qAPtnXkcBle1gINtmWl0H6k47GolhPMrlHRgiR/AXd4wDlyrosCgttywKoKP6iVOA577 vcq+MeuJyzz+mt489EyLamaXEOhUn/QjzwnY34UDAOqi5UScKK0cToA05tOz0RmOsUTh P5WbvjErp1zf+R8ZtQ2JMAsm7Et0Nu1quGf/0WINsDUOpcxwrkGZZi5s90oJ1hWCp8wE 09bw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1684693964; x=1687285964; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=DJyNPSTUUMs7c0Obxl2iPIkMqeFcBT3JyMI/OJTExCU=; b=B3ufYiSKQUaizx4REakBm46r24v5I0rd/mdKlkQ+chTiItdjxqwXvR969yljeNBH8f aMFJnInEx3fk/+TPMqfUkm7j8TDkhB8i0+TWuEO+jdzY0H36E1MAKgWUWAtWUAHJwVHp UDEC1IuDqLpwVcFpiooIw+Sxjx+VAJ94ICHtePoJPehBRnxquFm1NDbMCkYWtVnVbq33 8UWwPtgFR3h9wM2k2Wn+WVG/ORRdJrFW2BxIDnVVcjJh95/SnqHXiDtQqjyDdpVz0XH5 Nwprp0l+mxa1Kqkw7IsVEa+ClL7vs0NuVpqdep2xU6mN8jM5ZHMSIk/g4h7v4dFSZUPe ITWA== X-Gm-Message-State: AC+VfDx0XLa2hTSu1+HZmapsLJ/ZQn/7cKGIESML782gp+hMV/8iH5aZ iEoZsEej1NaWQykQDzaxDMQ= X-Google-Smtp-Source: ACHHUZ5tZW02l7qh1zYz1oas2FtUVTMTsN9MEpoDHXP7SP6vSX/umWDeP7xhLcIi562KWZV15xQR5A== X-Received: by 2002:a17:906:6495:b0:8d2:78c5:1d4e with SMTP id e21-20020a170906649500b008d278c51d4emr8723407ejm.5.1684693964448; Sun, 21 May 2023 11:32:44 -0700 (PDT) Received: from ivan-HLYL-WXX9.. ([141.136.93.87]) by smtp.gmail.com with ESMTPSA id mc20-20020a170906eb5400b0096f803afbe3sm2194252ejb.66.2023.05.21.11.32.43 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 21 May 2023 11:32:44 -0700 (PDT) From: Ivan Orlov To: perex@perex.cz, tiwai@suse.com, corbet@lwn.net, broonie@kernel.org, skhan@linuxfoundation.org Cc: Ivan Orlov , alsa-devel@alsa-project.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, gregkh@linuxfoundation.org, himadrispandya@gmail.com, linux-kernel-mentees@lists.linuxfoundation.org Subject: [PATCH v2 3/3] selftests: ALSA: Add test for the 'pcmtest' driver Date: Sun, 21 May 2023 22:32:18 +0400 Message-Id: <20230521183218.9641-3-ivan.orlov0322@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230521183218.9641-1-ivan.orlov0322@gmail.com> References: <20230521183218.9641-1-ivan.orlov0322@gmail.com> MIME-Version: 1.0 X-MailFrom: ivan.orlov0322@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-alsa-devel.alsa-project.org-0; header-match-alsa-devel.alsa-project.org-1 Message-ID-Hash: N3NGXV3I4RZ6TUTBBNUWBWQ6HZIC5VAP X-Message-ID-Hash: N3NGXV3I4RZ6TUTBBNUWBWQ6HZIC5VAP X-Mailman-Approved-At: Mon, 22 May 2023 06:03:21 +0000 X-Mailman-Version: 3.3.8 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This test covers the new Virtual PCM Test Driver, including the capturing, playback and ioctl redefinition functionalities for both interleaved and non-interleaved access modes. This test is also helpful as an usage example of the 'pcmtest' driver. We have a lot of different virtual media drivers, which can be used for testing of the userspace applications and media subsystem middle layer. However, all of them are aimed at testing the video functionality and simulating the video devices. For audio devices we have only snd-dummy module, which is good in simulating the correct behavior of an ALSA device. I decided to write a tool, which would help to test the userspace ALSA programs (and the PCM middle layer as well) under unusual circumstances to figure out how they would behave. So I came up with this Virtual PCM Test Driver. This new Virtual PCM Test Driver has several features which can be useful during the userspace ALSA applications testing/fuzzing, or testing/fuzzing of the PCM middle layer. Not all of them can be implemented using the existing virtual drivers (like dummy or loopback). Here is what can this driver do: - Simulate both capture and playback processes - Generate random or pattern-based capture data - Check the playback stream for containing the looped pattern - Inject delays into the playback and capturing processes - Inject errors during the PCM callbacks Also, this driver can check the playback stream for containing the predefined pattern, which is used in the corresponding selftest to check the PCM middle layer data transferring functionality. Additionally, this driver redefines the default RESET ioctl, and the selftest covers this PCM API functionality as well. After all, I think this driver would be a good start, and I believe in the future we will see more virtual sound drivers. Signed-off-by: Ivan Orlov --- V1 -> V2: - Rename the driver from 'valsa' to 'pcmtest'. - Implement support for interleaved and non-interleaved access modes - Add support for 8 substreams and 4 channels - Extend supported formats - Extend and rewrite in C the selftest for the driver tools/testing/selftests/alsa/Makefile | 2 +- .../selftests/alsa/test-pcmtest-driver.c | 323 ++++++++++++++++++ 2 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/alsa/test-pcmtest-driver.c diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 901949db80ad..5af9ba8a4645 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -12,7 +12,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test +TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver TEST_GEN_PROGS_EXTENDED := libatest.so diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c new file mode 100644 index 000000000000..47256e58db93 --- /dev/null +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This is the test which covers PCM middle layer data transferring using + * the virtual pcm test driver (snd-pcmtest). + * + * Copyright 2023 Ivan Orlov + */ +#include +#include +#include "../kselftest_harness.h" + +#define NI_CH 4 + +static char pattern_buf[4096]; +static unsigned int pattern_len; + +struct pcmtest_test_params { + unsigned long buffer_size; + unsigned long period_size; + unsigned long channels; + unsigned int rate; + snd_pcm_access_t access; + size_t sample_len; + int time; + snd_pcm_format_t format; +}; + +static int read_pattern(void) +{ + FILE *fp, *fpl; + + fpl = fopen("/sys/kernel/debug/pcmtest/pattern_len", "r"); + if (!fpl) + return -1; + fscanf(fpl, "%u", &pattern_len); + fclose(fpl); + + fp = fopen("/sys/kernel/debug/pcmtest/fill_pattern", "r"); + if (!fp) { + fclose(fpl); + return -1; + } + fread(pattern_buf, 1, pattern_len, fp); + fclose(fp); + + return 0; +} + +static int get_test_results(char *debug_name) +{ + int result; + FILE *f; + char fname[128]; + + sprintf(fname, "/sys/kernel/debug/pcmtest/%s", debug_name); + + f = fopen(fname, "r"); + if (!f) { + printf("Failed to open file\n"); + return -1; + } + fscanf(f, "%d", &result); + fclose(f); + + return result; +} + +static size_t get_sample_len(unsigned int rate, unsigned long channels, snd_pcm_format_t format) +{ + return rate * channels * snd_pcm_format_physical_width(format) / 8; +} + +static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, + snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, + int card, snd_pcm_stream_t stream) +{ + char pcm_name[32]; + int err; + + sprintf(pcm_name, "hw:%d,0,0", card); + err = snd_pcm_open(handle, pcm_name, stream, 0); + if (err < 0) + return err; + snd_pcm_hw_params_any(*handle, hwparams); + snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); + snd_pcm_hw_params_set_access(*handle, hwparams, params->access); + snd_pcm_hw_params_set_format(*handle, hwparams, params->format); + snd_pcm_hw_params_set_channels(*handle, hwparams, params->channels); + snd_pcm_hw_params_set_rate_near(*handle, hwparams, ¶ms->rate, 0); + snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); + snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); + snd_pcm_hw_params(*handle, hwparams); + snd_pcm_sw_params_current(*handle, swparams); + + snd_pcm_hw_params_set_rate_resample(*handle, hwparams, 0); + snd_pcm_sw_params_set_avail_min(*handle, swparams, params->period_size); + snd_pcm_hw_params_set_buffer_size_near(*handle, hwparams, ¶ms->buffer_size); + snd_pcm_hw_params_set_period_size_near(*handle, hwparams, ¶ms->period_size, 0); + snd_pcm_sw_params(*handle, swparams); + snd_pcm_hw_params(*handle, hwparams); + + return 0; +} + +FIXTURE(pcmtest) { + int card; + snd_pcm_sw_params_t *swparams; + snd_pcm_hw_params_t *hwparams; + struct pcmtest_test_params params; +}; + +FIXTURE_TEARDOWN(pcmtest) { +} + +FIXTURE_SETUP(pcmtest) { + char *card_name; + int err; + + if (geteuid()) + SKIP(exit(-1), "This test needs root to run!"); + + err = read_pattern(); + if (err) + SKIP(exit(-1), "Can't read pattern. Probably, module isn't loaded"); + + card_name = malloc(127); + ASSERT_NE(card_name, NULL); + self->params.buffer_size = 16384; + self->params.period_size = 4096; + self->params.channels = 1; + self->params.rate = 8000; + self->params.access = SND_PCM_ACCESS_RW_INTERLEAVED; + self->params.format = SND_PCM_FORMAT_S16_LE; + self->card = -1; + + self->params.sample_len = get_sample_len(self->params.rate, self->params.channels, + self->params.format); + self->params.time = 4; + + while (snd_card_next(&self->card) >= 0) { + if (self->card == -1) + break; + snd_card_get_name(self->card, &card_name); + if (!strcmp(card_name, "PCM-Test")) + break; + } + free(card_name); + ASSERT_NE(self->card, -1); +} + +/* + * Here we are trying to send the looped monotonically increasing sequence of bytes to the driver. + * If our data isn't corrupted, the driver will set the content of 'pc_test' debugfs file to '1' + */ +TEST_F(pcmtest, playback) { + snd_pcm_t *handle; + unsigned char *it; + size_t write_res; + int test_results; + int i; + unsigned short *rsamples; + void *samples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(self->params.sample_len * self->params.time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_PLAYBACK), 0); + snd_pcm_format_set_silence(params->format, samples, + params->rate * params->channels * params->time); + it = samples; + for (i = 0; i < self->params.sample_len * params->time; i++) + it[i] = pattern_buf[i % pattern_len]; + rsamples = samples; + for (i = 0; i < params->time; i++) { + write_res = snd_pcm_writei(handle, rsamples, params->rate); + rsamples += params->rate; + ASSERT_GE(write_res, 0); + } + + snd_pcm_close(handle); + free(samples); + test_results = get_test_results("pc_test"); + ASSERT_EQ(test_results, 1); +} + +/* + * Here we test that the virtual alsa driver returns looped and monotonically increasing sequence + * of bytes + */ +TEST_F(pcmtest, capture) { + snd_pcm_t *handle; + unsigned char *it; + size_t read_res; + int i; + void *samples; + unsigned short *rsamples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(self->params.sample_len * self->params.time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + params, self->card, SND_PCM_STREAM_CAPTURE), 0); + snd_pcm_format_set_silence(params->format, samples, + params->rate * params->channels * params->time); + rsamples = samples; + for (i = 0; i < params->time; i++) { + read_res = snd_pcm_readi(handle, rsamples, params->rate); + rsamples += params->rate; + ASSERT_GE(read_res, 0); + } + snd_pcm_close(handle); + it = (unsigned char *)samples; + for (i = 0; i < self->params.sample_len * self->params.time; i++) + ASSERT_EQ(it[i], pattern_buf[i % pattern_len]); + free(samples); +} + +// Test capture in the non-interleaved access mode. +TEST_F(pcmtest, ni_capture) { + snd_pcm_t *handle; + struct pcmtest_test_params params = self->params; + char **chan_samples; + size_t i, j, read_res; + + params.channels = NI_CH; + chan_samples = calloc(NI_CH, sizeof(*chan_samples)); + ASSERT_NE(chan_samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + ¶ms, self->card, SND_PCM_STREAM_CAPTURE), 0); + + for (i = 0; i < NI_CH; i++) + chan_samples[i] = calloc(params.sample_len * params.time, 1); + + for (i = 0; i < 1; i++) { + read_res = snd_pcm_readn(handle, (void **)chan_samples, params.rate * params.time); + ASSERT_GE(read_res, 0); + } + snd_pcm_close(handle); + + for (i = 0; i < NI_CH; i++) { + for (j = 0; j < params.rate * params.time; j++) + ASSERT_EQ(chan_samples[i][j], pattern_buf[j % pattern_len]); + free(chan_samples[i]); + } + free(chan_samples); +} + +TEST_F(pcmtest, ni_playback) { + snd_pcm_t *handle; + struct pcmtest_test_params params = self->params; + char **chan_samples; + size_t i, j, read_res; + int test_res; + + params.channels = NI_CH; + chan_samples = calloc(NI_CH, sizeof(*chan_samples)); + ASSERT_NE(chan_samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + params.access = SND_PCM_ACCESS_RW_NONINTERLEAVED; + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, + ¶ms, self->card, SND_PCM_STREAM_PLAYBACK), 0); + + for (i = 0; i < NI_CH; i++) { + chan_samples[i] = calloc(params.sample_len * params.time, 1); + for (j = 0; j < params.sample_len * params.time; j++) + chan_samples[i][j] = pattern_buf[j % pattern_len]; + } + + for (i = 0; i < 1; i++) { + read_res = snd_pcm_writen(handle, (void **)chan_samples, params.rate * params.time); + ASSERT_GE(read_res, 0); + } + + snd_pcm_close(handle); + test_res = get_test_results("pc_test"); + ASSERT_EQ(test_res, 1); + + for (i = 0; i < NI_CH; i++) + free(chan_samples[i]); + free(chan_samples); +} + +/* + * Here we are testing the custom ioctl definition inside the virtual driver. If it triggers + * successfully, the driver sets the content of 'ioctl_test' debugfs file to '1'. + */ +TEST_F(pcmtest, reset_ioctl) { + snd_pcm_t *handle; + unsigned char *it; + int test_res; + struct pcmtest_test_params *params = &self->params; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + snd_pcm_reset(handle); + test_res = get_test_results("ioctl_test"); + ASSERT_EQ(test_res, 1); + snd_pcm_close(handle); +} + +TEST_HARNESS_MAIN