Skip to content

Commit

Permalink
ALSA: hda - Allocate hda_pcm objects dynamically
Browse files Browse the repository at this point in the history
So far, the hda_codec object kept the hda_pcm list in an array, and
the codec driver was expected to assign the array.  However, this
makes the object life cycle management harder, because the assigned
array is freed at the codec driver detach while it might be still
accessed by the opened streams.

In this patch, we allocate each hda_pcm object dynamically and manage
it as a linked list.  Each object has a kref refcount, and both the
codec driver binder and the PCM open/close touches it, so that the
object won't be freed while in use.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
Takashi Iwai committed Mar 3, 2015
1 parent f4de8fe commit bbbc7e8
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 103 deletions.
116 changes: 76 additions & 40 deletions sound/pci/hda/hda_codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,60 @@ get_hda_cvt_setup(struct hda_codec *codec, hda_nid_t nid)
return p;
}

/*
* PCM device
*/
static void release_pcm(struct kref *kref)
{
struct hda_pcm *pcm = container_of(kref, struct hda_pcm, kref);

if (pcm->pcm)
snd_device_free(pcm->codec->card, pcm->pcm);
clear_bit(pcm->device, pcm->codec->bus->pcm_dev_bits);
kfree(pcm->name);
kfree(pcm);
}

void snd_hda_codec_pcm_put(struct hda_pcm *pcm)
{
kref_put(&pcm->kref, release_pcm);
}
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_put);

struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
const char *fmt, ...)
{
struct hda_pcm *pcm;
va_list args;

va_start(args, fmt);
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return NULL;

pcm->codec = codec;
kref_init(&pcm->kref);
pcm->name = kvasprintf(GFP_KERNEL, fmt, args);
if (!pcm->name) {
kfree(pcm);
return NULL;
}

list_add_tail(&pcm->list, &codec->pcm_list_head);
return pcm;
}
EXPORT_SYMBOL_GPL(snd_hda_codec_pcm_new);

static void codec_release_pcms(struct hda_codec *codec)
{
struct hda_pcm *pcm, *n;

list_for_each_entry_safe(pcm, n, &codec->pcm_list_head, list) {
list_del_init(&pcm->list);
snd_hda_codec_pcm_put(pcm);
}
}

/*
* codec destructor
*/
Expand All @@ -1124,6 +1178,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
if (!codec)
return;
cancel_delayed_work_sync(&codec->jackpoll_work);
codec_release_pcms(codec);
if (device_is_registered(hda_codec_dev(codec)))
device_del(hda_codec_dev(codec));
snd_hda_jack_tbl_clear(codec);
Expand Down Expand Up @@ -1251,6 +1306,7 @@ int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card,
snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16);
snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8);
INIT_LIST_HEAD(&codec->conn_list);
INIT_LIST_HEAD(&codec->pcm_list_head);

INIT_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);
codec->depop_delay = -1;
Expand Down Expand Up @@ -2370,9 +2426,8 @@ int snd_hda_lock_devices(struct hda_bus *bus)
goto err_clear;

