ALSA: PCM: volume API implementation

Introduced a new helper function snd_pcm_add_volume_ctls() to
create control elements representing the volume for each PCM
(sub)stream.

This patch also squashes the logic implemented by below change-

ASoC: Fix freed memory access of pcm stream kctl

Consider sound card instantiate fails due to
audrx init failure. In such case, all dais/ctls
are de-registered and freed. But as part of it,
access to unregistered ctls for pcm_chmap and similar
controls result in crash.  Ctls are freed at disconnection
but the disconnect is called only when it was registered.

CRs-Fixed: 1038054
Change-Id: Ief8817b4ec000c058d46aa021977b7c6003c0011
Signed-off-by: Laxminath Kasam <lkasam@codeaurora.org>
Signed-off-by: Damir Didjusto <damird@codeaurora.org>
Signed-off-by: Banajit Goswami <bgoswami@codeaurora.org>
Signed-off-by: Sudheer Papothi <spapothi@codeaurora.org>
Signed-off-by: Meng Wang <mwang@codeaurora.org>
This commit is contained in:
Meng Wang 2017-09-06 11:22:21 +08:00 • committed by Meng Wang
commit a1682a646a
3 changed files with 132 additions and 0 deletions

View file

@ -501,6 +501,9 @@ struct snd_pcm_str {
#endif
#endif
struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
#ifdef CONFIG_AUDIO_QGKI
struct snd_kcontrol *vol_kctl; /* volume controls */
#endif
struct device dev;
};
@ -1417,6 +1420,32 @@ static inline u64 pcm_format_to_bits(snd_pcm_format_t pcm_format)
return 1ULL << (__force int) pcm_format;
}
#ifdef CONFIG_AUDIO_QGKI
/*
* PCM Volume control API
*/
/* array element of volume */
struct snd_pcm_volume_elem {
int volume;
};
/* pp information; retrieved via snd_kcontrol_chip() */
struct snd_pcm_volume {
struct snd_pcm *pcm; /* assigned PCM instance */
int stream; /* PLAYBACK or CAPTURE */
struct snd_kcontrol *kctl;
const struct snd_pcm_volume_elem *volume;
int max_length;
void *private_data; /* optional: private data pointer */
};
int snd_pcm_add_volume_ctls(struct snd_pcm *pcm, int stream,
const struct snd_pcm_volume_elem *volume,
int max_length,
unsigned long private_value,
struct snd_pcm_volume **info_ret);
#endif
/* printk helpers */
#define pcm_err(pcm, fmt, args...) \
dev_err((pcm)->card->dev, fmt, ##args)

View file

@ -816,6 +816,12 @@ static void free_chmap(struct snd_pcm_str *pstr)
snd_ctl_remove(pstr->pcm->card, pstr->chmap_kctl);
pstr->chmap_kctl = NULL;
}
#ifdef CONFIG_AUDIO_QGKI
if (pstr->vol_kctl) {
snd_ctl_remove(pstr->pcm->card, pstr->vol_kctl);
pstr->vol_kctl = NULL;
}
#endif
}
static void snd_pcm_free_stream(struct snd_pcm_str * pstr)

View file

@ -30,6 +30,10 @@
#define trace_applptr(substream, prev, curr)
#endif
#ifdef CONFIG_AUDIO_QGKI
#define STRING_LENGTH_OF_INT 12
#endif
static int fill_silence_frames(struct snd_pcm_substream *substream,
snd_pcm_uframes_t off, snd_pcm_uframes_t frames);
@ -2400,6 +2404,26 @@ static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
kfree(info);
}
#ifdef CONFIG_AUDIO_QGKI
static int pcm_volume_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 0x2000;
return 0;
}
static void pcm_volume_ctl_private_free(struct snd_kcontrol *kcontrol)
{
struct snd_pcm_volume *info = snd_kcontrol_chip(kcontrol);
info->pcm->streams[info->stream].vol_kctl = NULL;
kfree(info);
}
#endif
/**
* snd_pcm_add_chmap_ctls - create channel-mapping control elements
* @pcm: the assigned PCM instance
@ -2461,3 +2485,76 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
return 0;
}
EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);
#ifdef CONFIG_AUDIO_QGKI
/**
* snd_pcm_add_volume_ctls - create volume control elements
* @pcm: the assigned PCM instance
* @stream: stream direction
* @max_length: the max length of the volume parameter of stream
* @private_value: the value passed to each kcontrol's private_value field
* @info_ret: store struct snd_pcm_volume instance if non-NULL
*
* Create volume control elements assigned to the given PCM stream(s).
* Returns zero if succeed, or a negative error value.
*/
int snd_pcm_add_volume_ctls(struct snd_pcm *pcm, int stream,
const struct snd_pcm_volume_elem *volume,
int max_length,
unsigned long private_value,
struct snd_pcm_volume **info_ret)
{
struct snd_pcm_volume *info;
struct snd_kcontrol_new knew = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = pcm_volume_ctl_info,
};
int err;
int size;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->pcm = pcm;
info->stream = stream;
info->volume = volume;
info->max_length = max_length;
size = sizeof("Playback ") + sizeof(" Volume") +
STRING_LENGTH_OF_INT*sizeof(char) + 1;
knew.name = kzalloc(size, GFP_KERNEL);
if (!knew.name) {
kfree(info);
return -ENOMEM;
}
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
snprintf((char *)knew.name, size, "%s %d %s",
"Playback", pcm->device, "Volume");
else
snprintf((char *)knew.name, size, "%s %d %s",
"Capture", pcm->device, "Volume");
knew.device = pcm->device;
knew.count = pcm->streams[stream].substream_count;
knew.private_value = private_value;
info->kctl = snd_ctl_new1(&knew, info);
if (!info->kctl) {
kfree(info);
kfree(knew.name);
return -ENOMEM;
}
info->kctl->private_free = pcm_volume_ctl_private_free;
err = snd_ctl_add(pcm->card, info->kctl);
if (err < 0) {
kfree(info);
kfree(knew.name);
return -ENOMEM;
}
pcm->streams[stream].vol_kctl = info->kctl;
if (info_ret)
*info_ret = info;
kfree(knew.name);
return 0;
}
EXPORT_SYMBOL(snd_pcm_add_volume_ctls);
#endif