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

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

ALSA: hda - Revive snd_hda_get_conn_list()



Manage the connection list cache using linked lists instead of
snd_array, and revive snd_hda_get_conn_list() again, so that we don't
have to keep the expanded values locally.
This will reduce the stack usage by recursive call of
snd_hda_get_conn_index() or parse_nid_path() of the generic parser.

The list management doesn't include any mutex protection, thus the
caller needs to take care of race appropriately.

Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 9cc159c6
Loading
Loading
Loading
Loading
+99 −65
Original line number Diff line number Diff line
@@ -334,20 +334,51 @@ int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
}
EXPORT_SYMBOL_HDA(snd_hda_get_sub_nodes);

/* connection list element */
struct hda_conn_list {
	struct list_head list;
	int len;
	hda_nid_t nid;
	hda_nid_t conns[0];
};

/* look up the cached results */
static hda_nid_t *lookup_conn_list(struct snd_array *array, hda_nid_t nid)
static struct hda_conn_list *
lookup_conn_list(struct hda_codec *codec, hda_nid_t nid)
{
	int i, len;
	for (i = 0; i < array->used; ) {
		hda_nid_t *p = snd_array_elem(array, i);
		if (nid == *p)
	struct hda_conn_list *p;
	list_for_each_entry(p, &codec->conn_list, list) {
		if (p->nid == nid)
			return p;
		len = p[1];
		i += len + 2;
	}
	return NULL;
}

static int add_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
			 const hda_nid_t *list)
{
	struct hda_conn_list *p;

	p = kmalloc(sizeof(*p) + len * sizeof(hda_nid_t), GFP_KERNEL);
	if (!p)
		return -ENOMEM;
	p->len = len;
	p->nid = nid;
	memcpy(p->conns, list, len * sizeof(hda_nid_t));
	list_add(&p->list, &codec->conn_list);
	return 0;
}

static void remove_conn_list(struct hda_codec *codec)
{
	while (!list_empty(&codec->conn_list)) {
		struct hda_conn_list *p;
		p = list_first_entry(&codec->conn_list, typeof(*p), list);
		list_del(&p->list);
		kfree(p);
	}
}

/* read the connection and add to the cache */
static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid)
{
@@ -360,6 +391,49 @@ static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid)
	return snd_hda_override_conn_list(codec, nid, len, list);
}

/**
 * snd_hda_get_conn_list - get connection list
 * @codec: the HDA codec
 * @nid: NID to parse
 * @len: number of connection list entries
 * @listp: the pointer to store NID list
 *
 * Parses the connection list of the given widget and stores the pointer
 * to the list of NIDs.
 *
 * Returns the number of connections, or a negative error code.
 *
 * Note that the returned pointer isn't protected against the list
 * modification.  If snd_hda_override_conn_list() might be called
 * concurrently, protect with a mutex appropriately.
 */
int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
			  const hda_nid_t **listp)
{
	bool added = false;

	for (;;) {
		int err;
		const struct hda_conn_list *p;

		/* if the connection-list is already cached, read it */
		p = lookup_conn_list(codec, nid);
		if (p) {
			if (listp)
				*listp = p->conns;
			return p->len;
		}
		if (snd_BUG_ON(added))
			return -EINVAL;

		err = read_and_add_raw_conns(codec, nid);
		if (err < 0)
			return err;
		added = true;
	}
}
EXPORT_SYMBOL_HDA(snd_hda_get_conn_list);

/**
 * snd_hda_get_connections - copy connection list
 * @codec: the HDA codec
@@ -375,39 +449,20 @@ static int read_and_add_raw_conns(struct hda_codec *codec, hda_nid_t nid)
int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
			    hda_nid_t *conn_list, int max_conns)
{
	struct snd_array *array = &codec->conn_lists;
	int len;
	hda_nid_t *p;
	bool added = false;
	const hda_nid_t *list;
	int len = snd_hda_get_conn_list(codec, nid, &list);

 again:
	mutex_lock(&codec->hash_mutex);
	len = -1;
	/* if the connection-list is already cached, read it */
	p = lookup_conn_list(array, nid);
	if (p) {
		len = p[1];
		if (conn_list && len > max_conns) {
	if (len > 0 && conn_list) {
		if (len > max_conns) {
			snd_printk(KERN_ERR "hda_codec: "
				   "Too many connections %d for NID 0x%x\n",
				   len, nid);
			mutex_unlock(&codec->hash_mutex);
			return -EINVAL;
		}
		if (conn_list && len)
			memcpy(conn_list, p + 2, len * sizeof(hda_nid_t));
		memcpy(conn_list, list, len * sizeof(hda_nid_t));
	}
	mutex_unlock(&codec->hash_mutex);
	if (len >= 0)
		return len;
	if (snd_BUG_ON(added))
		return -EINVAL;

	len = read_and_add_raw_conns(codec, nid);
	if (len < 0)
	return len;
	added = true;
	goto again;
}
EXPORT_SYMBOL_HDA(snd_hda_get_connections);

@@ -519,15 +574,6 @@ int snd_hda_get_raw_connections(struct hda_codec *codec, hda_nid_t nid,
	return conns;
}

static bool add_conn_list(struct snd_array *array, hda_nid_t nid)
{
	hda_nid_t *p = snd_array_new(array);
	if (!p)
		return false;
	*p = nid;
	return true;
}

/**
 * snd_hda_override_conn_list - add/modify the connection-list to cache
 * @codec: the HDA codec
@@ -543,28 +589,15 @@ static bool add_conn_list(struct snd_array *array, hda_nid_t nid)
int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int len,
			       const hda_nid_t *list)
{
	struct snd_array *array = &codec->conn_lists;
	hda_nid_t *p;
	int i, old_used;
	struct hda_conn_list *p;

	mutex_lock(&codec->hash_mutex);
	p = lookup_conn_list(array, nid);
	if (p)
		*p = -1; /* invalidate the old entry */

	old_used = array->used;
	if (!add_conn_list(array, nid) || !add_conn_list(array, len))
		goto error_add;
	for (i = 0; i < len; i++)
		if (!add_conn_list(array, list[i]))
			goto error_add;
	mutex_unlock(&codec->hash_mutex);
	return 0;
	p = lookup_conn_list(codec, nid);
	if (p) {
		list_del(&p->list);
		kfree(p);
	}

 error_add:
	array->used = old_used;
	mutex_unlock(&codec->hash_mutex);
	return -ENOMEM;
	return add_conn_list(codec, nid, len, list);
}
EXPORT_SYMBOL_HDA(snd_hda_override_conn_list);

