diff options
author | Takashi Iwai <tiwai@suse.de> | 2012-05-10 10:11:15 -0400 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2012-05-10 10:12:13 -0400 |
commit | c3b6bcc292da80ea08a979af177538ffdbbae36b (patch) | |
tree | 42202b40e340567e58feeeeeaf680c58c71d0196 /sound/pci/hda | |
parent | e3245cddcf56ccd810b73d0a2918e02560da93ab (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.c | 201 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.h | 1 |
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 */ | ||
1610 | static 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 | */ | ||
1629 | static unsigned int | ||
1630 | query_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 | |||
1653 | static 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 | */ |
1620 | u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) | 1675 | u32 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 | } |
1639 | EXPORT_SYMBOL_HDA(query_amp_caps); | 1681 | EXPORT_SYMBOL_HDA(query_amp_caps); |
1640 | 1682 | ||
@@ -1654,34 +1696,12 @@ EXPORT_SYMBOL_HDA(query_amp_caps); | |||
1654 | int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, | 1696 | int 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 | } |
1666 | EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps); | 1701 | EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps); |
1667 | 1702 | ||
1668 | static unsigned int | 1703 | static unsigned int read_pin_cap(struct hda_codec *codec, hda_nid_t nid, |
1669 | query_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 | |||
1684 | static 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 | */ |
1700 | u32 snd_hda_query_pin_caps(struct hda_codec *codec, hda_nid_t nid) | 1720 | u32 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 | } |
1705 | EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); | 1725 | EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); |
@@ -1717,41 +1737,47 @@ EXPORT_SYMBOL_HDA(snd_hda_query_pin_caps); | |||
1717 | int snd_hda_override_pin_caps(struct hda_codec *codec, hda_nid_t nid, | 1737 | int 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 | } |
1728 | EXPORT_SYMBOL_HDA(snd_hda_override_pin_caps); | 1742 | EXPORT_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 | */ |
1734 | static unsigned int get_vol_mute(struct hda_codec *codec, | 1747 | static struct hda_amp_info * |
1735 | struct hda_amp_info *info, hda_nid_t nid, | 1748 | update_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 | */ |
1756 | static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, | 1782 | static 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 | } |
1793 | EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read); | 1822 | EXPORT_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 | } |
3694 | EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format); | 3731 | EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format); |
3695 | 3732 | ||
3696 | static unsigned int get_pcm_param(struct hda_codec *codec, hda_nid_t nid) | 3733 | static 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 | ||
3709 | static unsigned int query_pcm_param(struct hda_codec *codec, hda_nid_t nid) | 3747 | static 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 | ||
3715 | static unsigned int get_stream_param(struct hda_codec *codec, hda_nid_t nid) | 3753 | static 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 | ||
3725 | static unsigned int query_stream_param(struct hda_codec *codec, hda_nid_t nid) | 3764 | static 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 */ |