2021-05-25 10:16:32

by Maxime Ripard

[permalink] [raw]
Subject: [PATCH 0/3] drm/vc4: Fix for the HDMI detect power state

Hi,



This fixes an issue found during a rework on the RPi3 where we would

end up with the detect callback of the HDMI connector called while the

device would be disabled.



This unfortunately results in a complete CPU hang on the RaspberryPi.



The documentation doesn't really provide any expectation on the power

state for various operations that could be performed while the device is

off, so the first patch makes that clear. The next two patches make sure

the device is sufficiently powered for detect to run without any issue.



Let me know what you think,

Maxime



Maxime Ripard (3):

drm: Mention the power state requirement on side-channel operations

drm/vc4: hdmi: Move the HSM clock enable to runtime_pm

drm/vc4: hdmi: Make sure the controller is powered in detect



drivers/gpu/drm/vc4/vc4_hdmi.c | 45 ++++++++++++++++++++++++++--------

include/drm/drm_connector.h | 5 ++++

include/drm/drm_dp_helper.h | 4 +++

include/drm/drm_mipi_dsi.h | 5 ++++

4 files changed, 49 insertions(+), 10 deletions(-)



--

2.31.1




2021-05-25 10:31:31

by Maxime Ripard

[permalink] [raw]
Subject: [PATCH 1/3] drm: Mention the power state requirement on side-channel operations

The drm_connector detect, drm_dp_aux transfer and mipi_dsi_host
operations typically require to access their underlying device to
perform what is expected of them.

However, there's no guarantee on the fact that the device has been
enabled through atomic_enable or similar that will usually power the
device. The access to an unpowered device is then an undefined behaviour
ranging from the access being ignored to a hard CPU hang.

Let's document that expectation to avoid as much as possible those
consequences.

Signed-off-by: Maxime Ripard <[email protected]>
---
include/drm/drm_connector.h | 5 +++++
include/drm/drm_dp_helper.h | 4 ++++
include/drm/drm_mipi_dsi.h | 5 +++++
3 files changed, 14 insertions(+)

diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 714d1a01c065..0a1d9a0fcbb2 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -848,6 +848,11 @@ struct drm_connector_funcs {
* locks to avoid races with concurrent modeset changes need to use
* &drm_connector_helper_funcs.detect_ctx instead.
*
+ * Also note that this callback can be called no matter the
+ * state the connector is in. Drivers that need the underlying
+ * device to be powered to perform the detection will first need
+ * to make sure it's been properly enabled.
+ *
* RETURNS:
*
* drm_connector_status indicating the connector's status.
diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index 06681bf46d81..4fcb027c8821 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -1884,6 +1884,10 @@ struct drm_dp_aux_cec {
* Note that the aux helper code assumes that the @transfer() function only
* modifies the reply field of the &drm_dp_aux_msg structure. The retry logic
* and i2c helpers assume this is the case.
+ *
+ * Also note that @transfer() can be called no matter the state @dev is
+ * in. Drivers that need that device to be powered to perform this
+ * operation will first need to make sure it's been properly enabled.
*/
struct drm_dp_aux {
const char *name;
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index 360e6377e84b..849d3029e303 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -80,6 +80,11 @@ int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
* Note that typically DSI packet transmission is atomic, so the .transfer()
* function will seldomly return anything other than the number of bytes
* contained in the transmit buffer on success.
+ *
+ * Also note that those callbacks can be called no matter the state the
+ * host is in. Drivers that need the underlying device to be powered to
+ * perform these operations will first need to make sure it's been
+ * properly enabled.
*/
struct mipi_dsi_host_ops {
int (*attach)(struct mipi_dsi_host *host,
--
2.31.1

2021-05-25 10:32:05

by Maxime Ripard

[permalink] [raw]
Subject: [PATCH 2/3] drm/vc4: hdmi: Move the HSM clock enable to runtime_pm

In order to access the HDMI controller, we need to make sure the HSM
clock is enabled. If we were to access it with the clock disabled, the
CPU would completely hang, resulting in an hard crash.

Since we have different code path that would require it, let's move that
clock enable / disable to runtime_pm that will take care of the
reference counting for us.

Fixes: 4f6e3d66ac52 ("drm/vc4: Add runtime PM support to the HDMI encoder driver")
Signed-off-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/vc4/vc4_hdmi.c | 41 +++++++++++++++++++++++++---------
1 file changed, 31 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
index f9de8632a28b..867009a471e1 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
@@ -632,7 +632,6 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);

clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock);
- clk_disable_unprepare(vc4_hdmi->hsm_clock);
clk_disable_unprepare(vc4_hdmi->pixel_clock);

ret = pm_runtime_put(&vc4_hdmi->pdev->dev);
@@ -943,13 +942,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
return;
}

- ret = clk_prepare_enable(vc4_hdmi->hsm_clock);
- if (ret) {
- DRM_ERROR("Failed to turn on HSM clock: %d\n", ret);
- clk_disable_unprepare(vc4_hdmi->pixel_clock);
- return;
- }
-
vc4_hdmi_cec_update_clk_div(vc4_hdmi);

if (pixel_rate > 297000000)
@@ -962,7 +954,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, bvb_rate);
if (ret) {
DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret);
- clk_disable_unprepare(vc4_hdmi->hsm_clock);
clk_disable_unprepare(vc4_hdmi->pixel_clock);
return;
}
@@ -970,7 +961,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
ret = clk_prepare_enable(vc4_hdmi->pixel_bvb_clock);
if (ret) {
DRM_ERROR("Failed to turn on pixel bvb clock: %d\n", ret);
- clk_disable_unprepare(vc4_hdmi->hsm_clock);
clk_disable_unprepare(vc4_hdmi->pixel_clock);
return;
}
@@ -2097,6 +2087,29 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi)
return 0;
}