list_for_each_entry(codec, &bus->codec_list, list) {
int pcm;
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
struct hda_pcm *cpcm;
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
if (!cpcm->pcm)
continue;
if (cpcm->pcm->streams[0].substream_opened ||
Expand Down Expand Up @@ -2419,8 +2474,6 @@ EXPORT_SYMBOL_GPL(snd_hda_unlock_devices);
int snd_hda_codec_reset(struct hda_codec *codec)
{
struct hda_bus *bus = codec->bus;
struct snd_card *card = codec->card;
int i;

if (snd_hda_lock_devices(bus) < 0)
return -EBUSY;
Expand All @@ -2429,14 +2482,7 @@ int snd_hda_codec_reset(struct hda_codec *codec)
cancel_delayed_work_sync(&codec->jackpoll_work);
flush_workqueue(bus->workq);
snd_hda_ctls_clear(codec);
/* release PCMs */
for (i = 0; i < codec->num_pcms; i++) {
if (codec->pcm_info[i].pcm) {
snd_device_free(card, codec->pcm_info[i].pcm);
clear_bit(codec->pcm_info[i].device,
bus->pcm_dev_bits);
}
}
codec_release_pcms(codec);
snd_hda_detach_beep_device(codec);
if (device_is_registered(hda_codec_dev(codec)))
device_del(hda_codec_dev(codec));
Expand All @@ -2454,8 +2500,6 @@ int snd_hda_codec_reset(struct hda_codec *codec)
snd_array_free(&codec->cvt_setups);
snd_array_free(&codec->spdif_out);
snd_array_free(&codec->verbs);
codec->num_pcms = 0;
codec->pcm_info = NULL;
codec->preset = NULL;
codec->slave_dig_outs = NULL;
codec->spdif_status_reset = 0;
Expand Down Expand Up @@ -3952,12 +3996,12 @@ static void hda_call_codec_resume(struct hda_codec *codec)
static int hda_codec_runtime_suspend(struct device *dev)
{
struct hda_codec *codec = dev_to_hda_codec(dev);
struct hda_pcm *pcm;
unsigned int state;
int i;

cancel_delayed_work_sync(&codec->jackpoll_work);
for (i = 0; i < codec->num_pcms; i++)
snd_pcm_suspend_all(codec->pcm_info[i].pcm);
list_for_each_entry(pcm, &codec->pcm_list_head, list)
snd_pcm_suspend_all(pcm->pcm);
state = hda_call_codec_suspend(codec);
if (codec->d3_stop_clk && codec->epss && (state & AC_PWRST_CLK_STOP_OK))
clear_bit(codec->addr, &codec->bus->codec_powered);
Expand Down Expand Up @@ -4018,22 +4062,21 @@ EXPORT_SYMBOL_GPL(snd_hda_build_controls);
*/
static int add_std_chmaps(struct hda_codec *codec)
{
int i, str, err;
struct hda_pcm *pcm;
int str, err;

for (i = 0; i < codec->num_pcms; i++) {
list_for_each_entry(pcm, &codec->pcm_list_head, list) {
for (str = 0; str < 2; str++) {
struct snd_pcm *pcm = codec->pcm_info[i].pcm;
struct hda_pcm_stream *hinfo =
&codec->pcm_info[i].stream[str];
struct hda_pcm_stream *hinfo = &pcm->stream[str];
struct snd_pcm_chmap *chmap;
const struct snd_pcm_chmap_elem *elem;

if (codec->pcm_info[i].own_chmap)
if (pcm->own_chmap)
continue;
if (!pcm || !hinfo->substreams)
continue;
elem = hinfo->chmap ? hinfo->chmap : snd_pcm_std_chmaps;
err = snd_pcm_add_chmap_ctls(pcm, str, elem,
err = snd_pcm_add_chmap_ctls(pcm->pcm, str, elem,
hinfo->channels_max,
0, &chmap);
if (err < 0)
Expand Down Expand Up @@ -4564,10 +4607,10 @@ static int get_empty_pcm_device(struct hda_bus *bus, unsigned int type)
/* call build_pcms ops of the given codec and set up the default parameters */
int snd_hda_codec_parse_pcms(struct hda_codec *codec)
{
unsigned int pcm;
struct hda_pcm *cpcm;
int err;

if (codec->num_pcms)
if (!list_empty(&codec->pcm_list_head))
return 0; /* already parsed */

if (!codec->patch_ops.build_pcms)
Expand All @@ -4580,17 +4623,14 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)
return err;
}

for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];
list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
int stream;

for (stream = 0; stream < 2; stream++) {
struct hda_pcm_stream *info = &cpcm->stream[stream];

if (!info->substreams)
continue;
if (snd_BUG_ON(!cpcm->name))
return -EINVAL;
err = set_pcm_default_values(codec, info);
if (err < 0) {
codec_warn(codec,
Expand All @@ -4608,7 +4648,7 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)
int snd_hda_codec_build_pcms(struct hda_codec *codec)
{
struct hda_bus *bus = codec->bus;
unsigned int pcm;
struct hda_pcm *cpcm;
int dev, err;

if (snd_BUG_ON(!bus->ops.attach_pcm))
Expand All @@ -4621,9 +4661,7 @@ int snd_hda_codec_build_pcms(struct hda_codec *codec)
}

/* attach a new PCM streams */
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
struct hda_pcm *cpcm = &codec->pcm_info[pcm];

list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
if (cpcm->pcm)
continue; /* already attached */
if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
Expand Down Expand Up @@ -4651,11 +4689,9 @@ int snd_hda_codec_build_pcms(struct hda_codec *codec)
*
* Create PCM information for each codec included in the bus.
*
* The build_pcms codec patch is requested to set up codec->num_pcms and
* codec->pcm_info properly. The array is referred by the top-level driver
* to create its PCM instances.
* The allocated codec->pcm_info should be released in codec->patch_ops.free
* callback.
* The build_pcms codec patch is requested to create and assign new
* hda_pcm objects. The codec is responsible to call snd_hda_codec_pcm_new()
* and fills the fields. Later they are instantiated by this function.
*
* At least, substreams, channels_min and channels_max must be filled for
* each stream. substreams = 0 indicates that the stream doesn't exist.
Expand Down
18 changes: 16 additions & 2 deletions sound/pci/hda/hda_codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#ifndef __SOUND_HDA_CODEC_H
#define __SOUND_HDA_CODEC_H

#include <linux/kref.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/pcm.h>
Expand Down Expand Up @@ -268,6 +269,10 @@ struct hda_pcm {
int device; /* device number to assign */
struct snd_pcm *pcm; /* assigned PCM instance */
bool own_chmap; /* codec driver provides own channel maps */
/* private: */
struct hda_codec *codec;
struct kref kref;
struct list_head list;
};

/* codec information */
Expand Down Expand Up @@ -301,8 +306,7 @@ struct hda_codec {
struct hda_codec_ops patch_ops;

/* PCM to create, set by patch_ops.build_pcms callback */
unsigned int num_pcms;
struct hda_pcm *pcm_info;
struct list_head pcm_list_head;

/* codec specific info */
void *spec;
Expand Down Expand Up @@ -521,6 +525,16 @@ int snd_hda_build_pcms(struct hda_bus *bus);
int snd_hda_codec_parse_pcms(struct hda_codec *codec);
int snd_hda_codec_build_pcms(struct hda_codec *codec);

__printf(2, 3)
struct hda_pcm *snd_hda_codec_pcm_new(struct hda_codec *codec,
const char *fmt, ...);

static inline void snd_hda_codec_pcm_get(struct hda_pcm *pcm)
{
kref_get(&pcm->kref);
}
void snd_hda_codec_pcm_put(struct hda_pcm *pcm);

int snd_hda_codec_prepare(struct hda_codec *codec,
struct hda_pcm_stream *hinfo,
unsigned int stream,
Expand Down
28 changes: 16 additions & 12 deletions sound/pci/hda/hda_generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -4644,7 +4644,7 @@ int snd_hda_gen_build_controls(struct hda_codec *codec)
err = snd_hda_create_dig_out_ctls(codec,
spec->multiout.dig_out_nid,
spec->multiout.dig_out_nid,
spec->pcm_rec[1].pcm_type);
spec->pcm_rec[1]->pcm_type);
if (err < 0)
return err;
if (!spec->no_analog) {
Expand Down Expand Up @@ -5115,20 +5115,20 @@ static void fill_pcm_stream_name(char *str, size_t len, const char *sfx,
int snd_hda_gen_build_pcms(struct hda_codec *codec)
{
struct hda_gen_spec *spec = codec->spec;
struct hda_pcm *info = spec->pcm_rec;
struct hda_pcm *info;
const struct hda_pcm_stream *p;
bool have_multi_adcs;

codec->num_pcms = 1;
codec->pcm_info = info;

if (spec->no_analog)
goto skip_analog;

fill_pcm_stream_name(spec->stream_name_analog,
sizeof(spec->stream_name_analog),
" Analog", codec->chip_name);
info->name = spec->stream_name_analog;
info = snd_hda_codec_pcm_new(codec, "%s", spec->stream_name_analog);
if (!info)
return -ENOMEM;
spec->pcm_rec[0] = info;

if (spec->multiout.num_dacs > 0) {
p = spec->stream_analog_playback;
Expand Down Expand Up @@ -5161,10 +5161,12 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
fill_pcm_stream_name(spec->stream_name_digital,
sizeof(spec->stream_name_digital),
" Digital", codec->chip_name);
codec->num_pcms = 2;
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_digital);
if (!info)
return -ENOMEM;
codec->slave_dig_outs = spec->multiout.slave_dig_outs;
info = spec->pcm_rec + 1;
info->name = spec->stream_name_digital;
spec->pcm_rec[1] = info;
if (spec->dig_out_type)
info->pcm_type = spec->dig_out_type;
else
Expand Down Expand Up @@ -5198,9 +5200,11 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
fill_pcm_stream_name(spec->stream_name_alt_analog,
sizeof(spec->stream_name_alt_analog),
" Alt Analog", codec->chip_name);
codec->num_pcms = 3;
info = spec->pcm_rec + 2;
info->name = spec->stream_name_alt_analog;
info = snd_hda_codec_pcm_new(codec, "%s",
spec->stream_name_alt_analog);
if (!info)
return -ENOMEM;
spec->pcm_rec[2] = info;
if (spec->alt_dac_nid) {
p = spec->stream_analog_alt_playback;
if (!p)
Expand Down
2 changes: 1 addition & 1 deletion sound/pci/hda/hda_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ struct hda_gen_spec {
int const_channel_count; /* channel count for all */

/* PCM information */
struct hda_pcm pcm_rec[3]; /* used in build_pcms() */
struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */

/* dynamic controls, init_verbs and input_mux */
struct auto_pin_cfg autocfg;
Expand Down
6 changes: 3 additions & 3 deletions sound/pci/hda/hda_proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ static void print_nid_array(struct snd_info_buffer *buffer,
static void print_nid_pcms(struct snd_info_buffer *buffer,
struct hda_codec *codec, hda_nid_t nid)
{
int pcm, type;
int type;
struct hda_pcm *cpcm;
for (pcm = 0; pcm < codec->num_pcms; pcm++) {
cpcm = &codec->pcm_info[pcm];

list_for_each_entry(cpcm, &codec->pcm_list_head, list) {
for (type = 0; type < 2; type++) {
if (cpcm->stream[type].nid != nid || cpcm->pcm == NULL)
continue;
Expand Down
Loading

0 comments on commit bbbc7e8

Please sign in to comment.