@@ -582,10 +615,10 @@ EXPORT_SYMBOL_HDA(snd_hda_override_conn_list);
int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux,
			   hda_nid_t nid, int recursive)
{
	hda_nid_t conn[HDA_MAX_NUM_INPUTS];
	const hda_nid_t *conn;
	int i, nums;

	nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
	nums = snd_hda_get_conn_list(codec, mux, &conn);
	for (i = 0; i < nums; i++)
		if (conn[i] == nid)
			return i;
@@ -1186,8 +1219,8 @@ static void snd_hda_codec_free(struct hda_codec *codec)
	snd_array_free(&codec->mixers);
	snd_array_free(&codec->nids);
	snd_array_free(&codec->cvt_setups);
	snd_array_free(&codec->conn_lists);
	snd_array_free(&codec->spdif_out);
	remove_conn_list(codec);
	codec->bus->caddr_tbl[codec->addr] = NULL;
	if (codec->patch_ops.free)
		codec->patch_ops.free(codec);
@@ -1257,10 +1290,11 @@ int snd_hda_codec_new(struct hda_bus *bus,
	snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
	snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
	snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8);
	snd_array_init(&codec->conn_lists, sizeof(hda_nid_t), 64);
	snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16);
	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_DELAYED_WORK(&codec->jackpoll_work, hda_jackpoll_work);

#ifdef CONFIG_PM
+3 −1
Original line number Diff line number Diff line
@@ -831,7 +831,7 @@ struct hda_codec {
	struct hda_cache_rec amp_cache;	/* cache for amp access */
	struct hda_cache_rec cmd_cache;	/* cache for other commands */

	struct snd_array conn_lists;	/* connection-list array */
	struct list_head conn_list;	/* linked-list of connection-list */

	struct mutex spdif_mutex;
	struct mutex control_mutex;
@@ -944,6 +944,8 @@ snd_hda_get_num_conns(struct hda_codec *codec, hda_nid_t nid)
}
int snd_hda_get_raw_connections(struct hda_codec *codec, hda_nid_t nid,
			    hda_nid_t *conn_list, int max_conns);
int snd_hda_get_conn_list(struct hda_codec *codec, hda_nid_t nid,
			  const hda_nid_t **listp);
int snd_hda_override_conn_list(struct hda_codec *codec, hda_nid_t nid, int nums,
			  const hda_nid_t *list);
int snd_hda_get_conn_index(struct hda_codec *codec, hda_nid_t mux,
+4 −4
Original line number Diff line number Diff line
@@ -208,7 +208,7 @@ static bool __parse_nid_path(struct hda_codec *codec,
			     int with_aa_mix, struct nid_path *path, int depth)
{
	struct hda_gen_spec *spec = codec->spec;
	hda_nid_t conn[16];
	const hda_nid_t *conn;
	int i, nums;

	if (to_nid == spec->mixer_nid) {
@@ -217,7 +217,7 @@ static bool __parse_nid_path(struct hda_codec *codec,
		with_aa_mix = HDA_PARSE_ALL; /* mark aa-mix is included */
	}

	nums = snd_hda_get_connections(codec, to_nid, conn, ARRAY_SIZE(conn));
	nums = snd_hda_get_conn_list(codec, to_nid, &conn);
	for (i = 0; i < nums; i++) {
		if (conn[i] != from_nid) {
			/* special case: when from_nid is 0,
@@ -481,12 +481,12 @@ static void activate_amp_in(struct hda_codec *codec, struct nid_path *path,
			    int i, bool enable, bool add_aamix)
{
	struct hda_gen_spec *spec = codec->spec;
	hda_nid_t conn[16];
	const hda_nid_t *conn;
	int n, nums, idx;
	int type;
	hda_nid_t nid = path->path[i];

	nums = snd_hda_get_connections(codec, nid, conn, ARRAY_SIZE(conn));
	nums = snd_hda_get_conn_list(codec, nid, &conn);
	type = get_wcaps_type(get_wcaps(codec, nid));
	if (type == AC_WID_PIN ||
	    (type == AC_WID_AUD_IN && codec->single_adc_amp)) {