Skip to content

Commit 1a462be

Browse files
committed
ALSA: hda: Manage concurrent reg access more properly
In the commit 8e85def ("ALSA: hda: enable regmap internal locking"), we re-enabled the regmap lock due to the reported regression that showed the possible concurrent accesses. It was a temporary workaround, and there are still a few opened races even after the revert. In this patch, we cover those still opened windows with a proper mutex lock and disable the regmap internal lock again. First off, the patch introduces a new snd_hdac_device.regmap_lock mutex that is applied for each snd_hdac_regmap_*() call, including read, write and update helpers. The mutex is applied carefully so that it won't block the self-power-up procedure in the helper function. Also, this assures the protection for the accesses without regmap, too. The snd_hdac_regmap_update_raw() is refactored to use the standard regmap_update_bits_check() function instead of the open-code. The non-regmap case is still open-coded but it's an easy part. The all read and write operations are in the single mutex protection, so it's now race-free. In addition, a couple of new helper functions are added: snd_hdac_regmap_update_raw_once() and snd_hdac_regmap_sync(). Both are called from HD-audio legacy driver. The former is to initialize the given verb bits but only once when it's not initialized yet. Due to this condition, the function invokes regcache_cache_only(), and it's now performed inside the regmap_lock (formerly it was racy) too. The latter function is for simply invoking regcache_sync() inside the regmap_lock, which is called from the codec resume call path. Along with that, the HD-audio codec driver code is slightly modified / simplified to adapt those new functions. And finally, snd_hdac_regmap_read_raw(), *_write_raw(), etc are rewritten with the helper macro. It's just for simplification because the code logic is identical among all those functions. Tested-by: Kai Vehmanen <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 73ac9f5 commit 1a462be

File tree

10 files changed

+135
-54
lines changed

10 files changed

+135
-54
lines changed

include/sound/hda_regmap.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
2424
unsigned int val);
2525
int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
2626
unsigned int mask, unsigned int val);
27+
int snd_hdac_regmap_update_raw_once(struct hdac_device *codec, unsigned int reg,
28+
unsigned int mask, unsigned int val);
29+
void snd_hdac_regmap_sync(struct hdac_device *codec);
2730

2831
/**
2932
* snd_hdac_regmap_encode_verb - encode the verb to a pseudo register

include/sound/hdaudio.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ struct hdac_device {
8686

8787
/* regmap */
8888
struct regmap *regmap;
89+
struct mutex regmap_lock;
8990
struct snd_array vendor_verbs;
9091
bool lazy_cache:1; /* don't wake up for writes */
9192
bool caps_overwriting:1; /* caps overwrite being in process */

sound/hda/hdac_device.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ int snd_hdac_device_init(struct hdac_device *codec, struct hdac_bus *bus,
5757
codec->addr = addr;
5858
codec->type = HDA_DEV_CORE;
5959
mutex_init(&codec->widget_lock);
60+
mutex_init(&codec->regmap_lock);
6061
pm_runtime_set_active(&codec->dev);
6162
pm_runtime_get_noresume(&codec->dev);
6263
atomic_set(&codec->in_pm, 0);

sound/hda/hdac_regmap.c

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ static const struct regmap_config hda_regmap_cfg = {
363363
.reg_write = hda_reg_write,
364364
.use_single_read = true,
365365
.use_single_write = true,
366+
.disable_locking = true,
366367
};
367368

368369
/**
@@ -425,12 +426,29 @@ EXPORT_SYMBOL_GPL(snd_hdac_regmap_add_vendor_verb);
425426
static int reg_raw_write(struct hdac_device *codec, unsigned int reg,
426427
unsigned int val)
427428
{
429+
int err;
430+
431+
mutex_lock(&codec->regmap_lock);
428432
if (!codec->regmap)
429-
return hda_reg_write(codec, reg, val);
433+
err = hda_reg_write(codec, reg, val);
430434
else
431-
return regmap_write(codec->regmap, reg, val);
435+
err = regmap_write(codec->regmap, reg, val);
436+
mutex_unlock(&codec->regmap_lock);
437+
return err;
432438
}
433439

440+
/* a helper macro to call @func_call; retry with power-up if failed */
441+
#define CALL_RAW_FUNC(codec, func_call) \
442+
({ \
443+
int _err = func_call; \
444+
if (_err == -EAGAIN) { \
445+
_err = snd_hdac_power_up_pm(codec); \
446+
if (_err >= 0) \
447+
_err = func_call; \
448+
snd_hdac_power_down_pm(codec); \
449+
} \
450+
_err;})
451+
434452
/**
435453
* snd_hdac_regmap_write_raw - write a pseudo register with power mgmt
436454
* @codec: the codec object
@@ -442,42 +460,29 @@ static int reg_raw_write(struct hdac_device *codec, unsigned int reg,
442460
int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
443461
unsigned int val)
444462
{
445-
int err;
446-
447-
err = reg_raw_write(codec, reg, val);
448-
if (err == -EAGAIN) {
449-
err = snd_hdac_power_up_pm(codec);
450-
if (err >= 0)
451-
err = reg_raw_write(codec, reg, val);
452-
snd_hdac_power_down_pm(codec);
453-
}
454-
return err;
463+
return CALL_RAW_FUNC(codec, reg_raw_write(codec, reg, val));
455464
}
456465
EXPORT_SYMBOL_GPL(snd_hdac_regmap_write_raw);
457466

458467
static int reg_raw_read(struct hdac_device *codec, unsigned int reg,
459468
unsigned int *val, bool uncached)
460469
{
470+
int err;
471+
472+
mutex_lock(&codec->regmap_lock);
461473
if (uncached || !codec->regmap)
462-
return hda_reg_read(codec, reg, val);
474+
err = hda_reg_read(codec, reg, val);
463475
else
464-
return regmap_read(codec->regmap, reg, val);
476+
err = regmap_read(codec->regmap, reg, val);
477+
mutex_unlock(&codec->regmap_lock);
478+
return err;
465479
}
466480

467481
static int __snd_hdac_regmap_read_raw(struct hdac_device *codec,
468482
unsigned int reg, unsigned int *val,
469483
bool uncached)
470484
{
471-
int err;
472-
473-
err = reg_raw_read(codec, reg, val, uncached);
474-
if (err == -EAGAIN) {
475-
err = snd_hdac_power_up_pm(codec);
476-
if (err >= 0)
477-
err = reg_raw_read(codec, reg, val, uncached);
478-
snd_hdac_power_down_pm(codec);
479-
}
480-
return err;
485+
return CALL_RAW_FUNC(codec, reg_raw_read(codec, reg, val, uncached));
481486
}
482487

483488
/**
@@ -504,6 +509,35 @@ int snd_hdac_regmap_read_raw_uncached(struct hdac_device *codec,
504509
return __snd_hdac_regmap_read_raw(codec, reg, val, true);
505510
}
506511

512+
static int reg_raw_update(struct hdac_device *codec, unsigned int reg,
513+
unsigned int mask, unsigned int val)
514+
{
515+
unsigned int orig;
516+
bool change;
517+
int err;
518+
519+
mutex_lock(&codec->regmap_lock);
520+
if (codec->regmap) {
521+
err = regmap_update_bits_check(codec->regmap, reg, mask, val,
522+
&change);
523+
if (!err)
524+
err = change ? 1 : 0;
525+
} else {
526+
err = hda_reg_read(codec, reg, &orig);
527+
if (!err) {
528+
val &= mask;
529+
val |= orig & ~mask;
530+
if (val != orig) {
531+
err = hda_reg_write(codec, reg, val);
532+
if (!err)
533+
err = 1;
534+
}
535+
}
536+
}
537+
mutex_unlock(&codec->regmap_lock);
538+
return err;
539+
}
540+
507541
/**
508542
* snd_hdac_regmap_update_raw - update a pseudo register with power mgmt
509543
* @codec: the codec object
@@ -515,20 +549,58 @@ int snd_hdac_regmap_read_raw_uncached(struct hdac_device *codec,
515549
*/
516550
int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
517551
unsigned int mask, unsigned int val)
552+
{
553+
return CALL_RAW_FUNC(codec, reg_raw_update(codec, reg, mask, val));
554+
}
555+
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw);
556+
557+
static int reg_raw_update_once(struct hdac_device *codec, unsigned int reg,
558+
unsigned int mask, unsigned int val)
518559
{
519560
unsigned int orig;
520561
int err;
521562

522-
val &= mask;
523-
err = snd_hdac_regmap_read_raw(codec, reg, &orig);
524-
if (err < 0)
525-
return err;
526-
val |= orig & ~mask;
527-
if (val == orig)
528-
return 0;
529-
err = snd_hdac_regmap_write_raw(codec, reg, val);
563+
if (!codec->regmap)
564+
return reg_raw_update(codec, reg, mask, val);
565+
566+
mutex_lock(&codec->regmap_lock);
567+
regcache_cache_only(codec->regmap, true);
568+
err = regmap_read(codec->regmap, reg, &orig);
569+
regcache_cache_only(codec->regmap, false);
530570
if (err < 0)
531-
return err;
532-
return 1;
571+
err = regmap_update_bits(codec->regmap, reg, mask, val);
572+
mutex_unlock(&codec->regmap_lock);
573+
return err;
533574
}
534-
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw);
575+
576+
/**
577+
* snd_hdac_regmap_update_raw_once - initialize the register value only once
578+
* @codec: the codec object
579+
* @reg: pseudo register
580+
* @mask: bit mask to update
581+
* @val: value to update
582+
*
583+
* Performs the update of the register bits only once when the register
584+
* hasn't been initialized yet. Used in HD-audio legacy driver.
585+
* Returns zero if successful or a negative error code
586+
*/
587+
int snd_hdac_regmap_update_raw_once(struct hdac_device *codec, unsigned int reg,
588+
unsigned int mask, unsigned int val)
589+
{
590+
return CALL_RAW_FUNC(codec, reg_raw_update_once(codec, reg, mask, val));
591+
}
592+
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw_once);
593+
594+
/**
595+
* snd_hdac_regmap_sync - sync out the cached values for PM resume
596+
* @codec: the codec object
597+
*/
598+
void snd_hdac_regmap_sync(struct hdac_device *codec)
599+
{
600+
if (codec->regmap) {
601+
mutex_lock(&codec->regmap_lock);
602+
regcache_sync(codec->regmap);
603+
mutex_unlock(&codec->regmap_lock);
604+
}
605+
}
606+
EXPORT_SYMBOL_GPL(snd_hdac_regmap_sync);

