summary refs log tree commit diff
path: root/sound/hda
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2015-04-08 11:43:14 +0200
committerTakashi Iwai <tiwai@suse.de>2015-04-08 13:50:42 +0200
commit664c715573c2c116c2d8f5de7d59ce85a98a1751 (patch)
tree78629587a6d0ddfc478fa59d0248baa815f7132f /sound/hda
parent142267c9e026827ca5fa622f1f13780b6db26cf8 (diff)
downloadlinux-664c715573c2c116c2d8f5de7d59ce85a98a1751.tar.gz
ALSA: hda - Work around races of power up/down with runtime PM
Currently, snd_hdac_power_up()/down() helpers checks whether the codec
is being in pm (suspend/resume), and skips the call of runtime get/put
during it.  This is needed as there are lots of power up/down
sequences called in the paths that are also used in the PM itself.  An
example is found in hda_codec.c::codec_exec_verb(), where this can
power up the codec while it may be called again in its power up
sequence, too.

The above works in most cases, but sometimes we really want to wait
for the real power up.  For example, the control element get/put may
want explicit power up so that the value change is assured to reach to
the hardware.   Using the current snd_hdac_power_up(), however,
results in a race, e.g. when it's called during the runtime suspend is
being performed.  In the worst case, as found in patch_ca0132.c, it
can even lead to the deadlock because the code assumes the power up
while it was skipped due to the check above.

For dealing with such cases, this patch makes snd_hdac_power_up() and
_down() to two variants: with and without in_pm flag check.  The
version with pm flag check is named as snd_hdac_power_up_pm() while
the version without pm flag check is still kept as
snd_hdac_power_up().  (Just because the usage of the former is fewer.)

Then finally, the patch replaces each call potentially done in PM with
the new _pm() variant.

In theory, we can implement a unified version -- if we can distinguish
the current context whether it's in the pm path.  But such an
implementation is cumbersome, so leave the code like this a bit messy
way for now...

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=96271
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/hda')
-rw-r--r--sound/hda/hdac_device.c16
-rw-r--r--sound/hda/hdac_regmap.c8
2 files changed, 11 insertions, 13 deletions
diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c
index d4a0e723af2c..92604bbcee5f 100644
--- a/sound/hda/hdac_device.c
+++ b/sound/hda/hdac_device.c
@@ -494,29 +494,27 @@ EXPORT_SYMBOL_GPL(snd_hdac_get_connections);
 
 #ifdef CONFIG_PM
 /**
- * snd_hdac_power_up - increment the runtime pm counter
+ * snd_hdac_power_up - power up the codec
  * @codec: the codec object
+ *
+ * This function calls the runtime PM helper to power up the given codec.
+ * Unlike snd_hdac_power_up_pm(), you should call this only for the code
+ * path that isn't included in PM path.  Otherwise it gets stuck.
  */
 void snd_hdac_power_up(struct hdac_device *codec)
 {
-	struct device *dev = &codec->dev;
-
-	if (atomic_read(&codec->in_pm))
-		return;
-	pm_runtime_get_sync(dev);
+	pm_runtime_get_sync(&codec->dev);
 }
 EXPORT_SYMBOL_GPL(snd_hdac_power_up);
 
 /**
- * snd_hdac_power_up - decrement the runtime pm counter
+ * snd_hdac_power_down - power down the codec
  * @codec: the codec object
  */
 void snd_hdac_power_down(struct hdac_device *codec)
 {
 	struct device *dev = &codec->dev;
 
-	if (atomic_read(&codec->in_pm))
-		return;
 	pm_runtime_mark_last_busy(dev);
 	pm_runtime_put_autosuspend(dev);
 }
diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c
index 1eb43209fe2c..64876fa357c9 100644
--- a/sound/hda/hdac_regmap.c
+++ b/sound/hda/hdac_regmap.c
@@ -402,9 +402,9 @@ int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
 
 	err = reg_raw_write(codec, reg, val);
 	if (err == -EAGAIN) {
-		snd_hdac_power_up(codec);
+		snd_hdac_power_up_pm(codec);
 		err = reg_raw_write(codec, reg, val);
-		snd_hdac_power_down(codec);
+		snd_hdac_power_down_pm(codec);
 	}
 	return err;
 }
@@ -434,9 +434,9 @@ int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg,
 
 	err = reg_raw_read(codec, reg, val);
 	if (err == -EAGAIN) {
-		snd_hdac_power_up(codec);
+		snd_hdac_power_up_pm(codec);
 		err = reg_raw_read(codec, reg, val);
-		snd_hdac_power_down(codec);
+		snd_hdac_power_down_pm(codec);
 	}
 	return err;
 }