[alsa-devel] [PATCH] ALSA: hda - call runtime_resume after S3 and S4 for each codec
Takashi Iwai
tiwai at suse.de
Mon Mar 18 13:59:58 CET 2019
On Mon, 18 Mar 2019 08:46:31 +0100,
Hui Wang wrote:
>
> On 2019/3/13 下午9:22, Hui Wang wrote:
> > On 2019/3/13 下午7:19, Takashi Iwai wrote:
> >> On Sat, 09 Mar 2019 16:19:47 +0100,
> >> Hui Wang wrote:
> >>> Recently we found the audio jack detection doesn't work after suspend
> >>> on many machines with Realtek codec. Sometimes the audio selection
> >>> dialogue didn't show up after users plugged headhphone/headset into
> >>> the headset jack, sometimes after uses plugged headphone/headset, then
> >>> click the sound icon on the upper-right corner of gnome-desktop, it
> >>> also showed the speaker rather than the headphone.
> >>>
> >>> The root cause is that before suspend, the codec already call the
> >>> runtime_suspend since this codec is not used by any apps, then in
> >>> resume, it will not call runtime_resume for this codec. But for some
> >>> realtek codec (so far, alc236, alc255 and alc891) with the specific
> >>> BIOS, if it doesn't run runtime_resume after suspend, all codec
> >>> functions including jack detection stop working anymore.
> >>>
> >>> This problem existed for a long time, but it was not exposed, that is
> >>> because if users is playing sound or recording sound, and at the same
> >>> time they run suspend, the runtime_suspend will be called in
> >>> pm_suspend and runtime_resume will be called in pm_resume; if audio
> >>> codec is not used by any apps and users run suspend, the
> >>> runtime_resume will not be called in pm_resume, then codec stops
> >>> working, but if users play sound or open sound-setting to check
> >>> audio device, this will trigger calling to runtime_resume (
> >>> via snd_hda_power_up), then the codec starts working again before
> >>> users notice this problem.
> >>>
> >>> Since we don't know how many codec and BIOS combinations have this
> >>> problem, to fix it, let the driver call runtime_resume for all codecs
> >>> in pm_resume, maybe for some codecs, this is not needed, but it is
> >>> harmless. After a codec is runtime resumed, if it is not used by any
> >>> apps, it will be runtime suspended soon and furthermore we don't run
> >>> suspend frequently, this change will not add much power consumption.
> >>>
> >>> Signed-off-by: Hui Wang <hui.wang at canonical.com>
> >>> ---
> >>> include/sound/hda_codec.h | 3 +++
> >>> sound/pci/hda/hda_codec.c | 26 +++++++++++++++++++++++---
> >>> 2 files changed, 26 insertions(+), 3 deletions(-)
> >>>
> >>> diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h
> >>> index cc7c8d42d4fd..a4e26d1d18bc 100644
> >>> --- a/include/sound/hda_codec.h
> >>> +++ b/include/sound/hda_codec.h
> >>> @@ -262,6 +262,9 @@ struct hda_codec {
> >>> unsigned int auto_runtime_pm:1; /* enable automatic codec runtime pm */
> >>> unsigned int force_pin_prefix:1; /* Add location prefix */
> >>> unsigned int link_down_at_suspend:1; /* link down at runtime suspend */
> >>> +#ifdef CONFIG_PM_SLEEP
> >>> + unsigned int already_rt_suspend:1; /* already runtime suspend before sleep */
> >>> +#endif
> >>> #ifdef CONFIG_PM
> >>> unsigned long power_on_acct;
> >>> unsigned long power_off_acct;
> >>> diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
> >>> index 5f2005098a60..7c2bbe25adde 100644
> >>> --- a/sound/pci/hda/hda_codec.c
> >>> +++ b/sound/pci/hda/hda_codec.c
> >>> @@ -2939,34 +2939,54 @@ static int hda_codec_runtime_resume(struct device *dev)
> >>> #endif /* CONFIG_PM */
> >>>
> >>> #ifdef CONFIG_PM_SLEEP
> >>> +static int hda_codec_force_resume(struct device *dev)
> >>> +{
> >>> + struct hda_codec *codec = dev_to_hda_codec(dev);
> >>> +
> >>> + if (codec->already_rt_suspend) {
> >>> + int ret;
> >>> +
> >>> + pm_runtime_get_noresume(dev);
> >>> + ret = pm_runtime_force_resume(dev);
> >>> + pm_runtime_put(dev);
> >>> + return ret;
> >>> + } else
> >>> + return pm_runtime_force_resume(dev);
> >>> +}
> >> I don't think we need codec->already_rt_suspend flag check. And it
> >> deserves a comment. i.e. hda_codec_force_resume() will be something
> >> like:
> >>
> >> static int hda_codec_force_resume(struct device *dev)
> >> {
> >> int ret;
> >>
> >> /* The get/put pair below enforces the runtime resume even if the
> >> * device hasn't been used at suspend time. This trick is needed to
> >> * update the jack state change during the sleep.
> >> */
> >> pm_runtime_get_noresume(dev);
> >> ret = pm_runtime_force_resume(dev);
> >> pm_runtime_put(dev);
> >> return ret;
> >> }
> >>
> >>
> >> Could you check whether this works?
> >>
> >>
> >> thanks,
> >>
> >> Takashi
> > Got it, will test it this weekend or next week after I get the hardware.
> >
> > Thanks,
> >
> > Hui.
>
> When I tested the patch on the v5.0 (from v5.0-rc1 to v5.0), I found the
> hda_codec_runtime_resume() is called after S3 even without my patch.
> That is because one patch is introduced in the kernel from v5.0-rc1:
> 3baffc4a84d7 (ALSA: hda/intel: Refactoring PM code), the commit header
> said there shouldn't be any functional difference after refactoring, but
> there is one functional difference which makes the
> hda_codec_runtime_resume() is called after S3, that is in the
> azx_resume(), it will trigger jackpoll_work, before refactoring, it won't.
Oh, indeed this is an unexpected side-effect although it's partially
positive...
> It looks like this commit fixed my audio issue, but after investigating,
> I found it introduced a new issue, that is after s3, all codec is
> keeping in the runtime active mode even there is no application uses
> them, and it will not enter runtime_suspend() automatically. If users
> play sound or uses audio device, after using them, then the codec can
> enter runtime_suspend() again.
>
> I did a simple debug, found it is a balance issue of
> dev->power.usage_count, azx_resume()->trigger
> jackpoll_work()->...->codec_exec_verb()->snd_hda_power_up_pm(), this
> will make usage_count plus 1 and codec->in_pm unchanged (keep to be 0),
> and before it execute snd_hda_power_down_pm(), the hda_codec_pm_resume()
> starts running in another kthread, since the usage_count is added 1 in
> the previous kthread (workqueue), the hda_codec_runtime_resume() is
> triggered, then it fixed my issue, but the hda_codec_runtime_resume()
> will call snd_hdac_enter_pm(), this will make codec->in_pm to be 1, and
> then it will call codec_exec_verb()->snd_hda_power_up_pm(), the
> codec->in_pm is 2 now, then this kthread will blocked by
> mutex_lock(&bus->core.cmd_mutex), now the 1st kthread execute
> snd_hda_power_down_pm(), it will make codec->in_pm to be 1 and make
> dev->power.usage_count unchanged, then the 2nd kthread execute
> snd_hda_power_down_pm(), the codec->in_pm return to 0 now but
> dev->power.usage_count is still unchanged, this introduced the new issue.
>
> In conclusion, when jackpoll_work() call snd_hda_power_up_pm(), it is
> out the range of snd_hdac_enter_pm() ... snd_hdac_leave_pm(), while when
> jackpoll_work() call snd_hda_power_down_pm(), it is in the middle of
> snd_hdac_enter_pm() ... snd_hdac_leave_pm(). This introduced the new
> issue. I tried some ways to make it balanced, but still can't work.
Thanks for the detailed analysis. This is an interesting case.
Actually the problem is triggered by two factors:
- the S3 resume of the controller triggers async work to kick off the
runtime resume of the children.
- S3 resume of the codec (children) is implemented with
pm_runtime_force_resume().
And one can say that the culprit is partially the design of
pm_runtime_force_resume(). The pm_runtime_force_resume() assumes that
no other things touching the same code path and calls the callback
directly *after* setting RPM_ACTIVE state. That, of course, would
race if a concurrent runtime resume is running.
So, we can fix either the runtime PM core stuff, or fix it locally by
your patch. Since it's a regression and our PM handling is somewhat
special, I'd take the latter for now as a quick resolution.
> So far, the only fix I can figure out is: restore the azx_resume(), then
> apply my previous patch:
This looks OK (and I suppose you've tested it).
Could you resubmit with a proper patch description and ritual
sign-off, etc?
thanks,
Takashi
> This is the 1st patch:
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index e5c49003e75f..59e6af2db847 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -947,7 +947,7 @@ static void __azx_runtime_suspend(struct azx *chip)
> display_power(chip, false);
> }
>
> -static void __azx_runtime_resume(struct azx *chip)
> +static void __azx_runtime_resume(struct azx *chip, bool from_rt)
> {
> struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
> struct hdac_bus *bus = azx_bus(chip);
> @@ -964,7 +964,7 @@ static void __azx_runtime_resume(struct azx *chip)
> azx_init_pci(chip);
> hda_intel_init_chip(chip, true);
>
> - if (status) {
> + if (status && from_rt) {
> list_for_each_codec(codec, &chip->bus)
> if (status & (1 << codec->addr))
> schedule_delayed_work(&codec->jackpoll_work,
> @@ -1016,7 +1016,7 @@ static int azx_resume(struct device *dev)
> chip->msi = 0;
> if (azx_acquire_irq(chip, 1) < 0)
> return -EIO;
> - __azx_runtime_resume(chip);
> + __azx_runtime_resume(chip, false);
> snd_power_change_state(card, SNDRV_CTL_POWER_D0);
>
> trace_azx_resume(chip);
> @@ -1081,7 +1081,7 @@ static int azx_runtime_resume(struct device *dev)
> chip = card->private_data;
> if (!azx_has_pm_runtime(chip))
> return 0;
> - __azx_runtime_resume(chip);
> + __azx_runtime_resume(chip, true);
>
> /* disable controller Wake Up event*/
> azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) &
>
>
>
> This is the 2nd patch:
> diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
> index 5f2005098a60..ec0b8595eb4d 100644
> --- a/sound/pci/hda/hda_codec.c
> +++ b/sound/pci/hda/hda_codec.c
> @@ -2939,6 +2939,20 @@ static int hda_codec_runtime_resume(struct device
> *dev)
> #endif /* CONFIG_PM */
>
> #ifdef CONFIG_PM_SLEEP
> +static int hda_codec_force_resume(struct device *dev)
> +{
> + int ret;
> +
> + /* The get/put pair below enforces the runtime resume even if the
> + * device hasn't been used at suspend time. This trick is needed to
> + * update the jack state change during the sleep.
> + */
> + pm_runtime_get_noresume(dev);
> + ret = pm_runtime_force_resume(dev);
> + pm_runtime_put(dev);
> + return ret;
> +}
> +
> static int hda_codec_pm_suspend(struct device *dev)
> {
> dev->power.power_state = PMSG_SUSPEND;
> @@ -2948,7 +2962,7 @@ static int hda_codec_pm_suspend(struct device *dev)
> static int hda_codec_pm_resume(struct device *dev)
> {
> dev->power.power_state = PMSG_RESUME;
> - return pm_runtime_force_resume(dev);
> + return hda_codec_force_resume(dev);
> }
>
> static int hda_codec_pm_freeze(struct device *dev)
> @@ -2960,13 +2974,13 @@ static int hda_codec_pm_freeze(struct device *dev)
> static int hda_codec_pm_thaw(struct device *dev)
> {
> dev->power.power_state = PMSG_THAW;
> - return pm_runtime_force_resume(dev);
> + return hda_codec_force_resume(dev);
> }
>
> static int hda_codec_pm_restore(struct device *dev)
> {
> dev->power.power_state = PMSG_RESTORE;
> - return pm_runtime_force_resume(dev);
> + return hda_codec_force_resume(dev);
> }
> #endif /* CONFIG_PM_SLEEP */
>
>
> >
> >> _______________________________________________
> >> Alsa-devel mailing list
> >> Alsa-devel at alsa-project.org
> >> https://mailman.alsa-project.org/mailman/listinfo/alsa-devel
> >>
>
More information about the Alsa-devel
mailing list