Commit c3b6bcc2 authored by Takashi Iwai's avatar Takashi Iwai

ALSA: hda - Fix concurrent hash accesses

The amp and caps hashes aren't protected properly for concurrent
accesses.  Protect them via a new mutex now.

But it can't be so simple as originally thought: since the update of a
hash table entry itself might trigger the power-up sequence which
again accesses the hash table, we can't cover the whole function
simply via mutex.  Thus the update part has to be split from the mutex
and revalidated.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent e3245cdd
...@@ -1255,6 +1255,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, ...@@ -1255,6 +1255,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
codec->addr = codec_addr; codec->addr = codec_addr;
mutex_init(&codec->spdif_mutex); mutex_init(&codec->spdif_mutex);
mutex_init(&codec->control_mutex); mutex_init(&codec->control_mutex);
mutex_init(&codec->hash_mutex);
init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32); snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32);
...@@ -1605,6 +1606,60 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key) ...@@ -1605,6 +1606,60 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key)
return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key); return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key);
} }
/* overwrite the value with the key in the caps hash */
static int write_caps_hash(struct hda_codec *codec, u32 key, unsigned int val)
{
struct hda_amp_info *info;
mutex_lock(&codec->hash_mutex);
info = get_alloc_amp_hash(codec, key);
if (!info) {
mutex_unlock(&codec->hash_mutex);
return -EINVAL;
}
info->amp_caps = val;
info->head.val |= INFO_AMP_CAPS;
mutex_unlock(&codec->hash_mutex);
return 0;
}
/* query the value from the caps hash; if not found, fetch the current
* value from the given function and store in the hash
*/
static unsigned int
query_caps_hash(struct hda_codec *codec, hda_nid_t nid, int dir, u32 key,
unsigned int (*func)(struct hda_codec *, hda_nid_t, int))
{
struct hda_amp_info *info;
unsigned int val;
mutex_lock(&codec->hash_mutex);
info = get_alloc_amp_hash(codec, key);
if (!info) {
mutex_unlock(&codec->hash_mutex);
return 0;
}
if (!(info->head.val & INFO_AMP_CAPS)) {
mutex_unlock(&codec->hash_mutex); /* for reentrance */
val = func(codec, nid, dir);
write_caps_hash(codec, key, val);
} else {
val = info->amp_caps;
mutex_unlock(&codec->hash_mutex);
}
return val;
}
static unsigned int read_amp_cap(struct hda_codec *codec, hda_nid_t nid,
int direction)
{
if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
nid = codec->afg;
return snd_hda_param_read(codec, nid,
direction == HDA_OUTPUT ?
AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
}
/** /**
* query_amp_caps - query AMP capabilities * query_amp_caps - query AMP capabilities
* @codec: the HD-auio codec * @codec: the HD-auio codec
...@@ -1619,22 +1674,9 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key) ...@@ -1619,22 +1674,9 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key)
*/ */
u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
{ {
struct hda_amp_info *info; return query_caps_hash(codec, nid, direction,
HDA_HASH_KEY(nid, direction, 0),
info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0)); read_amp_cap);
if (!info)
return 0;
if (!(info->head.val & INFO_AMP_CAPS)) {
if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
nid = codec->afg;
info->amp_caps = snd_hda_param_read(codec, nid,
direction == HDA_OUTPUT ?
AC_PAR_AMP_OUT_CAP :
AC_PAR_AMP_IN_CAP);
if (info->amp_caps)
info->head.val |= INFO_AMP_CAPS;
}
return info->amp_caps;
} }
EXPORT_SYMBOL_HDA(query_amp_caps); EXPORT_SYMBOL_HDA(query_amp_caps);
...@@ -1654,34 +1696,12 @@ EXPORT_SYMBOL_HDA(query_amp_caps); ...@@ -1654,34 +1696,12 @@ EXPORT_SYMBOL_HDA(query_amp_caps);
int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
unsigned int caps) unsigned int caps)
{ {
struct hda_amp_info *info; return write_caps_hash(codec, HDA_HASH_KEY(nid, dir, 0), caps);
info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0));
if (!info)
return -EINVAL;
info->amp_caps = caps;
info->head.val |= INFO_AMP_CAPS;
return 0;
} }
EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps); EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps);
static unsigned int static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid,
query_caps_hash(struct hda_codec *codec, hda_nid_t nid, u32 key, int dir)
unsigned int (*func)(struct hda_codec *, hda_nid_t))
{
struct hda_amp_info *info;
info = get_alloc_amp_hash(codec, key);
if (!info)
return 0;
if (!info->head.val) {
info->head.val |= INFO_AMP_CAPS;
info->amp_caps = func(codec, nid);
}
return info->amp_caps;
}
static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid)
{ {
return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
} }
...@@ -1699,7 +1719,7 @@ static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid) ...@@ -1699,7 +1719,7 @@ static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid)
*/ */
u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
{ {
return query_caps_hash(codec, nid, HDA_HASH_PINCAP_KEY(nid), return query_caps_hash(codec, nid, 0, HDA_HASH_PINCAP_KEY(nid),
read_pin_cap); read_pin_cap);
} }
EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
...@@ -1717,41 +1737,47 @@ EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); ...@@ -1717,41 +1737,47 @@ EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid, int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
unsigned int caps) unsigned int caps)
{ {
struct hda_amp_info *info; return write_caps_hash(codec, HDA_HASH_PINCAP_KEY(nid), caps);
info = get_alloc_amp_hash(codec, HDA_HASH_PINCAP_KEY(nid));
if (!info)
return -ENOMEM;
info->amp_caps = caps;
info->head.val |= INFO_AMP_CAPS;
return 0;
} }
EXPORT_SYMBOL_HDA(snd_hda_override_pin_caps); EXPORT_SYMBOL_HDA(snd_hda_override_pin_caps);
/* /* read or sync the hash value with the current value;
* read the current volume to info * call within hash_mutex
* if the cache exists, read the cache value.
*/ */
static unsigned int get_vol_mute(struct hda_codec *codec, static struct hda_amp_info *
struct hda_amp_info *info, hda_nid_t nid, update_amp_hash(struct hda_codec *codec, hda_nid_t nid, int ch,
int ch, int direction, int index) int direction, int index)
{ {
u32 val, parm; struct hda_amp_info *info;
unsigned int parm, val = 0;
if (info->head.val & INFO_AMP_VOL(ch)) bool val_read = false;
return info->vol[ch];
parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT; retry:
parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT; info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
parm |= index; if (!info)
val = snd_hda_codec_read(codec, nid, 0, return NULL;
if (!(info->head.val & INFO_AMP_VOL(ch))) {
if (!val_read) {
mutex_unlock(&codec->hash_mutex);
parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
parm |= direction == HDA_OUTPUT ?
AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
parm |= index;
val = snd_hda_codec_read(codec, nid, 0,
AC_VERB_GET_AMP_GAIN_MUTE, parm); AC_VERB_GET_AMP_GAIN_MUTE, parm);
info->vol[ch] = val & 0xff; val &= 0xff;
info->head.val |= INFO_AMP_VOL(ch); val_read = true;
return info->vol[ch]; mutex_lock(&codec->hash_mutex);
goto retry;
}
info->vol[ch] = val;
info->head.val |= INFO_AMP_VOL(ch);
}
return info;
} }
/* /*
* write the current volume in info to the h/w and update the cache * write the current volume in info to the h/w
*/ */
static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
hda_nid_t nid, int ch, int direction, int index, hda_nid_t nid, int ch, int direction, int index,
...@@ -1768,7 +1794,6 @@ static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, ...@@ -1768,7 +1794,6 @@ static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
else else
parm |= val; parm |= val;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm); snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
info->vol[ch] = val;
} }
/** /**
...@@ -1785,10 +1810,14 @@ int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, ...@@ -1785,10 +1810,14 @@ int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
int direction, int index) int direction, int index)
{ {
struct hda_amp_info *info; struct hda_amp_info *info;
info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index)); unsigned int val = 0;
if (!info)
return 0; mutex_lock(&codec->hash_mutex);
return get_vol_mute(codec, info, nid, ch, direction, index); info = update_amp_hash(codec, nid, ch, direction, index);
if (info)
val = info->vol[ch];
mutex_unlock(&codec->hash_mutex);
return val;
} }
EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read); EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read);
...@@ -1810,15 +1839,23 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, ...@@ -1810,15 +1839,23 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
{ {
struct hda_amp_info *info; struct hda_amp_info *info;
info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
if (!info)
return 0;
if (snd_BUG_ON(mask & ~0xff)) if (snd_BUG_ON(mask & ~0xff))
mask &= 0xff; mask &= 0xff;
val &= mask; val &= mask;
val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask;
if (info->vol[ch] == val) mutex_lock(&codec->hash_mutex);
info = update_amp_hash(codec, nid, ch, direction, idx);
if (!info) {
mutex_unlock(&codec->hash_mutex);
return 0;
}
val |= info->vol[ch] & ~mask;
if (info->vol[ch] == val) {
mutex_unlock(&codec->hash_mutex);
return 0; return 0;
}
info->vol[ch] = val;
mutex_unlock(&codec->hash_mutex);
put_vol_mute(codec, info, nid, ch, direction, idx, val); put_vol_mute(codec, info, nid, ch, direction, idx, val);
return 1; return 1;
} }
...@@ -3693,7 +3730,8 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate, ...@@ -3693,7 +3730,8 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
} }
EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format); EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format);
static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid) static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid,
int dir)
{ {
unsigned int val = 0; unsigned int val = 0;
if (nid != codec->afg && if (nid != codec->afg &&
...@@ -3708,11 +3746,12 @@ static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid) ...@@ -3708,11 +3746,12 @@ static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid)
static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid) static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid)
{ {
return query_caps_hash(codec, nid, HDA_HASH_PARPCM_KEY(nid), return query_caps_hash(codec, nid, 0, HDA_HASH_PARPCM_KEY(nid),
get_pcm_param); get_pcm_param);
} }
static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid) static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid,
int dir)
{ {
unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
if (!streams || streams == -1) if (!streams || streams == -1)
...@@ -3724,7 +3763,7 @@ static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid) ...@@ -3724,7 +3763,7 @@ static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid)
static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid) static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid)
{ {
return query_caps_hash(codec, nid, HDA_HASH_PARSTR_KEY(nid), return query_caps_hash(codec, nid, 0, HDA_HASH_PARSTR_KEY(nid),
get_stream_param); get_stream_param);
} }
......
...@@ -827,6 +827,7 @@ struct hda_codec { ...@@ -827,6 +827,7 @@ struct hda_codec {
struct mutex spdif_mutex; struct mutex spdif_mutex;
struct mutex control_mutex; struct mutex control_mutex;
struct mutex hash_mutex;
struct snd_array spdif_out; struct snd_array spdif_out;
unsigned int spdif_in_enable; /* SPDIF input enable? */ unsigned int spdif_in_enable; /* SPDIF input enable? */
const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */ const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment