diff mbox series

drm/nouveau: don't attempt to schedule hpd_work on headless cards

Message ID 20240528215344.2442-1-anarsoul@gmail.com (mailing list archive)
State New, archived
Headers show
Series drm/nouveau: don't attempt to schedule hpd_work on headless cards | expand

Commit Message

Vasily Khoruzhick May 28, 2024, 9:52 p.m. UTC
If the card doesn't have display hardware, hpd_work and hpd_lock are
left uninitialized which causes BUG when attempting to schedule hpd_work
on runtime PM resume.

Fix it by adding headless flag to DRM and skip any hpd if it's set.

Fixes: ae1aadb1eb8d ("nouveau: don't fail driver load if no display hw present.")
Link: https://gitlab.freedesktop.org/drm/nouveau/-/issues/337
Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
---
 drivers/gpu/drm/nouveau/dispnv04/disp.c     |  2 +-
 drivers/gpu/drm/nouveau/dispnv50/disp.c     |  2 +-
 drivers/gpu/drm/nouveau/nouveau_connector.c |  3 +++
 drivers/gpu/drm/nouveau/nouveau_display.c   | 11 ++++++++++-
 drivers/gpu/drm/nouveau/nouveau_drv.h       |  1 +
 5 files changed, 16 insertions(+), 3 deletions(-)

Comments

Ben Skeggs May 28, 2024, 12:15 p.m. UTC | #1
On 29/5/24 07:52, Vasily Khoruzhick wrote:

> If the card doesn't have display hardware, hpd_work and hpd_lock are
> left uninitialized which causes BUG when attempting to schedule hpd_work
> on runtime PM resume.

Hi,

Good catch, thank you for looking at this.  A couple of initial comments 
below:

Ben.

>
> Fix it by adding headless flag to DRM and skip any hpd if it's set.
>
> Fixes: ae1aadb1eb8d ("nouveau: don't fail driver load if no display hw present.")
> Link: https://gitlab.freedesktop.org/drm/nouveau/-/issues/337
> Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
> ---
>   drivers/gpu/drm/nouveau/dispnv04/disp.c     |  2 +-
>   drivers/gpu/drm/nouveau/dispnv50/disp.c     |  2 +-
>   drivers/gpu/drm/nouveau/nouveau_connector.c |  3 +++
>   drivers/gpu/drm/nouveau/nouveau_display.c   | 11 ++++++++++-
>   drivers/gpu/drm/nouveau/nouveau_drv.h       |  1 +
>   5 files changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.c b/drivers/gpu/drm/nouveau/dispnv04/disp.c
> index 13705c5f1497..4b7497a8755c 100644
> --- a/drivers/gpu/drm/nouveau/dispnv04/disp.c
> +++ b/drivers/gpu/drm/nouveau/dispnv04/disp.c
> @@ -68,7 +68,7 @@ nv04_display_fini(struct drm_device *dev, bool runtime, bool suspend)
>   	if (nv_two_heads(dev))
>   		NVWriteCRTC(dev, 1, NV_PCRTC_INTR_EN_0, 0);
>   
> -	if (!runtime)
> +	if (!runtime && !drm->headless)
>   		cancel_work_sync(&drm->hpd_work);
>   
>   	if (!suspend)
> diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> index 88728a0b2c25..674dc567e179 100644
> --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> @@ -2680,7 +2680,7 @@ nv50_display_fini(struct drm_device *dev, bool runtime, bool suspend)
>   			nv50_mstm_fini(nouveau_encoder(encoder));
>   	}
>   
> -	if (!runtime)
> +	if (!runtime && !drm->headless)
>   		cancel_work_sync(&drm->hpd_work);
>   }
>   
> diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
> index 856b3ef5edb8..b315a2ef5b28 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_connector.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
> @@ -1190,6 +1190,9 @@ nouveau_connector_hpd(struct nouveau_connector *nv_connector, u64 bits)
>   	u32 mask = drm_connector_mask(&nv_connector->base);
>   	unsigned long flags;
>   
> +	if (drm->headless)
> +		return;
> +

You shouldn't need this change, the function can't be called if there's 
no display.