+#ifdef CONFIG_PM
+static int vc4_hdmi_runtime_suspend(struct device *dev)
+{
+ struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(vc4_hdmi->hsm_clock);
+
+ return 0;
+}
+
+static int vc4_hdmi_runtime_resume(struct device *dev)
+{
+ struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(vc4_hdmi->hsm_clock);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+#endif
+
static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
{
const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev);
@@ -2353,11 +2366,19 @@ static const struct of_device_id vc4_hdmi_dt_match[] = {
{}
};

+
+static const struct dev_pm_ops vc4_hdmi_pm_ops = {
+ SET_RUNTIME_PM_OPS(vc4_hdmi_runtime_suspend,
+ vc4_hdmi_runtime_resume,
+ NULL)
+};
+
struct platform_driver vc4_hdmi_driver = {
.probe = vc4_hdmi_dev_probe,
.remove = vc4_hdmi_dev_remove,
.driver = {
.name = "vc4_hdmi",
.of_match_table = vc4_hdmi_dt_match,
+ .pm = &vc4_hdmi_pm_ops,
},
};
--
2.31.1

2021-06-16 10:52:58

by Dave Stevenson

[permalink] [raw]
Subject: Re: [PATCH 2/3] drm/vc4: hdmi: Move the HSM clock enable to runtime_pm

Hi Maxime

On Tue, 25 May 2021 at 10:11, Maxime Ripard <[email protected]> wrote:
>
> In order to access the HDMI controller, we need to make sure the HSM
> clock is enabled. If we were to access it with the clock disabled, the
> CPU would completely hang, resulting in an hard crash.
>
> Since we have different code path that would require it, let's move that
> clock enable / disable to runtime_pm that will take care of the
> reference counting for us.

This does change the order of clk_set_rate vs clk_prepare_enable, so
the clock is already running during
vc4_hdmi_encoder_pre_crtc_configure when the pixel rate is known.
However the crtc and HDMI blocks won't be actively passing pixels at
that point, so I don't see an issue with changing the rate. The clock
manager block will sort out the rate change happily.

Reviewed-by: Dave Stevenson <[email protected]>

> Fixes: 4f6e3d66ac52 ("drm/vc4: Add runtime PM support to the HDMI encoder driver")
> Signed-off-by: Maxime Ripard <[email protected]>
> ---
> drivers/gpu/drm/vc4/vc4_hdmi.c | 41 +++++++++++++++++++++++++---------
> 1 file changed, 31 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
> index f9de8632a28b..867009a471e1 100644
> --- a/drivers/gpu/drm/vc4/vc4_hdmi.c
> +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
> @@ -632,7 +632,6 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
> HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);
>
> clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock);
> - clk_disable_unprepare(vc4_hdmi->hsm_clock);
> clk_disable_unprepare(vc4_hdmi->pixel_clock);
>
> ret = pm_runtime_put(&vc4_hdmi->pdev->dev);
> @@ -943,13 +942,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
> return;
> }
>
> - ret = clk_prepare_enable(vc4_hdmi->hsm_clock);
> - if (ret) {
> - DRM_ERROR("Failed to turn on HSM clock: %d\n", ret);
> - clk_disable_unprepare(vc4_hdmi->pixel_clock);
> - return;
> - }
> -
> vc4_hdmi_cec_update_clk_div(vc4_hdmi);
>
> if (pixel_rate > 297000000)
> @@ -962,7 +954,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
> ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, bvb_rate);
> if (ret) {
> DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret);
> - clk_disable_unprepare(vc4_hdmi->hsm_clock);
> clk_disable_unprepare(vc4_hdmi->pixel_clock);
> return;
> }
> @@ -970,7 +961,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
> ret = clk_prepare_enable(vc4_hdmi->pixel_bvb_clock);
> if (ret) {
> DRM_ERROR("Failed to turn on pixel bvb clock: %d\n", ret);
> - clk_disable_unprepare(vc4_hdmi->hsm_clock);
> clk_disable_unprepare(vc4_hdmi->pixel_clock);
> return;
> }
> @@ -2097,6 +2087,29 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi)
> return 0;
> }
>
> +#ifdef CONFIG_PM
> +static int vc4_hdmi_runtime_suspend(struct device *dev)
> +{
> + struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
> +
> + clk_disable_unprepare(vc4_hdmi->hsm_clock);
> +
> + return 0;
> +}
> +
> +static int vc4_hdmi_runtime_resume(struct device *dev)
> +{
> + struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = clk_prepare_enable(vc4_hdmi->hsm_clock);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +#endif
> +
> static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
> {
> const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev);
> @@ -2353,11 +2366,19 @@ static const struct of_device_id vc4_hdmi_dt_match[] = {
> {}
> };
>
> +
> +static const struct dev_pm_ops vc4_hdmi_pm_ops = {
> + SET_RUNTIME_PM_OPS(vc4_hdmi_runtime_suspend,
> + vc4_hdmi_runtime_resume,
> + NULL)
> +};
> +
> struct platform_driver vc4_hdmi_driver = {
> .probe = vc4_hdmi_dev_probe,
> .remove = vc4_hdmi_dev_remove,
> .driver = {
> .name = "vc4_hdmi",
> .of_match_table = vc4_hdmi_dt_match,
> + .pm = &vc4_hdmi_pm_ops,
> },
> };
> --
> 2.31.1
>