2018-09-10 08:25:20

by Agrawal, Akshu

[permalink] [raw]
Subject: [PATCH 1/2] ASoC: AMD: Fix simultaneous playback and capture on different channel

If capture and playback are started on different channel (I2S/BT)
there is a possibilty that channel information passed from machine driver
is overwritten before the configuration is done in dma driver.
Example:
113.597588: cz_max_startup: ---playback sets BT channel
113.597694: cz_dmic1_startup: ---capture sets I2S channel
113.597979: acp_dma_hw_params: ---configures capture for I2S channel
113.598114: acp_dma_hw_params: ---configures playback for I2S channel

This is fixed by having lock between startup and prepare. This ensures
no other codec startup gets called between a codec's startup(where channel
info is set) and hw_params(where channel info is read).

Signed-off-by: Akshu Agrawal <[email protected]>
---
sound/soc/amd/acp-da7219-max98357a.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)

diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c
index 3879ccc..b98ffbc 100644
--- a/sound/soc/amd/acp-da7219-max98357a.c
+++ b/sound/soc/amd/acp-da7219-max98357a.c
@@ -47,6 +47,7 @@

static struct snd_soc_jack cz_jack;
static struct clk *da7219_dai_clk;
+static struct mutex instance_lock;
extern int bt_uart_enable;

static int cz_da7219_init(struct snd_soc_pcm_runtime *rtd)
@@ -150,6 +151,7 @@ static int cz_da7219_startup(struct snd_pcm_substream *substream)
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);

+ mutex_lock(&instance_lock);
machine->i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL1;
return da7219_clk_enable(substream);
@@ -160,6 +162,12 @@ static void cz_da7219_shutdown(struct snd_pcm_substream *substream)
da7219_clk_disable();
}

+static int cz_da7219_prepare(struct snd_pcm_substream *substream)
+{
+ mutex_unlock(&instance_lock);
+ return 0;
+}
+
static int cz_max_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -177,6 +185,7 @@ static int cz_max_startup(struct snd_pcm_substream *substream)
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);

+ mutex_lock(&instance_lock);
machine->i2s_instance = I2S_BT_INSTANCE;
return da7219_clk_enable(substream);
}
@@ -186,6 +195,12 @@ static void cz_max_shutdown(struct snd_pcm_substream *substream)
da7219_clk_disable();
}

+static int cz_max_prepare(struct snd_pcm_substream *substream)
+{
+ mutex_unlock(&instance_lock);
+ return 0;
+}
+
static int cz_dmic0_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -203,6 +218,7 @@ static int cz_dmic0_startup(struct snd_pcm_substream *substream)
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);

+ mutex_lock(&instance_lock);
machine->i2s_instance = I2S_BT_INSTANCE;
return da7219_clk_enable(substream);
}
@@ -224,6 +240,7 @@ static int cz_dmic1_startup(struct snd_pcm_substream *substream)
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
&constraints_rates);

+ mutex_lock(&instance_lock);
machine->i2s_instance = I2S_SP_INSTANCE;
machine->capture_channel = CAP_CHANNEL0;
return da7219_clk_enable(substream);
@@ -234,24 +251,34 @@ static void cz_dmic_shutdown(struct snd_pcm_substream *substream)
da7219_clk_disable();
}

+static int cz_dmic_prepare(struct snd_pcm_substream *substream)
+{
+ mutex_unlock(&instance_lock);
+ return 0;
+}
+
static const struct snd_soc_ops cz_da7219_cap_ops = {
.startup = cz_da7219_startup,
.shutdown = cz_da7219_shutdown,
+ .prepare = cz_da7219_prepare,
};

static const struct snd_soc_ops cz_max_play_ops = {
.startup = cz_max_startup,
.shutdown = cz_max_shutdown,
+ .prepare = cz_max_prepare,
};

static const struct snd_soc_ops cz_dmic0_cap_ops = {
.startup = cz_dmic0_startup,
.shutdown = cz_dmic_shutdown,
+ .prepare = cz_dmic_prepare,
};

static const struct snd_soc_ops cz_dmic1_cap_ops = {
.startup = cz_dmic1_startup,
.shutdown = cz_dmic_shutdown,
+ .prepare = cz_dmic_prepare,
};

