Commit cf5c079c authored by Takashi Iwai's avatar Takashi Iwai Committed by Greg Kroah-Hartman

ALSA: hda - Fix unexpected resume through regmap code path

commit fc4f000b upstream.

HD-audio driver has a mechanism to trigger the runtime resume
automatically at accessing the verbs.  This auto-resume, however,
causes the mutex deadlock when invoked from the regmap handler since
the regmap keeps the mutex while auto-resuming.  For avoiding that,
there is some tricky check in the HDA regmap handler to return -EAGAIN
error to back-off when the codec is powered down.  Then the caller of
regmap r/w will retry after properly turning on the codec power.

This works in most cases, but there seems a slight race between the
codec power check and the actual on-demand auto-resume trigger.  This
resulted in the lockdep splat, eventually leading to a real deadlock.

This patch tries to address the race window by getting the runtime PM
refcount at the check time using pm_runtime_get_if_in_use().  With
this call, we can keep the power on only when the codec has been
already turned on, and back off if not.

For keeping the code consistency, the code touching the runtime PM is
stored in hdac_device.c although it's used only locally in
hdac_regmap.c.
Reported-by: default avatarJiri Slaby <jslaby@suse.cz>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 840c32e2
...@@ -168,11 +168,13 @@ int snd_hdac_power_up(struct hdac_device *codec); ...@@ -168,11 +168,13 @@ int snd_hdac_power_up(struct hdac_device *codec);
int snd_hdac_power_down(struct hdac_device *codec); int snd_hdac_power_down(struct hdac_device *codec);
int snd_hdac_power_up_pm(struct hdac_device *codec); int snd_hdac_power_up_pm(struct hdac_device *codec);
int snd_hdac_power_down_pm(struct hdac_device *codec); int snd_hdac_power_down_pm(struct hdac_device *codec);
int snd_hdac_keep_power_up(struct hdac_device *codec);
#else #else
static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; } static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; }
static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; } static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; }
static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; } static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; }
static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; } static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; }
static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; }
#endif #endif
/* /*
......
...@@ -611,6 +611,22 @@ int snd_hdac_power_up_pm(struct hdac_device *codec) ...@@ -611,6 +611,22 @@ int snd_hdac_power_up_pm(struct hdac_device *codec)
} }
EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm); EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm);
/* like snd_hdac_power_up_pm(), but only increment the pm count when
* already powered up. Returns -1 if not powered up, 1 if incremented
* or 0 if unchanged. Only used in hdac_regmap.c
*/
int snd_hdac_keep_power_up(struct hdac_device *codec)
{
if (!atomic_inc_not_zero(&codec->in_pm)) {
int ret = pm_runtime_get_if_in_use(&codec->dev);
if (!ret)
return -1;
if (ret < 0)
return 0;
}
return 1;
}
/** /**
* snd_hdac_power_down_pm - power down the codec * snd_hdac_power_down_pm - power down the codec
* @codec: the codec object * @codec: the codec object
......
...@@ -21,13 +21,16 @@ ...@@ -21,13 +21,16 @@
#include <sound/hdaudio.h> #include <sound/hdaudio.h>
#include <sound/hda_regmap.h> #include <sound/hda_regmap.h>
#ifdef CONFIG_PM static int codec_pm_lock(struct hdac_device *codec)
#define codec_is_running(codec) \ {
(atomic_read(&(codec)->in_pm) || \ return snd_hdac_keep_power_up(codec);
!pm_runtime_suspended(&(codec)->dev)) }
#else
#define codec_is_running(codec) true static void codec_pm_unlock(struct hdac_device *codec, int lock)
#endif {
if (lock == 1)
snd_hdac_power_down_pm(codec);
}
#define get_verb(reg) (((reg) >> 8) & 0xfff) #define get_verb(reg) (((reg) >> 8) & 0xfff)
...@@ -238,20 +241,28 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val) ...@@ -238,20 +241,28 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
struct hdac_device *codec = context; struct hdac_device *codec = context;
int verb = get_verb(reg); int verb = get_verb(reg);
int err; int err;
int pm_lock = 0;
if (!codec_is_running(codec) && verb != AC_VERB_GET_POWER_STATE) if (verb != AC_VERB_GET_POWER_STATE) {
return -EAGAIN; pm_lock = codec_pm_lock(codec);
if (pm_lock < 0)
return -EAGAIN;
}
reg |= (codec->addr << 28); reg |= (codec->addr << 28);
if (is_stereo_amp_verb(reg)) if (is_stereo_amp_verb(reg)) {
return hda_reg_read_stereo_amp(codec, reg, val); err = hda_reg_read_stereo_amp(codec, reg, val);
if (verb == AC_VERB_GET_PROC_COEF) goto out;
return hda_reg_read_coef(codec, reg, val); }
if (verb == AC_VERB_GET_PROC_COEF) {
err = hda_reg_read_coef(codec, reg, val);
goto out;
}
if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE) if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE)
reg &= ~AC_AMP_FAKE_MUTE; reg &= ~AC_AMP_FAKE_MUTE;
err = snd_hdac_exec_verb(codec, reg, 0, val); err = snd_hdac_exec_verb(codec, reg, 0, val);
if (err < 0) if (err < 0)
return err; goto out;
/* special handling for asymmetric reads */ /* special handling for asymmetric reads */
if (verb == AC_VERB_GET_POWER_STATE) { if (verb == AC_VERB_GET_POWER_STATE) {
if (*val & AC_PWRST_ERROR) if (*val & AC_PWRST_ERROR)
...@@ -259,7 +270,9 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val) ...@@ -259,7 +270,9 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
else /* take only the actual state */ else /* take only the actual state */
*val = (*val >> 4) & 0x0f; *val = (*val >> 4) & 0x0f;
} }
return 0; out:
codec_pm_unlock(codec, pm_lock);
return err;
} }
static int hda_reg_write(void *context, unsigned int reg, unsigned int val) static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
...@@ -267,6 +280,7 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val) ...@@ -267,6 +280,7 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
struct hdac_device *codec = context; struct hdac_device *codec = context;
unsigned int verb; unsigned int verb;
int i, bytes, err; int i, bytes, err;
int pm_lock = 0;
if (codec->caps_overwriting) if (codec->caps_overwriting)
return 0; return 0;
...@@ -275,14 +289,21 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val) ...@@ -275,14 +289,21 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
reg |= (codec->addr << 28); reg |= (codec->addr << 28);
verb = get_verb(reg); verb = get_verb(reg);
if (!codec_is_running(codec) && verb != AC_VERB_SET_POWER_STATE) if (verb != AC_VERB_SET_POWER_STATE) {
return codec->lazy_cache ? 0 : -EAGAIN; pm_lock = codec_pm_lock(codec);
if (pm_lock < 0)
return codec->lazy_cache ? 0 : -EAGAIN;
}
if (is_stereo_amp_verb(reg)) if (is_stereo_amp_verb(reg)) {
return hda_reg_write_stereo_amp(codec, reg, val); err = hda_reg_write_stereo_amp(codec, reg, val);
goto out;
}
if (verb == AC_VERB_SET_PROC_COEF) if (verb == AC_VERB_SET_PROC_COEF) {
return hda_reg_write_coef(codec, reg, val); err = hda_reg_write_coef(codec, reg, val);
goto out;
}
switch (verb & 0xf00) { switch (verb & 0xf00) {
case AC_VERB_SET_AMP_GAIN_MUTE: case AC_VERB_SET_AMP_GAIN_MUTE:
...@@ -319,10 +340,12 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val) ...@@ -319,10 +340,12 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff); reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff);
err = snd_hdac_exec_verb(codec, reg, 0, NULL); err = snd_hdac_exec_verb(codec, reg, 0, NULL);
if (err < 0) if (err < 0)
return err; goto out;
} }
return 0; out:
codec_pm_unlock(codec, pm_lock);
return err;
} }
static const struct regmap_config hda_regmap_cfg = { static const struct regmap_config hda_regmap_cfg = {
......
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