Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bbbc7e85 authored by Takashi Iwai's avatar Takashi Iwai
Browse files

ALSA: hda - Allocate hda_pcm objects dynamically



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: default avatarTakashi Iwai <tiwai@suse.de>
parent f4de8fe6
Loading
Loading
Loading
Loading
+76 −40
Original line number Diff line number Diff line
@@ -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
 */
@@ -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);
@@ -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;
@@ -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 ||
@@ -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;
@@ -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));
@@ -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;
@@ -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);
@@ -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)
@@ -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)
@@ -4580,8 +4623,7 @@ 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++) {
@@ -4589,8 +4631,6 @@ int snd_hda_codec_parse_pcms(struct hda_codec *codec)

			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,
@@ -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))
@@ -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)
@@ -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.
+16 −2
Original line number Diff line number Diff line
@@ -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>
@@ -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 */
@@ -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;
@@ -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,
+16 −12
Original line number Diff line number Diff line
@@ -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) {
@@ -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;
@@ -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
@@ -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)
+1 −1
Original line number Diff line number Diff line
@@ -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;
+3 −3
Original line number Diff line number Diff line
@@ -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;
Loading