static struct snd_soc_dai_link cz_dai_7219_98357[] = {
@@ -409,6 +436,7 @@ static int cz_probe(struct platform_device *pdev)
card = &cz_card;
cz_card.dev = &pdev->dev;
platform_set_drvdata(pdev, card);
+ mutex_init(&instance_lock);
snd_soc_card_set_drvdata(card, machine);
ret = devm_snd_soc_register_card(&pdev->dev, &cz_card);
if (ret) {
--
1.9.1



2018-09-10 08:10:13

by Agrawal, Akshu

[permalink] [raw]
Subject: [PATCH 2/2] ASoC: AMD: Ensure reset bit is cleared before configuring

HW register descriptions says:
"DMA Channel Reset...Software must confirm that this bit is
cleared before reprogramming any of the channel configuration registers."
There could be cases where dma stop errored out leaving dma channel
in reset state. We need to ensure that before the start of another dma,
channel is out of the reset state.

Signed-off-by: Akshu Agrawal <[email protected]>
---
sound/soc/amd/acp-pcm-dma.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)

diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c
index e359938..77b265b 100644
--- a/sound/soc/amd/acp-pcm-dma.c
+++ b/sound/soc/amd/acp-pcm-dma.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/sizes.h>
#include <linux/pm_runtime.h>

@@ -184,6 +185,24 @@ static void config_dma_descriptor_in_sram(void __iomem *acp_mmio,
acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
}

+static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num)
+{
+ u32 dma_ctrl;
+ int ret;
+
+ /* clear the reset bit */
+ dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
+ dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
+ acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
+ /* check the reset bit before programming configuration registers */
+ ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4),
+ dma_ctrl,
+ !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK),
+ 100, ACP_DMA_RESET_TIME);
+ if (ret < 0)
+ pr_err("Failed to clear reset of channel : %d\n", ch_num);
+}
+
/*
* Initialize the DMA descriptor information for transfer between
* system memory <-> ACP SRAM
@@ -236,6 +255,7 @@ static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio,
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
+ pre_config_reset(acp_mmio, ch);
config_acp_dma_channel(acp_mmio, ch,
dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
@@ -275,6 +295,7 @@ static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size,
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
+ pre_config_reset(acp_mmio, ch);
/* Configure the DMA channel with the above descriptore */
config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
--
1.9.1


2018-09-10 11:36:48

by Mark Brown

[permalink] [raw]
Subject: Applied "ASoC: AMD: Ensure reset bit is cleared before configuring" to the asoc tree

The patch

ASoC: AMD: Ensure reset bit is cleared before configuring

has been applied to the asoc tree at

https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

From 2a665dba016d5493c7d826fec82b0cb643b30d42 Mon Sep 17 00:00:00 2001
From: Akshu Agrawal <[email protected]>
Date: Mon, 10 Sep 2018 13:36:30 +0530
Subject: [PATCH] ASoC: AMD: Ensure reset bit is cleared before configuring

HW register descriptions says:
"DMA Channel Reset...Software must confirm that this bit is
cleared before reprogramming any of the channel configuration registers."
There could be cases where dma stop errored out leaving dma channel
in reset state. We need to ensure that before the start of another dma,
channel is out of the reset state.

Signed-off-by: Akshu Agrawal <[email protected]>
Signed-off-by: Mark Brown <[email protected]>
---
sound/soc/amd/acp-pcm-dma.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)

diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c
index e359938e3d7e..77b265bd0505 100644
--- a/sound/soc/amd/acp-pcm-dma.c
+++ b/sound/soc/amd/acp-pcm-dma.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/sizes.h>
#include <linux/pm_runtime.h>

@@ -184,6 +185,24 @@ static void config_dma_descriptor_in_sram(void __iomem *acp_mmio,
acp_reg_write(descr_info->xfer_val, acp_mmio, mmACP_SRBM_Targ_Idx_Data);
}

