Skip to content

Commit fc4f000

Browse files
committed
ALSA: hda - Fix unexpected resume through regmap code path
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: Jiri Slaby <[email protected]> Cc: <[email protected]> Signed-off-by: Takashi Iwai <[email protected]>
1 parent ad09ef2 commit fc4f000

File tree

3 files changed

+64
-23
lines changed

3 files changed

+64
-23
lines changed

include/sound/hdaudio.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,13 @@ int snd_hdac_power_up(struct hdac_device *codec);
168168
int snd_hdac_power_down(struct hdac_device *codec);
169169
int snd_hdac_power_up_pm(struct hdac_device *codec);
170170
int snd_hdac_power_down_pm(struct hdac_device *codec);
171+
int snd_hdac_keep_power_up(struct hdac_device *codec);
171172
#else
172173
static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; }
173174
static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; }
174175
static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; }
175176
static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; }
177+
static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; }
176178
#endif
177179

178180
/*

sound/hda/hdac_device.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,22 @@ int snd_hdac_power_up_pm(struct hdac_device *codec)
611611
}
612612
EXPORT_SYMBOL_GPL(snd_hdac_power_up_pm);
613613

614+
/* like snd_hdac_power_up_pm(), but only increment the pm count when
615+
* already powered up. Returns -1 if not powered up, 1 if incremented
616+
* or 0 if unchanged. Only used in hdac_regmap.c
617+
*/
618+
int snd_hdac_keep_power_up(struct hdac_device *codec)
619+
{
620+
if (!atomic_inc_not_zero(&codec->in_pm)) {
621+
int ret = pm_runtime_get_if_in_use(&codec->dev);
622+
if (!ret)
623+
return -1;
624+
if (ret < 0)
625+
return 0;
626+
}
627+
return 1;
628+
}
629+
614630
/**
615631
* snd_hdac_power_down_pm - power down the codec
616632
* @codec: the codec object

sound/hda/hdac_regmap.c

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@
2121
#include <sound/hdaudio.h>
2222
#include <sound/hda_regmap.h>
2323

24-
#ifdef CONFIG_PM
25-
#define codec_is_running(codec) \
26-
(atomic_read(&(codec)->in_pm) || \
27-
!pm_runtime_suspended(&(codec)->dev))
28-
#else
29-
#define codec_is_running(codec) true
30-
#endif
24+
static int codec_pm_lock(struct hdac_device *codec)
25+
{
26+
return snd_hdac_keep_power_up(codec);
27+
}
28+
29+
static void codec_pm_unlock(struct hdac_device *codec, int lock)
30+
{
31+
if (lock == 1)
32+
snd_hdac_power_down_pm(codec);
33+
}
3134

3235
#define get_verb(reg) (((reg) >> 8) & 0xfff)
3336

@@ -238,35 +241,46 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
238241
struct hdac_device *codec = context;
239242
int verb = get_verb(reg);
240243
int err;
244+
int pm_lock = 0;
241245

242-
if (!codec_is_running(codec) && verb != AC_VERB_GET_POWER_STATE)
243-
return -EAGAIN;
246+
if (verb != AC_VERB_GET_POWER_STATE) {
247+
pm_lock = codec_pm_lock(codec);
248+
if (pm_lock < 0)
249+
return -EAGAIN;
250+
}
244251
reg |= (codec->addr << 28);
245-
if (is_stereo_amp_verb(reg))
246-
return hda_reg_read_stereo_amp(codec, reg, val);
247-
if (verb == AC_VERB_GET_PROC_COEF)
248-
return hda_reg_read_coef(codec, reg, val);
252+
if (is_stereo_amp_verb(reg)) {
253+
err = hda_reg_read_stereo_amp(codec, reg, val);
254+
goto out;
255+
}
256+
if (verb == AC_VERB_GET_PROC_COEF) {
257+
err = hda_reg_read_coef(codec, reg, val);
258+
goto out;
259+
}
249260
if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE)
250261
reg &= ~AC_AMP_FAKE_MUTE;
251262

252263
err = snd_hdac_exec_verb(codec, reg, 0, val);
253264
if (err < 0)
254-
return err;
265+
goto out;
255266
/* special handling for asymmetric reads */
256267
if (verb == AC_VERB_GET_POWER_STATE) {
257268
if (*val & AC_PWRST_ERROR)
258269
*val = -1;
259270
else /* take only the actual state */
260271
*val = (*val >> 4) & 0x0f;
261272
}
262-
return 0;
273+
out:
274+
codec_pm_unlock(codec, pm_lock);
275+
return err;
263276
}
264277

265278
static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
266279
{
267280
struct hdac_device *codec = context;
268281
unsigned int verb;
269282
int i, bytes, err;
283+
int pm_lock = 0;
270284

271285
if (codec->caps_overwriting)
272286
return 0;
@@ -275,14 +289,21 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
275289
reg |= (codec->addr << 28);
276290
verb = get_verb(reg);
277291

278-
if (!codec_is_running(codec) && verb != AC_VERB_SET_POWER_STATE)
279-
return codec->lazy_cache ? 0 : -EAGAIN;
292+
if (verb != AC_VERB_SET_POWER_STATE) {
293+
pm_lock = codec_pm_lock(codec);
294+
if (pm_lock < 0)
295+
return codec->lazy_cache ? 0 : -EAGAIN;
296+
}
280297

281-
if (is_stereo_amp_verb(reg))
282-
return hda_reg_write_stereo_amp(codec, reg, val);
298+
if (is_stereo_amp_verb(reg)) {
299+
err = hda_reg_write_stereo_amp(codec, reg, val);
300+
goto out;
301+
}
283302

284-
if (verb == AC_VERB_SET_PROC_COEF)
285-
return hda_reg_write_coef(codec, reg, val);
303+
if (verb == AC_VERB_SET_PROC_COEF) {
304+
err = hda_reg_write_coef(codec, reg, val);
305+
goto out;
306+
}
286307

287308
switch (verb & 0xf00) {
288309
case AC_VERB_SET_AMP_GAIN_MUTE:
@@ -319,10 +340,12 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
319340
reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff);
320341
err = snd_hdac_exec_verb(codec, reg, 0, NULL);
321342
if (err < 0)
322-
return err;
343+
goto out;
323344
}
324345

325-
return 0;
346+
out:
347+
codec_pm_unlock(codec, pm_lock);
348+
return err;
326349
}
327350

328351
static const struct regmap_config hda_regmap_cfg = {

0 commit comments

Comments
 (0)