[PATCH] drm/fb-helper: Fix hpd vs. initial config races
Daniel Vetter
daniel at ffwll.ch
Tue Apr 22 01:26:37 PDT 2014
On Thu, Apr 17, 2014 at 10:38:17AM +0200, Thierry Reding wrote:
> On Wed, Apr 16, 2014 at 04:45:21PM +0200, Daniel Vetter wrote:
> > Some drivers need to be able to have a perfect race-free fbcon setup.
> > Current drivers only enable hotplug processing after the call to
> > drm_fb_helper_initial_config which leaves a tiny but important race.
> >
> > This race is especially noticable on embedded platforms where the
> > driver itself enables the voltage for the hdmi output, since only then
> > will monitors (after a bit of delay, as usual) respond by asserting
> > the hpd pin.
> >
> > Most of the infrastructure is already there with the split-out
> > drm_fb_helper_init. And drm_fb_helper_initial_config already has all
> > the required locking to handle concurrent hpd events since
> >
> > commit 53f1904bced78d7c00f5d874c662ec3ac85d0f9f
> > Author: Daniel Vetter <daniel.vetter at ffwll.ch>
> > Date: Thu Mar 20 14:26:35 2014 +0100
> >
> > drm/fb-helper: improve drm_fb_helper_initial_config locking
> >
> > The only missing bit is making drm_fb_helper_hotplug_event save
> > against concurrent calls of drm_fb_helper_initial_config. The only
> > unprotected bit is the check for fb_helper->fb.
> >
> > With that drivers can first initialize the fb helper, then enabel
> > hotplug processing and then set up the initial config all in a
> > completely race-free manner. Update kerneldoc and convert i915 as a
> > proof of concept.
> >
> > Feature requested by Thierry since his tegra driver atm reliably boots
> > slowly enough to misses the hotplug event for an external hdmi screen,
> > but also reliably boots to quickly for the hpd pin to be asserted when
> > the fb helper calls into the hdmi ->detect function.
> >
> > Cc: Thierry Reding <treding at nvidia.com>
> > Signed-off-by: Daniel Vetter <daniel.vetter at ffwll.ch>
> > ---
> > drivers/gpu/drm/drm_fb_helper.c | 11 +++++------
> > drivers/gpu/drm/i915/i915_dma.c | 3 ---
> > drivers/gpu/drm/i915/i915_drv.c | 2 --
> > drivers/gpu/drm/i915/i915_drv.h | 1 -
> > drivers/gpu/drm/i915/i915_irq.c | 4 ----
> > 5 files changed, 5 insertions(+), 16 deletions(-)
>
> The FB helper parts:
>
> Tested-by: Thierry Reding <treding at nvidia.com>
>
> But I have one comment below...
>
> > diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
> [...]
> > - * Note that the driver must ensure that this is only called _after_ the fb has
> > - * been fully set up, i.e. after the call to drm_fb_helper_initial_config.
> > + * Note that drivers may call this even before calling
> > + * drm_fb_helper_initial_config but only aftert drm_fb_helper_init. This allows
>
> I don't think the requirement is that strict. To elaborate: on Tegra we
> cannot call drm_fb_helper_init() because the number of CRTCs and
> connectors isn't known this early. We determine that dynamically after
> all sub-devices have been initialized. So instead of calling
> drm_fb_helper_init() before drm_kms_helper_poll_init(), I did something
> more minimal (see attached patch). It's kind of difficult to tell
> because of the context, but tegra_drm_fb_prepare() sets up the mode
> config and functions and allocate memory for the FB helper and sets the
> FB helper functions.
>
> This may not be enough for all drivers, but on Tegra the implementation
> of .output_poll_changed() simply calls drm_fb_helper_hotplug_event(),
> which will work fine with just that rudimentary initialization.
Hm yeah I think this should be sufficient, too. It would be good to
extract this minimal initialization into a new drm_fb_helper_prepare
function and update the kerneldoc a bit more. Maybe as a patch on top of
mine?
Then we could merge this all as an early tegra-next pull to Dave.
-Daniel
>
> Thierry
> From ea394150524c8b54ee4131ad830bf5beb6b1056e Mon Sep 17 00:00:00 2001
> From: Thierry Reding <treding at nvidia.com>
> Date: Thu, 17 Apr 2014 10:02:17 +0200
> Subject: [PATCH] drm/tegra: Implement race-free hotplug detection
>
> A race condition currently exists on Tegra, where it can happen that a
> monitor attached via HDMI isn't detected during the initial FB helper
> setup, but the hotplug event happens too early to be processed by the
> poll helpers because they haven't been initialized yet. This happens
> because on some boards the HDMI driver can control the regulator that
> supplies the +5V pin on the HDMI connector. Therefore depending on the
> timing between the initialization of the HDMI driver and the rest of
> DRM, it's possible that the monitor returns the hotplug signal right
> within the window where we would miss it.
>
> Unfortunately, drm_kms_helper_poll_init() will wreak havoc when called
> before at least some parts of the FB helpers have been set up.
>
> This commit fixes this by splitting out the minimum of initialization
> required to make drm_kms_helper_poll_init() work into a separate
> function that can be called early. It is then safe to move all of the
> poll helper initialization to an earlier point in time (before the
> HDMI output driver has a chance to enable the +5V supply). That way if
> the hotplug signal is returned before the initial FB helper setup, the
> monitor will be forcefully detected at that point, and if the hotplug
> signal is returned after that it will be properly handled by the poll
> helpers.
>
> Signed-off-by: Thierry Reding <treding at nvidia.com>
> ---
> drivers/gpu/drm/tegra/drm.c | 8 ++++++--
> drivers/gpu/drm/tegra/drm.h | 1 +
> drivers/gpu/drm/tegra/fb.c | 50 ++++++++++++++++++++++++++++++---------------
> 3 files changed, 40 insertions(+), 19 deletions(-)
>
> diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c
> index 09ee77923d67..d492c2f12ca8 100644
> --- a/drivers/gpu/drm/tegra/drm.c
> +++ b/drivers/gpu/drm/tegra/drm.c
> @@ -41,6 +41,12 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
>
> drm_mode_config_init(drm);
>
> + err = tegra_drm_fb_prepare(drm);
> + if (err < 0)
> + return err;
> +
> + drm_kms_helper_poll_init(drm);
> +
> err = host1x_device_init(device);
> if (err < 0)
> return err;
> @@ -60,8 +66,6 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
> if (err < 0)
> return err;
>
> - drm_kms_helper_poll_init(drm);
> -
> return 0;
> }
>
> diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
> index 784fd5c77441..d100f706d818 100644
> --- a/drivers/gpu/drm/tegra/drm.h
> +++ b/drivers/gpu/drm/tegra/drm.h
> @@ -284,6 +284,7 @@ struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer,
> unsigned int index);
> bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer);
> bool tegra_fb_is_tiled(struct drm_framebuffer *framebuffer);
> +int tegra_drm_fb_prepare(struct drm_device *drm);
> int tegra_drm_fb_init(struct drm_device *drm);
> void tegra_drm_fb_exit(struct drm_device *drm);
> #ifdef CONFIG_DRM_TEGRA_FBDEV
> diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c
> index f7fca09d4921..2d7c589b550a 100644
> --- a/drivers/gpu/drm/tegra/fb.c
> +++ b/drivers/gpu/drm/tegra/fb.c
> @@ -271,14 +271,9 @@ static struct drm_fb_helper_funcs tegra_fb_helper_funcs = {
> .fb_probe = tegra_fbdev_probe,
> };
>
> -static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm,
> - unsigned int preferred_bpp,
> - unsigned int num_crtc,
> - unsigned int max_connectors)
> +static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm)
> {
> - struct drm_fb_helper *helper;
> struct tegra_fbdev *fbdev;
> - int err;
>
> fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL);
> if (!fbdev) {
> @@ -287,12 +282,23 @@ static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm,
> }
>
> fbdev->base.funcs = &tegra_fb_helper_funcs;
> - helper = &fbdev->base;
> + fbdev->base.dev = drm;
> +
> + return fbdev;
> +}
> +
> +static int tegra_fbdev_init(struct tegra_fbdev *fbdev,
> + unsigned int preferred_bpp,
> + unsigned int num_crtc,
> + unsigned int max_connectors)
> +{
> + struct drm_device *drm = fbdev->base.dev;
> + int err;
>
> err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors);
> if (err < 0) {
> dev_err(drm->dev, "failed to initialize DRM FB helper\n");
> - goto free;
> + return err;
> }
>
> err = drm_fb_helper_single_add_all_connectors(&fbdev->base);
> @@ -301,21 +307,17 @@ static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm,
> goto fini;
> }
>
> - drm_helper_disable_unused_functions(drm);
> -
> err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp);
> if (err < 0) {
> dev_err(drm->dev, "failed to set initial configuration\n");
> goto fini;
> }
>
> - return fbdev;
> + return 0;
>
> fini:
> drm_fb_helper_fini(&fbdev->base);
> -free:
> - kfree(fbdev);
> - return ERR_PTR(err);
> + return err;
> }
>
> static void tegra_fbdev_free(struct tegra_fbdev *fbdev)
> @@ -369,7 +371,7 @@ static const struct drm_mode_config_funcs tegra_drm_mode_funcs = {
> #endif
> };
>
> -int tegra_drm_fb_init(struct drm_device *drm)
> +int tegra_drm_fb_prepare(struct drm_device *drm)
> {
> #ifdef CONFIG_DRM_TEGRA_FBDEV
> struct tegra_drm *tegra = drm->dev_private;
> @@ -384,8 +386,7 @@ int tegra_drm_fb_init(struct drm_device *drm)
> drm->mode_config.funcs = &tegra_drm_mode_funcs;
>
> #ifdef CONFIG_DRM_TEGRA_FBDEV
> - tegra->fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc,
> - drm->mode_config.num_connector);
> + tegra->fbdev = tegra_fbdev_create(drm);
> if (IS_ERR(tegra->fbdev))
> return PTR_ERR(tegra->fbdev);
> #endif
> @@ -393,6 +394,21 @@ int tegra_drm_fb_init(struct drm_device *drm)
> return 0;
> }
>
> +int tegra_drm_fb_init(struct drm_device *drm)
> +{
> +#ifdef CONFIG_DRM_TEGRA_FBDEV
> + struct tegra_drm *tegra = drm->dev_private;
> + int err;
> +
> + err = tegra_fbdev_init(tegra->fbdev, 32, drm->mode_config.num_crtc,
> + drm->mode_config.num_connector);
> + if (err < 0)
> + return err;
> +#endif
> +
> + return 0;
> +}
> +
> void tegra_drm_fb_exit(struct drm_device *drm)
> {
> #ifdef CONFIG_DRM_TEGRA_FBDEV
> --
> 1.9.2
>
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
More information about the dri-devel
mailing list