+static void pre_config_reset(void __iomem *acp_mmio, u16 ch_num)
+{
+ u32 dma_ctrl;
+ int ret;
+
+ /* clear the reset bit */
+ dma_ctrl = acp_reg_read(acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
+ dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
+ acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0 + ch_num);
+ /* check the reset bit before programming configuration registers */
+ ret = readl_poll_timeout(acp_mmio + ((mmACP_DMA_CNTL_0 + ch_num) * 4),
+ dma_ctrl,
+ !(dma_ctrl & ACP_DMA_CNTL_0__DMAChRst_MASK),
+ 100, ACP_DMA_RESET_TIME);
+ if (ret < 0)
+ pr_err("Failed to clear reset of channel : %d\n", ch_num);
+}
+
/*
* Initialize the DMA descriptor information for transfer between
* system memory <-> ACP SRAM
@@ -236,6 +255,7 @@ static void set_acp_sysmem_dma_descriptors(void __iomem *acp_mmio,
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
+ pre_config_reset(acp_mmio, ch);
config_acp_dma_channel(acp_mmio, ch,
dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
@@ -275,6 +295,7 @@ static void set_acp_to_i2s_dma_descriptors(void __iomem *acp_mmio, u32 size,
config_dma_descriptor_in_sram(acp_mmio, dma_dscr_idx,
&dmadscr[i]);
}
+ pre_config_reset(acp_mmio, ch);
/* Configure the DMA channel with the above descriptore */
config_acp_dma_channel(acp_mmio, ch, dma_dscr_idx - 1,
NUM_DSCRS_PER_CHANNEL,
--
2.19.0.rc1


2018-09-10 11:39:59

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: AMD: Fix simultaneous playback and capture on different channel

On Mon, Sep 10, 2018 at 01:36:29PM +0530, Akshu Agrawal wrote:
> If capture and playback are started on different channel (I2S/BT)
> there is a possibilty that channel information passed from machine driver
> is overwritten before the configuration is done in dma driver.
> Example:
> 113.597588: cz_max_startup: ---playback sets BT channel
> 113.597694: cz_dmic1_startup: ---capture sets I2S channel
> 113.597979: acp_dma_hw_params: ---configures capture for I2S channel
> 113.598114: acp_dma_hw_params: ---configures playback for I2S channel
>
> This is fixed by having lock between startup and prepare. This ensures
> no other codec startup gets called between a codec's startup(where channel
> info is set) and hw_params(where channel info is read).

This isn't viable - the driver will deadlock if the application hits an
error and never gets to startup, or if the application tries to
simultaneously configure two channels (ie, do all the prepares and then
all the parameter configuration and then startup). The DMA driver needs
to remember the configurations for the different channels separately.


Attachments:
(No filename) (1.10 kB)
signature.asc (499.00 B)
Download all attachments

2018-09-10 17:22:58

by Agrawal, Akshu

[permalink] [raw]
Subject: Re: [PATCH 1/2] ASoC: AMD: Fix simultaneous playback and capture on different channel



On 9/10/2018 5:08 PM, Mark Brown wrote:
> On Mon, Sep 10, 2018 at 01:36:29PM +0530, Akshu Agrawal wrote:
>> If capture and playback are started on different channel (I2S/BT)
>> there is a possibilty that channel information passed from machine driver
>> is overwritten before the configuration is done in dma driver.
>> Example:
>> 113.597588: cz_max_startup: ---playback sets BT channel
>> 113.597694: cz_dmic1_startup: ---capture sets I2S channel
>> 113.597979: acp_dma_hw_params: ---configures capture for I2S channel
>> 113.598114: acp_dma_hw_params: ---configures playback for I2S channel
>>
>> This is fixed by having lock between startup and prepare. This ensures
>> no other codec startup gets called between a codec's startup(where channel
>> info is set) and hw_params(where channel info is read).
>
> This isn't viable - the driver will deadlock if the application hits an
> error and never gets to startup, or if the application tries to
> simultaneously configure two channels (ie, do all the prepares and then
> all the parameter configuration and then startup).

We can avoid deadlock by having another mutex_unlock in the shutdown
call of each of codec's ops. Wouldn't in all possible termination
scenarios, it will cleanup and exit via shutdown callback?

Having said that I think there is a better approach to this, is by
having 2 separate instance variable for playback and capture for passing
instance info from machine driver to dma driver. Respective codec in
machine driver will set the capture/playback instance. dma driver on the
basis of substream->stream can read the correct one. No fear of deadlock
in this.

Thanks,
Akshu