sound/pci/hda/hda_codec.c

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,18 @@ int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
12671267
}
12681268
EXPORT_SYMBOL_GPL(snd_hda_override_amp_caps);
12691269

1270+
static unsigned int encode_amp(struct hda_codec *codec, hda_nid_t nid,
1271+
int ch, int dir, int idx)
1272+
{
1273+
unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
1274+
1275+
/* enable fake mute if no h/w mute but min=mute */
1276+
if ((query_amp_caps(codec, nid, dir) &
1277+
(AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) == AC_AMPCAP_MIN_MUTE)
1278+
cmd |= AC_AMP_FAKE_MUTE;
1279+
return cmd;
1280+
}
1281+
12701282
/**
12711283
* snd_hda_codec_amp_update - update the AMP mono value
12721284
* @codec: HD-audio codec
@@ -1282,12 +1294,8 @@ EXPORT_SYMBOL_GPL(snd_hda_override_amp_caps);
12821294
int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid,
12831295
int ch, int dir, int idx, int mask, int val)
12841296
{
1285-
unsigned int cmd = snd_hdac_regmap_encode_amp(nid, ch, dir, idx);
1297+
unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
12861298

1287-
/* enable fake mute if no h/w mute but min=mute */
1288-
if ((query_amp_caps(codec, nid, dir) &
1289-
(AC_AMPCAP_MUTE | AC_AMPCAP_MIN_MUTE)) == AC_AMPCAP_MIN_MUTE)
1290-
cmd |= AC_AMP_FAKE_MUTE;
12911299
return snd_hdac_regmap_update_raw(&codec->core, cmd, mask, val);
12921300
}
12931301
EXPORT_SYMBOL_GPL(snd_hda_codec_amp_update);
@@ -1335,16 +1343,11 @@ EXPORT_SYMBOL_GPL(snd_hda_codec_amp_stereo);
13351343
int snd_hda_codec_amp_init(struct hda_codec *codec, hda_nid_t nid, int ch,
13361344
int dir, int idx, int mask, int val)
13371345
{
1338-
int orig;
1346+
unsigned int cmd = encode_amp(codec, nid, ch, dir, idx);
13391347

13401348
if (!codec->core.regmap)
13411349
return -EINVAL;
1342-
regcache_cache_only(codec->core.regmap, true);
1343-
orig = snd_hda_codec_amp_read(codec, nid, ch, dir, idx);
1344-
regcache_cache_only(codec->core.regmap, false);
1345-
if (orig >= 0)
1346-
return 0;
1347-
return snd_hda_codec_amp_update(codec, nid, ch, dir, idx, mask, val);
1350+
return snd_hdac_regmap_update_raw_once(&codec->core, cmd, mask, val);
13481351
}
13491352
EXPORT_SYMBOL_GPL(snd_hda_codec_amp_init);
13501353

@@ -2905,8 +2908,7 @@ static void hda_call_codec_resume(struct hda_codec *codec)
29052908
else {
29062909
if (codec->patch_ops.init)
29072910
codec->patch_ops.init(codec);
2908-
if (codec->core.regmap)
2909-
regcache_sync(codec->core.regmap);
2911+
snd_hda_regmap_sync(codec);
29102912
}
29112913

29122914
if (codec->jackpoll_interval)

sound/pci/hda/hda_generic.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6027,7 +6027,7 @@ int snd_hda_gen_init(struct hda_codec *codec)
60276027
/* call init functions of standard auto-mute helpers */
60286028
update_automute_all(codec);
60296029

6030-
regcache_sync(codec->core.regmap);
6030+
snd_hda_regmap_sync(codec);
60316031

60326032
if (spec->vmaster_mute.sw_kctl && spec->vmaster_mute.hook)
60336033
snd_hda_sync_vmaster_hook(&spec->vmaster_mute);

sound/pci/hda/hda_local.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ int snd_hda_codec_reset(struct hda_codec *codec);
138138
void snd_hda_codec_register(struct hda_codec *codec);
139139
void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec);
140140

141+
#define snd_hda_regmap_sync(codec) snd_hdac_regmap_sync(&(codec)->core)
142+
141143
enum {
142144
HDA_VMUTE_OFF,
143145
HDA_VMUTE_ON,

sound/pci/hda/patch_hdmi.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2404,7 +2404,7 @@ static int generic_hdmi_resume(struct hda_codec *codec)
24042404
int pin_idx;
24052405

24062406
codec->patch_ops.init(codec);
2407-
regcache_sync(codec->core.regmap);
2407+
snd_hda_regmap_sync(codec);
24082408

24092409
for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) {
24102410
struct hdmi_spec_per_pin *per_pin = get_pin(spec, pin_idx);

sound/pci/hda/patch_realtek.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ static int alc_resume(struct hda_codec *codec)
907907
if (!spec->no_depop_delay)
908908
msleep(150); /* to avoid pop noise */
909909
codec->patch_ops.init(codec);
910-
regcache_sync(codec->core.regmap);
910+
snd_hda_regmap_sync(codec);
911911
hda_call_check_power_status(codec, 0x01);
912912
return 0;
913913
}
@@ -3638,7 +3638,7 @@ static int alc269_resume(struct hda_codec *codec)
36383638
msleep(200);
36393639
}
36403640

3641-
regcache_sync(codec->core.regmap);
3641+
snd_hda_regmap_sync(codec);
36423642
hda_call_check_power_status(codec, 0x01);
36433643

36443644
/* on some machine, the BIOS will clear the codec gpio data when enter

sound/pci/hda/patch_via.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ static int via_resume(struct hda_codec *codec)
396396
/* some delay here to make jack detection working (bko#98921) */
397397
msleep(10);
398398
codec->patch_ops.init(codec);
399-
regcache_sync(codec->core.regmap);
399+
snd_hda_regmap_sync(codec);
400400
return 0;
401401
}
402402
#endif

0 commit comments

Comments
 (0)