>   	spin_lock_irqsave(&drm->hpd_lock, flags);
>   	if (!(drm->hpd_pending & mask)) {
>   		nv_connector->hpd_pending |= bits;
> diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
> index aed5d5b51b43..1961ef665e97 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_display.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_display.c
> @@ -450,6 +450,9 @@ nouveau_display_hpd_resume(struct drm_device *dev)
>   {
>   	struct nouveau_drm *drm = nouveau_drm(dev);
>   
> +	if (drm->headless)
> +		return;
> +
>   	spin_lock_irq(&drm->hpd_lock);
>   	drm->hpd_pending = ~0;
>   	spin_unlock_irq(&drm->hpd_lock);
> @@ -468,6 +471,11 @@ nouveau_display_hpd_work(struct work_struct *work)
>   	int changed = 0;
>   	struct drm_connector *first_changed_connector = NULL;
>   
> +	WARN_ON_ONCE(drm->headless);
> +
> +	if (drm->headless)
> +		return;
> +

Same here.


>   	pm_runtime_get_sync(dev->dev);
>   
>   	spin_lock_irq(&drm->hpd_lock);
> @@ -635,7 +643,7 @@ nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime)
>   	}
>   	drm_connector_list_iter_end(&conn_iter);
>   
> -	if (!runtime)
> +	if (!runtime && !drm->headless)
>   		cancel_work_sync(&drm->hpd_work);
>   
>   	drm_kms_helper_poll_disable(dev);
> @@ -729,6 +737,7 @@ nouveau_display_create(struct drm_device *dev)
>   		/* no display hw */
>   		if (ret == -ENODEV) {
>   			ret = 0;
> +			drm->headless = true;
>   			goto disp_create_err;
>   		}
>   
> diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
> index e239c6bf4afa..25fca98a20bc 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_drv.h
> +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
> @@ -276,6 +276,7 @@ struct nouveau_drm {
>   	/* modesetting */
>   	struct nvbios vbios;
>   	struct nouveau_display *display;
> +	bool headless;
>   	struct work_struct hpd_work;
>   	spinlock_t hpd_lock;
>   	u32 hpd_pending;
Dave Airlie June 7, 2024, 1:37 a.m. UTC | #2
readding original poster

On Wed, 29 May 2024 at 09:57, Ben Skeggs <bskeggs@nvidia.com> wrote:
>
> On 29/5/24 07:52, Vasily Khoruzhick wrote:
>
> > If the card doesn't have display hardware, hpd_work and hpd_lock are
> > left uninitialized which causes BUG when attempting to schedule hpd_work
> > on runtime PM resume.
>
> Hi,
>
> Good catch, thank you for looking at this.  A couple of initial comments
> below:
>
> Ben.
>
> >
> > Fix it by adding headless flag to DRM and skip any hpd if it's set.
> >
> > Fixes: ae1aadb1eb8d ("nouveau: don't fail driver load if no display hw present.")
> > Link: https://gitlab.freedesktop.org/drm/nouveau/-/issues/337
> > Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com>
> > ---
> >   drivers/gpu/drm/nouveau/dispnv04/disp.c     |  2 +-
> >   drivers/gpu/drm/nouveau/dispnv50/disp.c     |  2 +-
> >   drivers/gpu/drm/nouveau/nouveau_connector.c |  3 +++
> >   drivers/gpu/drm/nouveau/nouveau_display.c   | 11 ++++++++++-
> >   drivers/gpu/drm/nouveau/nouveau_drv.h       |  1 +
> >   5 files changed, 16 insertions(+), 3 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.c b/drivers/gpu/drm/nouveau/dispnv04/disp.c
> > index 13705c5f1497..4b7497a8755c 100644
> > --- a/drivers/gpu/drm/nouveau/dispnv04/disp.c
> > +++ b/drivers/gpu/drm/nouveau/dispnv04/disp.c
> > @@ -68,7 +68,7 @@ nv04_display_fini(struct drm_device *dev, bool runtime, bool suspend)
> >       if (nv_two_heads(dev))
> >               NVWriteCRTC(dev, 1, NV_PCRTC_INTR_EN_0, 0);
> >
> > -     if (!runtime)
> > +     if (!runtime && !drm->headless)
> >               cancel_work_sync(&drm->hpd_work);
> >
> >       if (!suspend)
> > diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> > index 88728a0b2c25..674dc567e179 100644
> > --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
> > +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
> > @@ -2680,7 +2680,7 @@ nv50_display_fini(struct drm_device *dev, bool runtime, bool suspend)
> >                       nv50_mstm_fini(nouveau_encoder(encoder));
> >       }
> >
> > -     if (!runtime)
> > +     if (!runtime && !drm->headless)
> >               cancel_work_sync(&drm->hpd_work);
> >   }
> >
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
> > index 856b3ef5edb8..b315a2ef5b28 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_connector.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
> > @@ -1190,6 +1190,9 @@ nouveau_connector_hpd(struct nouveau_connector *nv_connector, u64 bits)
> >       u32 mask = drm_connector_mask(&nv_connector->base);
> >       unsigned long flags;
> >
> > +     if (drm->headless)
> > +             return;
> > +
>
> You shouldn't need this change, the function can't be called if there's
> no display.
>
>
> >       spin_lock_irqsave(&drm->hpd_lock, flags);
> >       if (!(drm->hpd_pending & mask)) {
> >               nv_connector->hpd_pending |= bits;
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
> > index aed5d5b51b43..1961ef665e97 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_display.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_display.c
> > @@ -450,6 +450,9 @@ nouveau_display_hpd_resume(struct drm_device *dev)
> >   {
> >       struct nouveau_drm *drm = nouveau_drm(dev);
> >
> > +     if (drm->headless)
> > +             return;
> > +
> >       spin_lock_irq(&drm->hpd_lock);
> >       drm->hpd_pending = ~0;
> >       spin_unlock_irq(&drm->hpd_lock);
> > @@ -468,6 +471,11 @@ nouveau_display_hpd_work(struct work_struct *work)
> >       int changed = 0;
> >       struct drm_connector *first_changed_connector = NULL;
> >
> > +     WARN_ON_ONCE(drm->headless);
> > +
> > +     if (drm->headless)
> > +             return;
> > +
>
> Same here.
>
>
> >       pm_runtime_get_sync(dev->dev);
> >
> >       spin_lock_irq(&drm->hpd_lock);
> > @@ -635,7 +643,7 @@ nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime)
> >       }
> >       drm_connector_list_iter_end(&conn_iter);
> >
> > -     if (!runtime)
> > +     if (!runtime && !drm->headless)
> >               cancel_work_sync(&drm->hpd_work);
> >
> >       drm_kms_helper_poll_disable(dev);
> > @@ -729,6 +737,7 @@ nouveau_display_create(struct drm_device *dev)
> >               /* no display hw */
> >               if (ret == -ENODEV) {
> >                       ret = 0;
> > +                     drm->headless = true;
> >                       goto disp_create_err;
> >               }
> >
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
> > index e239c6bf4afa..25fca98a20bc 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_drv.h
> > +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
> > @@ -276,6 +276,7 @@ struct nouveau_drm {
> >       /* modesetting */
> >       struct nvbios vbios;
> >       struct nouveau_display *display;
> > +     bool headless;
> >       struct work_struct hpd_work;
> >       spinlock_t hpd_lock;
> >       u32 hpd_pending;
Vasily Khoruzhick June 7, 2024, 4:52 p.m. UTC | #3
On Thu, Jun 6, 2024 at 6:37 PM Dave Airlie <airlied@gmail.com> wrote:
>
> readding original poster

Thanks, Dave!

Ben, please keep me on CC, since I'm not subscribed to either nouveau
or dri-devel mailing lists.

> On Wed, 29 May 2024 at 09:57, Ben Skeggs <bskeggs@nvidia.com> wrote:

> > > diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
> > > index 856b3ef5edb8..b315a2ef5b28 100644
> > > --- a/drivers/gpu/drm/nouveau/nouveau_connector.c
> > > +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
> > > @@ -1190,6 +1190,9 @@ nouveau_connector_hpd(struct nouveau_connector *nv_connector, u64 bits)
> > >       u32 mask = drm_connector_mask(&nv_connector->base);
> > >       unsigned long flags;
> > >
> > > +     if (drm->headless)
> > > +             return;
> > > +
> >
> > You shouldn't need this change, the function can't be called if there's
> > no display.

You are right. I added it for extra safety in case if the code changes
in future and it somehow gets called, but I don't have a strong
preference, so I can drop it in v2.

> >
> > >       spin_lock_irqsave(&drm->hpd_lock, flags);
> > >       if (!(drm->hpd_pending & mask)) {
> > >               nv_connector->hpd_pending |= bits;
> > > diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
> > > index aed5d5b51b43..1961ef665e97 100644
> > > --- a/drivers/gpu/drm/nouveau/nouveau_display.c
> > > +++ b/drivers/gpu/drm/nouveau/nouveau_display.c
> > > @@ -450,6 +450,9 @@ nouveau_display_hpd_resume(struct drm_device *dev)
> > >   {
> > >       struct nouveau_drm *drm = nouveau_drm(dev);
> > >
> > > +     if (drm->headless)
> > > +             return;
> > > +
> > >       spin_lock_irq(&drm->hpd_lock);
> > >       drm->hpd_pending = ~0;
> > >       spin_unlock_irq(&drm->hpd_lock);
> > > @@ -468,6 +471,11 @@ nouveau_display_hpd_work(struct work_struct *work)
> > >       int changed = 0;
> > >       struct drm_connector *first_changed_connector = NULL;
> > >
> > > +     WARN_ON_ONCE(drm->headless);
> > > +
> > > +     if (drm->headless)
> > > +             return;
> > > +
> >
> > Same here.

Same comment, I'll drop it for v2.

Regards,
Vasily
diff mbox series

Patch

diff --git a/drivers/gpu/drm/nouveau/dispnv04/disp.c b/drivers/gpu/drm/nouveau/dispnv04/disp.c
index 13705c5f1497..4b7497a8755c 100644
--- a/drivers/gpu/drm/nouveau/dispnv04/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv04/disp.c
@@ -68,7 +68,7 @@  nv04_display_fini(struct drm_device *dev, bool runtime, bool suspend)
 	if (nv_two_heads(dev))
 		NVWriteCRTC(dev, 1, NV_PCRTC_INTR_EN_0, 0);
 
-	if (!runtime)
+	if (!runtime && !drm->headless)
 		cancel_work_sync(&drm->hpd_work);
 
 	if (!suspend)
diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c
index 88728a0b2c25..674dc567e179 100644
--- a/drivers/gpu/drm/nouveau/dispnv50/disp.c
+++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c
@@ -2680,7 +2680,7 @@  nv50_display_fini(struct drm_device *dev, bool runtime, bool suspend)
 			nv50_mstm_fini(nouveau_encoder(encoder));
 	}
 
-	if (!runtime)
+	if (!runtime && !drm->headless)
 		cancel_work_sync(&drm->hpd_work);
 }
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c
index 856b3ef5edb8..b315a2ef5b28 100644
--- a/drivers/gpu/drm/nouveau/nouveau_connector.c
+++ b/drivers/gpu/drm/nouveau/nouveau_connector.c
@@ -1190,6 +1190,9 @@  nouveau_connector_hpd(struct nouveau_connector *nv_connector, u64 bits)
 	u32 mask = drm_connector_mask(&nv_connector->base);
 	unsigned long flags;
 
+	if (drm->headless)
+		return;
+
 	spin_lock_irqsave(&drm->hpd_lock, flags);
 	if (!(drm->hpd_pending & mask)) {
 		nv_connector->hpd_pending |= bits;
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index aed5d5b51b43..1961ef665e97 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -450,6 +450,9 @@  nouveau_display_hpd_resume(struct drm_device *dev)
 {
 	struct nouveau_drm *drm = nouveau_drm(dev);
 
+	if (drm->headless)
+		return;
+
 	spin_lock_irq(&drm->hpd_lock);
 	drm->hpd_pending = ~0;
 	spin_unlock_irq(&drm->hpd_lock);
@@ -468,6 +471,11 @@  nouveau_display_hpd_work(struct work_struct *work)
 	int changed = 0;
 	struct drm_connector *first_changed_connector = NULL;
 
+	WARN_ON_ONCE(drm->headless);
+
+	if (drm->headless)
+		return;
+
 	pm_runtime_get_sync(dev->dev);
 
 	spin_lock_irq(&drm->hpd_lock);
@@ -635,7 +643,7 @@  nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime)
 	}
 	drm_connector_list_iter_end(&conn_iter);
 
-	if (!runtime)
+	if (!runtime && !drm->headless)
 		cancel_work_sync(&drm->hpd_work);
 
 	drm_kms_helper_poll_disable(dev);
@@ -729,6 +737,7 @@  nouveau_display_create(struct drm_device *dev)
 		/* no display hw */
 		if (ret == -ENODEV) {
 			ret = 0;
+			drm->headless = true;
 			goto disp_create_err;
 		}
 
diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h
index e239c6bf4afa..25fca98a20bc 100644
--- a/drivers/gpu/drm/nouveau/nouveau_drv.h
+++ b/drivers/gpu/drm/nouveau/nouveau_drv.h
@@ -276,6 +276,7 @@  struct nouveau_drm {
 	/* modesetting */
 	struct nvbios vbios;
 	struct nouveau_display *display;
+	bool headless;
 	struct work_struct hpd_work;
 	spinlock_t hpd_lock;
 	u32 hpd_pending;