From ebcb53c524794b9864438e1637ff215545667c40 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 5 Jul 2021 15:47:43 +0200 Subject: [PATCH 1/3] drm/vc4: hdmi: Drop devm interrupt handler for CEC interrupts The CEC interrupt handlers are registered through the devm_request_threaded_irq function. However, while free_irq is indeed called properly when the device is unbound or bind fails, it's called after unbind or bind is done. In our particular case, it means that on failure it creates a window where our interrupt handler can be called, but we're freeing every resource (CEC adapter, DRM objects, etc.) it might need. In order to address this, let's switch to the non-devm variant to control better when the handler will be unregistered and allow us to make it safe. Fixes: 15b4511a4af6 ("drm/vc4: add HDMI CEC support") Signed-off-by: Maxime Ripard --- drivers/gpu/drm/vc4/vc4_hdmi.c | 49 +++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index b26710ec4cec38..1ec5f1cbe8801f 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1922,38 +1922,46 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) vc4_hdmi_cec_update_clk_div(vc4_hdmi); if (vc4_hdmi->variant->external_irq_controller) { - ret = devm_request_threaded_irq(&pdev->dev, - platform_get_irq_byname(pdev, "cec-rx"), - vc4_cec_irq_handler_rx_bare, - vc4_cec_irq_handler_rx_thread, 0, - "vc4 hdmi cec rx", vc4_hdmi); + ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-rx"), + vc4_cec_irq_handler_rx_bare, + vc4_cec_irq_handler_rx_thread, 0, + "vc4 hdmi cec rx", vc4_hdmi); if (ret) goto err_delete_cec_adap; - ret = devm_request_threaded_irq(&pdev->dev, - platform_get_irq_byname(pdev, "cec-tx"), - vc4_cec_irq_handler_tx_bare, - vc4_cec_irq_handler_tx_thread, 0, - "vc4 hdmi cec tx", vc4_hdmi); + ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-tx"), + vc4_cec_irq_handler_tx_bare, + vc4_cec_irq_handler_tx_thread, 0, + "vc4 hdmi cec tx", vc4_hdmi); if (ret) - goto err_delete_cec_adap; + goto err_remove_cec_rx_handler; } else { HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); - ret = devm_request_threaded_irq(&pdev->dev, platform_get_irq(pdev, 0), - vc4_cec_irq_handler, - vc4_cec_irq_handler_thread, 0, - "vc4 hdmi cec", vc4_hdmi); + ret = request_threaded_irq(platform_get_irq(pdev, 0), + vc4_cec_irq_handler, + vc4_cec_irq_handler_thread, 0, + "vc4 hdmi cec", vc4_hdmi); if (ret) goto err_delete_cec_adap; } ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev); if (ret < 0) - goto err_delete_cec_adap; + goto err_remove_handlers; return 0; +err_remove_handlers: + if (vc4_hdmi->variant->external_irq_controller) + free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi); + else + free_irq(platform_get_irq(pdev, 0), vc4_hdmi); + +err_remove_cec_rx_handler: + if (vc4_hdmi->variant->external_irq_controller) + free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi); + err_delete_cec_adap: cec_delete_adapter(vc4_hdmi->cec_adap); @@ -1962,6 +1970,15 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) { + struct platform_device *pdev = vc4_hdmi->pdev; + + if (vc4_hdmi->variant->external_irq_controller) { + free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi); + free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi); + } else { + free_irq(platform_get_irq(pdev, 0), vc4_hdmi); + } + cec_unregister_adapter(vc4_hdmi->cec_adap); } #else From f4a532bba3387408d50208f43411cf4963b192e6 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 5 Jul 2021 17:31:48 +0200 Subject: [PATCH 2/3] drm/vc4: hdmi: Drop devm interrupt handler for hotplug interrupts The hotplugs interrupt handlers are registered through the devm_request_threaded_irq function. However, while free_irq is indeed called properly when the device is unbound or bind fails, it's called after unbind or bind is done. In our particular case, it means that on failure it creates a window where our interrupt handler can be called, but we're freeing every resource (CEC adapter, DRM objects, etc.) it might need. In order to address this, let's switch to the non-devm variant to control better when the handler will be unregistered and allow us to make it safe. Fixes: f4790083c7c2 ("drm/vc4: hdmi: Rely on interrupts to handle hotplug") Signed-off-by: Maxime Ripard --- drivers/gpu/drm/vc4/vc4_hdmi.c | 41 +++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 1ec5f1cbe8801f..009404b2697eb2 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1630,26 +1630,28 @@ static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv) static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi) { struct platform_device *pdev = vc4_hdmi->pdev; - struct device *dev = &pdev->dev; struct drm_connector *connector = &vc4_hdmi->connector; int ret; if (vc4_hdmi->variant->external_irq_controller) { - ret = devm_request_threaded_irq(dev, - platform_get_irq_byname(pdev, "hpd-connected"), - NULL, - vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, - "vc4 hdmi hpd connected", vc4_hdmi); + unsigned int hpd_con = platform_get_irq_byname(pdev, "hpd-connected"); + unsigned int hpd_rm = platform_get_irq_byname(pdev, "hpd-removed"); + + ret = request_threaded_irq(hpd_con, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd connected", vc4_hdmi); if (ret) return ret; - ret = devm_request_threaded_irq(dev, - platform_get_irq_byname(pdev, "hpd-removed"), - NULL, - vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, - "vc4 hdmi hpd disconnected", vc4_hdmi); - if (ret) + ret = request_threaded_irq(hpd_rm, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd disconnected", vc4_hdmi); + if (ret) { + free_irq(hpd_con, vc4_hdmi); return ret; + } connector->polled = DRM_CONNECTOR_POLL_HPD; } @@ -1657,6 +1659,16 @@ static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi) return 0; } +static void vc4_hdmi_hotplug_exit(struct vc4_hdmi *vc4_hdmi) +{ + struct platform_device *pdev = vc4_hdmi->pdev; + + if (vc4_hdmi->variant->external_irq_controller) { + free_irq(platform_get_irq_byname(pdev, "hpd-connected"), vc4_hdmi); + free_irq(platform_get_irq_byname(pdev, "hpd-removed"), vc4_hdmi); + } +} + #ifdef CONFIG_DRM_VC4_HDMI_CEC static irqreturn_t vc4_cec_irq_handler_rx_thread(int irq, void *priv) { @@ -2336,7 +2348,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) ret = vc4_hdmi_cec_init(vc4_hdmi); if (ret) - goto err_destroy_conn; + goto err_free_hotplug; ret = vc4_hdmi_audio_init(vc4_hdmi); if (ret) @@ -2352,6 +2364,8 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) err_free_cec: vc4_hdmi_cec_exit(vc4_hdmi); +err_free_hotplug: + vc4_hdmi_hotplug_exit(vc4_hdmi); err_destroy_conn: vc4_hdmi_connector_destroy(&vc4_hdmi->connector); err_destroy_encoder: @@ -2394,6 +2408,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, kfree(vc4_hdmi->hd_regset.regs); vc4_hdmi_cec_exit(vc4_hdmi); + vc4_hdmi_hotplug_exit(vc4_hdmi); vc4_hdmi_connector_destroy(&vc4_hdmi->connector); drm_encoder_cleanup(&vc4_hdmi->encoder.base.base); From eccc70aa014bacaac0e05cbd219f440d394748b7 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 5 Jul 2021 16:15:56 +0200 Subject: [PATCH 3/3] drm/vc4: hdmi: Only call into DRM framework if registered Our hotplug handler will currently call the drm_kms_helper_hotplug_event every time a hotplug interrupt is called. However, since the device is registered after all the drivers have finished their bind callback, we have a window between when we install our interrupt handler and when drm_dev_register() is eventually called where our handler can run and call drm_kms_helper_hotplug_event but the device hasn't been registered yet, causing a null pointer dereference. Fix this by making sure we only call drm_kms_helper_hotplug_event if our device has been properly registered. Fixes: f4790083c7c2 ("drm/vc4: hdmi: Rely on interrupts to handle hotplug") Signed-off-by: Maxime Ripard --- drivers/gpu/drm/vc4/vc4_hdmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 009404b2697eb2..c5dc3c61332b0b 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1621,7 +1621,7 @@ static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv) struct vc4_hdmi *vc4_hdmi = priv; struct drm_device *dev = vc4_hdmi->connector.dev; - if (dev) + if (dev && dev->registered) drm_kms_helper_hotplug_event(dev); return IRQ_HANDLED;