aboutsummaryrefslogtreecommitdiffstats
path: root/sound/pci/hda
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2012-05-10 10:11:15 -0400
committerTakashi Iwai <tiwai@suse.de>2012-05-10 10:12:13 -0400
commitc3b6bcc292da80ea08a979af177538ffdbbae36b (patch)
tree42202b40e340567e58feeeeeaf680c58c71d0196 /sound/pci/hda
parente3245cddcf56ccd810b73d0a2918e02560da93ab (diff)
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: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci/hda')
-rw-r--r--sound/pci/hda/hda_codec.c201
-rw-r--r--sound/pci/hda/hda_codec.h1
2 files changed, 121 insertions, 81 deletions
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index 5e7c4bf83abe..7a621f5986e0 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -1255,6 +1255,7 @@ int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus,
1255 codec->addr = codec_addr; 1255 codec->addr = codec_addr;
1256 mutex_init(&codec->spdif_mutex); 1256 mutex_init(&codec->spdif_mutex);
1257 mutex_init(&codec->control_mutex); 1257 mutex_init(&codec->control_mutex);
1258 mutex_init(&codec->hash_mutex);
1258 init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); 1259 init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
1259 init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); 1260 init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
1260 snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32); 1261 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 return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key); 1606 return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key);
1606} 1607}
1607 1608
1609/* overwrite the value with the key in the caps hash */
1610static int write_caps_hash(struct hda_codec *codec, u32 key, unsigned int val)
1611{
1612 struct hda_amp_info *info;
1613
1614 mutex_lock(&codec->hash_mutex);
1615 info = get_alloc_amp_hash(codec, key);
1616 if (!info) {
1617 mutex_unlock(&codec->hash_mutex);
1618 return -EINVAL;
1619 }
1620 info->amp_caps = val;
1621 info->head.val |= INFO_AMP_CAPS;
1622 mutex_unlock(&codec->hash_mutex);
1623 return 0;
1624}
1625
1626/* query the value from the caps hash; if not found, fetch the current
1627 * value from the given function and store in the hash
1628 */
1629static unsigned int
1630query_caps_hash(struct hda_codec *codec, hda_nid_t nid, int dir, u32 key,
1631 unsigned int (*func)(struct hda_codec *, hda_nid_t, int))
1632{
1633 struct hda_amp_info *info;
1634 unsigned int val;
1635
1636 mutex_lock(&codec->hash_mutex);
1637 info = get_alloc_amp_hash(codec, key);
1638 if (!info) {
1639 mutex_unlock(&codec->hash_mutex);
1640 return 0;
1641 }
1642 if (!(info->head.val & INFO_AMP_CAPS)) {
1643 mutex_unlock(&codec->hash_mutex); /* for reentrance */
1644 val = func(codec, nid, dir);
1645 write_caps_hash(codec, key, val);
1646 } else {
1647 val = info->amp_caps;
1648 mutex_unlock(&codec->hash_mutex);
1649 }
1650 return val;
1651}
1652
1653static unsigned int read_amp_cap(struct hda_codec *codec, hda_nid_t nid,
1654 int direction)
1655{
1656 if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
1657 nid = codec->afg;
1658 return snd_hda_param_read(codec, nid,
1659 direction == HDA_OUTPUT ?
1660 AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
1661}
1662
1608/** 1663/**
1609 * query_amp_caps - query AMP capabilities 1664 * query_amp_caps - query AMP capabilities
1610 * @codec: the HD-auio codec 1665 * @codec: the HD-auio codec
@@ -1619,22 +1674,9 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key)
1619 */ 1674 */
1620u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) 1675u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
1621{ 1676{
1622 struct hda_amp_info *info; 1677 return query_caps_hash(codec, nid, direction,
1623 1678 HDA_HASH_KEY(nid, direction, 0),
1624 info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0)); 1679 read_amp_cap);
1625 if (!info)
1626 return 0;
1627 if (!(info->head.val & INFO_AMP_CAPS)) {
1628 if (!(get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
1629 nid = codec->afg;
1630 info->amp_caps = snd_hda_param_read(codec, nid,
1631 direction == HDA_OUTPUT ?
1632 AC_PAR_AMP_OUT_CAP :
1633 AC_PAR_AMP_IN_CAP);
1634 if (info->amp_caps)
1635 info->head.val |= INFO_AMP_CAPS;
1636 }
1637 return info->amp_caps;
1638} 1680}
1639EXPORT_SYMBOL_HDA(query_amp_caps); 1681EXPORT_SYMBOL_HDA(query_amp_caps);
1640 1682
@@ -1654,34 +1696,12 @@ EXPORT_SYMBOL_HDA(query_amp_caps);
1654int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, 1696int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
1655 unsigned int caps) 1697 unsigned int caps)
1656{ 1698{
1657 struct hda_amp_info *info; 1699 return write_caps_hash(codec, HDA_HASH_KEY(nid, dir, 0), caps);
1658
1659 info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0));
1660 if (!info)
1661 return -EINVAL;
1662 info->amp_caps = caps;
1663 info->head.val |= INFO_AMP_CAPS;
1664 return 0;
1665} 1700}
1666EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps); 1701EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps);
1667 1702
1668static unsigned int 1703static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid,
1669query_caps_hash(struct hda_codec *codec, hda_nid_t nid, u32 key, 1704 int dir)
1670 unsigned int (*func)(struct hda_codec *, hda_nid_t))
1671{
1672 struct hda_amp_info *info;
1673
1674 info = get_alloc_amp_hash(codec, key);
1675 if (!info)
1676 return 0;
1677 if (!info->head.val) {
1678 info->head.val |= INFO_AMP_CAPS;
1679 info->amp_caps = func(codec, nid);
1680 }
1681 return info->amp_caps;
1682}
1683
1684static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid)
1685{ 1705{
1686 return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); 1706 return snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
1687} 1707}
@@ -1699,7 +1719,7 @@ static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid)
1699 */ 1719 */
1700u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) 1720u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid)
1701{ 1721{
1702 return query_caps_hash(codec, nid, HDA_HASH_PINCAP_KEY(nid), 1722 return query_caps_hash(codec, nid, 0, HDA_HASH_PINCAP_KEY(nid),
1703 read_pin_cap); 1723 read_pin_cap);
1704} 1724}
1705EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); 1725EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
@@ -1717,41 +1737,47 @@ EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps);
1717int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid, 1737int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid,
1718 unsigned int caps) 1738 unsigned int caps)
1719{ 1739{
1720 struct hda_amp_info *info; 1740 return write_caps_hash(codec, HDA_HASH_PINCAP_KEY(nid), caps);
1721 info = get_alloc_amp_hash(codec, HDA_HASH_PINCAP_KEY(nid));
1722 if (!info)
1723 return -ENOMEM;
1724 info->amp_caps = caps;
1725 info->head.val |= INFO_AMP_CAPS;
1726 return 0;
1727} 1741}
1728EXPORT_SYMBOL_HDA(snd_hda_override_pin_caps); 1742EXPORT_SYMBOL_HDA(snd_hda_override_pin_caps);
1729 1743
1730/* 1744/* read or sync the hash value with the current value;
1731 * read the current volume to info 1745 * call within hash_mutex
1732 * if the cache exists, read the cache value.
1733 */ 1746 */
1734static unsigned int get_vol_mute(struct hda_codec *codec, 1747static struct hda_amp_info *
1735 struct hda_amp_info *info, hda_nid_t nid, 1748update_amp_hash(struct hda_codec *codec, hda_nid_t nid, int ch,
1736 int ch, int direction, int index) 1749 int direction, int index)
1737{ 1750{
1738 u32 val, parm; 1751 struct hda_amp_info *info;
1739 1752 unsigned int parm, val = 0;
1740 if (info->head.val & INFO_AMP_VOL(ch)) 1753 bool val_read = false;
1741 return info->vol[ch];
1742 1754
1743 parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT; 1755 retry:
1744 parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT; 1756 info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
1745 parm |= index; 1757 if (!info)
1746 val = snd_hda_codec_read(codec, nid, 0, 1758 return NULL;
1759 if (!(info->head.val & INFO_AMP_VOL(ch))) {
1760 if (!val_read) {
1761 mutex_unlock(&codec->hash_mutex);
1762 parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
1763 parm |= direction == HDA_OUTPUT ?
1764 AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
1765 parm |= index;
1766 val = snd_hda_codec_read(codec, nid, 0,
1747 AC_VERB_GET_AMP_GAIN_MUTE, parm); 1767 AC_VERB_GET_AMP_GAIN_MUTE, parm);
1748 info->vol[ch] = val & 0xff; 1768 val &= 0xff;
1749 info->head.val |= INFO_AMP_VOL(ch); 1769 val_read = true;
1750 return info->vol[ch]; 1770 mutex_lock(&codec->hash_mutex);
1771 goto retry;
1772 }
1773 info->vol[ch] = val;
1774 info->head.val |= INFO_AMP_VOL(ch);
1775 }
1776 return info;
1751} 1777}
1752 1778
1753/* 1779/*
1754 * write the current volume in info to the h/w and update the cache 1780 * write the current volume in info to the h/w
1755 */ 1781 */
1756static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, 1782static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
1757 hda_nid_t nid, int ch, int direction, int index, 1783 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 else 1794 else
1769 parm |= val; 1795 parm |= val;
1770 snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm); 1796 snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
1771 info->vol[ch] = val;
1772} 1797}
1773 1798
1774/** 1799/**
@@ -1785,10 +1810,14 @@ int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
1785 int direction, int index) 1810 int direction, int index)
1786{ 1811{
1787 struct hda_amp_info *info; 1812 struct hda_amp_info *info;
1788 info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index)); 1813 unsigned int val = 0;
1789 if (!info) 1814
1790 return 0; 1815 mutex_lock(&codec->hash_mutex);
1791 return get_vol_mute(codec, info, nid, ch, direction, index); 1816 info = update_amp_hash(codec, nid, ch, direction, index);
1817 if (info)
1818 val = info->vol[ch];
1819 mutex_unlock(&codec->hash_mutex);
1820 return val;
1792} 1821}
1793EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read); 1822EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read);
1794 1823
@@ -1810,15 +1839,23 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
1810{ 1839{
1811 struct hda_amp_info *info; 1840 struct hda_amp_info *info;
1812 1841
1813 info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
1814 if (!info)
1815 return 0;
1816 if (snd_BUG_ON(mask & ~0xff)) 1842 if (snd_BUG_ON(mask & ~0xff))
1817 mask &= 0xff; 1843 mask &= 0xff;
1818 val &= mask; 1844 val &= mask;
1819 val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask; 1845
1820 if (info->vol[ch] == val) 1846 mutex_lock(&codec->hash_mutex);
1847 info = update_amp_hash(codec, nid, ch, direction, idx);
1848 if (!info) {
1849 mutex_unlock(&codec->hash_mutex);
1850 return 0;
1851 }
1852 val |= info->vol[ch] & ~mask;
1853 if (info->vol[ch] == val) {
1854 mutex_unlock(&codec->hash_mutex);
1821 return 0; 1855 return 0;
1856 }
1857 info->vol[ch] = val;
1858 mutex_unlock(&codec->hash_mutex);
1822 put_vol_mute(codec, info, nid, ch, direction, idx, val); 1859 put_vol_mute(codec, info, nid, ch, direction, idx, val);
1823 return 1; 1860 return 1;
1824} 1861}
@@ -3693,7 +3730,8 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
3693} 3730}
3694EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format); 3731EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format);
3695 3732
3696static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid) 3733static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid,
3734 int dir)
3697{ 3735{
3698 unsigned int val = 0; 3736 unsigned int val = 0;
3699 if (nid != codec->afg && 3737 if (nid != codec->afg &&
@@ -3708,11 +3746,12 @@ static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid)
3708 3746
3709static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid) 3747static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid)
3710{ 3748{
3711 return query_caps_hash(codec, nid, HDA_HASH_PARPCM_KEY(nid), 3749 return query_caps_hash(codec, nid, 0, HDA_HASH_PARPCM_KEY(nid),
3712 get_pcm_param); 3750 get_pcm_param);
3713} 3751}
3714 3752
3715static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid) 3753static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid,
3754 int dir)
3716{ 3755{
3717 unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM); 3756 unsigned int streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
3718 if (!streams || streams == -1) 3757 if (!streams || streams == -1)
@@ -3724,7 +3763,7 @@ static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid)
3724 3763
3725static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid) 3764static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid)
3726{ 3765{
3727 return query_caps_hash(codec, nid, HDA_HASH_PARSTR_KEY(nid), 3766 return query_caps_hash(codec, nid, 0, HDA_HASH_PARSTR_KEY(nid),
3728 get_stream_param); 3767 get_stream_param);
3729} 3768}
3730 3769
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index fce30b42bc46..29a311b05f2d 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -827,6 +827,7 @@ struct hda_codec {
827 827
828 struct mutex spdif_mutex; 828 struct mutex spdif_mutex;
829 struct mutex control_mutex; 829 struct mutex control_mutex;
830 struct mutex hash_mutex;
830 struct snd_array spdif_out; 831 struct snd_array spdif_out;
831 unsigned int spdif_in_enable; /* SPDIF input enable? */ 832 unsigned int spdif_in_enable; /* SPDIF input enable? */
832 const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */ 833 const hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */