Re: [PATCH v2 2/9] drm/i915: Add get_eld audio component

Daniel Vetter
2015-12-04T15:00:50Z

On Fri, Dec 04, 2015 at 11:49:46AM +0100, Takashi Iwai wrote:
> On Fri, 04 Dec 2015 11:21:02 +0100,
> Daniel Vetter wrote:
> >
> > On Tue, Dec 01, 2015 at 05:09:51PM +0100, Takashi Iwai wrote:
> > > Implement a new i915_audio_component_ops, get_eld(). It's called by
> > > the audio driver to fetch the current audio status and ELD of the
> > > given HDMI/DP port. It returns the size of expected ELD bytes if it's
> > > valid, zero if no valid ELD is found, or a negative error code. The
> > > current state of audio on/off is stored in the given pointer, too.
> > >
> > > Note that the returned size isn't limited to the given max bytes. If
> > > the size is greater than the max bytes, it means that only a part of
> > > ELD has been copied back.
> > >
> > > A big warning about the usage of this callback is: you must not call
> > > it from eld_notify. The eld_notify itself is called in the modeset
> > > lock, and it leads to a deadlock since get_eld takes the modeset lock,
> > > too. You need to call get_eld in a work, for example, in such a case.
> > > We'll see the actual implementation in the later patch in
> > > sound/pci/hda/patch_hdmi.c.
> > >
> > > For achieving this implementation, a new field audio_enabled is added
> > > to struct intel_digital_port. This is set/reset at each audio
> > > enable/disable call in intel_audio.c. It's protected with the modeset
> > > lock as well.
> > >
> > > Signed-off-by: Takashi Iwai <tiwai@suse.de>
> > > ---
> > > v1->v2:
> > > * Use modeset lock for get_eld lock, drop av mutex
> > > * Return the expected size from get_eld, not the copied size
> > >
> > > drivers/gpu/drm/i915/intel_audio.c | 40 ++++++++++++++++++++++++++++++++++++++
> > > drivers/gpu/drm/i915/intel_drv.h | 1 +
> > > include/drm/i915_component.h | 6 ++++++
> > > 3 files changed, 47 insertions(+)
> > >
> > > diff --git a/drivers/gpu/drm/i915/intel_audio.c b/drivers/gpu/drm/i915/intel_audio.c
> > > index 0c38cc6c82ae..1965a61769ea 100644
> > > --- a/drivers/gpu/drm/i915/intel_audio.c
> > > +++ b/drivers/gpu/drm/i915/intel_audio.c
> > > @@ -521,6 +521,7 @@ void intel_audio_codec_enable(struct intel_encoder *intel_encoder)
> > >
> > > connector->eld[6] = drm_av_sync_delay(connector, adjusted_mode) / 2;
> > >
> > > + intel_dig_port->audio_enabled = true;
> > > if (dev_priv->display.audio_codec_enable)
> > > dev_priv->display.audio_codec_enable(connector, intel_encoder,
> > > adjusted_mode);
> > > @@ -545,6 +546,7 @@ void intel_audio_codec_disable(struct intel_encoder *intel_encoder)
> > > struct intel_digital_port *intel_dig_port = enc_to_dig_port(encoder);
> > > enum port port = intel_dig_port->port;
> > >
> > > + intel_dig_port->audio_enabled = false;
> > > if (dev_priv->display.audio_codec_disable)
> > > dev_priv->display.audio_codec_disable(intel_encoder);
> > >
> > > @@ -702,6 +704,43 @@ static int i915_audio_component_sync_audio_rate(struct device *dev,
> > > return 0;
> > > }
> > >
> > > +static int i915_audio_component_get_eld(struct device *dev, int port,
> > > + bool *enabled,
> > > + unsigned char *buf, int max_bytes)
> > > +{
> > > + struct drm_i915_private *dev_priv = dev_to_i915(dev);
> > > + struct drm_device *drm_dev = dev_priv->dev;
> > > + struct intel_encoder *intel_encoder;
> > > + struct intel_digital_port *intel_dig_port;
> > > + struct drm_connector *connector;
> > > + unsigned char *eld;
> > > + int ret = -EINVAL;
> > > +
> > > + drm_modeset_lock_all(drm_dev);
> >
> > This is super expensive and shouldn't ever be used in new code. So either
> > just the connection_mutex or resurrect the av_mutex and just cache what
> > you need under that.
>
> OK, I need to make it harder, then.
>
> > Tbh I prefer the separate lock + cache for such
> > specific things since it completely avoids spreading and entangling
> > locking contexts. We use the same design to get modeset information into
> > the PSR tracking, FBC tracking and other code which sits between KMS and
> > other subsystems.
>
> I didn't want to be involved with the modeset lock, but it has to be.
> This function calls drm_select_eld() and it requires both
> mode_config.mutex and connection_mutex.
>
> (snip)
> > > struct i915_audio_component_audio_ops {
> > > @@ -55,6 +58,9 @@ struct i915_audio_component_audio_ops {
> > > * pin sense and/or ELD information has changed.
> > > * @audio_ptr: HDA driver object
> > > * @port: Which port has changed (PORTA / PORTB / PORTC etc)
> > > + *
> > > + * Note that you can't call i915_audio_component_ops.get_eld directly
> > > + * from the notifier callback as it may lead to deadlocks.
> >
> > With av_mutex we don't even need that note here ;-)
>
> So here is the problem. av_mutex itself doesn't suffice for
> drm_select_eld(), and taking the modeset lock leads to a deadlock when
> invoked from eld_notify.

Yeah, select_eld is broken currently in atomic land, and we need to fix
that. It's by far not the only one that's iffy.

> Maybe one alternative is to pass the audio state and ELD bytes already
> in eld_notify itself. Then it doesn't have to call get_eld from
> there. But we still need the explicit fetch in some cases (at the
> first probe and at resume), so get_eld op is still required. Then it
> needs to take locks by itself.

Well my idea was that in the enable/disable hooks (where we should hold
relevant modeset locks already, except for that icky unsolved thing I need
to take care of anyway), and store a copy (protected by av_lock). Then
get_eld would only look at that copy. That kind of "cache relevant data,
protected with new leaf lock" trick is what I meant we should use here,
and it's the usual approach to avoid acquiring modeset locks from random
other subsystems (since that ends in deadlocks sooner or later). So no
calling drm_get_eld from the new get_eld hook at all.

There's still the problem that currently calling drm_get_eld is broken
with atomic modesets even in i915 audio enable/disable functions. But
that's a preexisting problem with atomic, and one I know we need to fix
still before we can enable atomic for real (all legacy paths get away
since there we take more locks).

Cheers, Daniel
--
Daniel Vetter
Software Engineer, Intel Corporation