Hi,
Here is a series that address multiple issues when trying to unbind/rebind vc4-related devices to their drivers.
Most of these issues involve either use-after-free, improper resource liberation or similar.
It has been tested on the Pi3 and Pi4, with X and glxgears running and KASAN enabled to properly validate our memory accesses.
Pi3 isn't functional after a rebind though, with vblank timeouts occuring. I'm not quite sure why at this point, but at least the kernel doesn't completely crash now.
Let me know what you think, Maxime
Maxime Ripard (64): drm/mipi-dsi: Detach devices when removing the host drm/crtc: Introduce drmm_crtc_init_with_planes drm/encoder: Introduce drmm_encoder_init drm/connector: Reorder headers drm/connector: Mention the cleanup after drm_connector_init drm/connector: Introduce drmm_connector_init drm/connector: Introduce drmm_connector_init_with_ddc drm/writeback: Introduce drmm_writeback_connector_init drm/simple: Introduce drmm_simple_encoder_init drm/bridge: panel: Introduce drmm_panel_bridge_add drm/bridge: panel: Introduce drmm_of_get_bridge drm/vc4: Call component_unbind_all() drm/vc4: hvs: Protect device resources after removal drm/vc4: hvs: Remove planes currently allocated before taking down drm/vc4: plane: Take possible_crtcs as an argument drm/vc4: plane: Switch to drmm_universal_plane_alloc() drm/vc4: crtc: Move debugfs_name to crtc_data drm/vc4: crtc: Switch to drmm_kzalloc drm/vc4: crtc: Switch to DRM-managed CRTC initialization drm/vc4: dpi: Remove vc4_dev dpi pointer drm/vc4: dpi: Embed DRM structures into the private structure drm/vc4: dpi: Switch to drmm_kzalloc drm/vc4: dpi: Return an error if we can't enable our clock drm/vc4: dpi: Remove unnecessary drm_of_panel_bridge_remove call drm/vc4: dpi: Add action to disable the clock drm/vc4: dpi: Switch to DRM-managed encoder initialization drm/vc4: dpi: Switch to drmm_of_get_bridge drm/vc4: dpi: Protect device resources drm/vc4: dsi: Embed DRM structures into the private structure drm/vc4: dsi: Switch to DRM-managed encoder initialization drm/vc4: dsi: Switch to drmm_of_get_bridge drm/vc4: dsi: Fix the driver structure lifetime drm/vc4: dsi: Switch to devm_pm_runtime_enable drm/vc4: hdmi: Switch to drmm_kzalloc drm/vc4: hdmi: Switch to DRM-managed encoder initialization drm/vc4: hdmi: Switch to DRM-managed connector initialization drm/vc4: hdmi: Switch to device-managed ALSA initialization drm/vc4: hdmi: Switch to device-managed CEC initialization drm/vc4: hdmi: Use a device-managed action for DDC drm/vc4: hdmi: Switch to DRM-managed kfree to build regsets drm/vc4: hdmi: Use devm to register hotplug interrupts drm/vc4: hdmi: Move audio structure offset checks drm/vc4: hdmi: Protect device resources after removal drm/vc4: hdmi: Switch to devm_pm_runtime_enable drm/vc4: txp: Remove vc4_dev txp pointer drm/vc4: txp: Remove duplicate regset drm/vc4: txp: Switch to drmm_kzalloc drm/vc4: txp: Switch to DRM-managed writeback initialization drm/vc4: txp: Protect device resources drm/vc4: vec: Remove vc4_dev vec pointer drm/vc4: vec: Embed DRM structures into the private structure drm/vc4: vec: Switch to drmm_kzalloc drm/vc4: vec: Switch to DRM-managed encoder initialization drm/vc4: vec: Switch to DRM-managed connector initialization drm/vc4: vec: Protect device resources after removal drm/vc4: vec: Switch to devm_pm_runtime_enable drm/vc4: debugfs: Protect device resources drm/vc4: debugfs: Return an error on failure drm/vc4: debugfs: Simplify debugfs registration drm/vc4: Switch to drmm_mutex_init drm/vc4: perfmon: Add missing mutex_destroy drm/vc4: v3d: Stop disabling interrupts drm/vc4: v3d: Rework the runtime_pm setup drm/vc4: v3d: Switch to devm_pm_runtime_enable
drivers/gpu/drm/bridge/panel.c | 74 +++ drivers/gpu/drm/drm_connector.c | 186 +++++-- drivers/gpu/drm/drm_crtc.c | 70 ++- drivers/gpu/drm/drm_encoder.c | 48 +- drivers/gpu/drm/drm_mipi_dsi.c | 1 + drivers/gpu/drm/drm_simple_kms_helper.c | 46 +- drivers/gpu/drm/drm_writeback.c | 136 +++-- drivers/gpu/drm/vc4/vc4_bo.c | 33 +- drivers/gpu/drm/vc4/vc4_crtc.c | 69 ++- drivers/gpu/drm/vc4/vc4_debugfs.c | 71 ++- drivers/gpu/drm/vc4/vc4_dpi.c | 131 ++--- drivers/gpu/drm/vc4/vc4_drv.c | 18 +- drivers/gpu/drm/vc4/vc4_drv.h | 47 +- drivers/gpu/drm/vc4/vc4_dsi.c | 120 +++-- drivers/gpu/drm/vc4/vc4_gem.c | 10 +- drivers/gpu/drm/vc4/vc4_hdmi.c | 637 +++++++++++++++++------- drivers/gpu/drm/vc4/vc4_hdmi.h | 3 +- drivers/gpu/drm/vc4/vc4_hvs.c | 145 +++++- drivers/gpu/drm/vc4/vc4_irq.c | 2 +- drivers/gpu/drm/vc4/vc4_perfmon.c | 1 + drivers/gpu/drm/vc4/vc4_plane.c | 36 +- drivers/gpu/drm/vc4/vc4_txp.c | 69 +-- drivers/gpu/drm/vc4/vc4_v3d.c | 65 ++- drivers/gpu/drm/vc4/vc4_vec.c | 216 ++++---- include/drm/drm_bridge.h | 4 + include/drm/drm_connector.h | 9 + include/drm/drm_crtc.h | 6 + include/drm/drm_encoder.h | 5 + include/drm/drm_simple_kms_helper.h | 3 + include/drm/drm_writeback.h | 5 + 30 files changed, 1621 insertions(+), 645 deletions(-)
Whenever the MIPI-DSI host is unregistered, the code of mipi_dsi_host_unregister() loops over every device currently found on that bus and will unregister it.
However, it doesn't detach it from the bus first, which leads to all kind of resource leaks if the host wants to perform some clean up whenever a device is detached.
Fixes: 068a00233969 ("drm: Add MIPI DSI bus support") Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_mipi_dsi.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index c40bde96cfdf..c317ee9fa445 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -346,6 +346,7 @@ static int mipi_dsi_remove_device_fn(struct device *dev, void *priv) { struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);
+ mipi_dsi_detach(dsi); mipi_dsi_device_unregister(dsi);
return 0;
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_crtc.c | 70 ++++++++++++++++++++++++++++++++++++-- include/drm/drm_crtc.h | 6 ++++ 2 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 26a77a735905..fd986a7dd4ad 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -341,9 +341,10 @@ static int __drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc * * The @primary and @cursor planes are only relevant for legacy uAPI, see * &drm_crtc.primary and &drm_crtc.cursor. * - * Note: consider using drmm_crtc_alloc_with_planes() instead of - * drm_crtc_init_with_planes() to let the DRM managed resource infrastructure - * take care of cleanup and deallocation. + * Note: consider using drmm_crtc_alloc_with_planes() or + * drmm_crtc_init_with_planes() instead of drm_crtc_init_with_planes() + * to let the DRM managed resource infrastructure take care of cleanup + * and deallocation. * * Returns: * Zero on success, error code on failure. @@ -368,6 +369,69 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc, } EXPORT_SYMBOL(drm_crtc_init_with_planes);
+static void drmm_crtc_init_with_planes_cleanup(struct drm_device *dev, + void *ptr) +{ + struct drm_crtc *crtc = ptr; + + drm_crtc_cleanup(crtc); +} + +/** + * drmm_crtc_init_with_planes - Initialise a new CRTC object with + * specified primary and cursor planes. + * @dev: DRM device + * @crtc: CRTC object to init + * @primary: Primary plane for CRTC + * @cursor: Cursor plane for CRTC + * @funcs: callbacks for the new CRTC + * @name: printf style format string for the CRTC name, or NULL for default name + * + * Inits a new object created as base part of a driver crtc object. Drivers + * should use this function instead of drm_crtc_init(), which is only provided + * for backwards compatibility with drivers which do not yet support universal + * planes). For really simple hardware which has only 1 plane look at + * drm_simple_display_pipe_init() instead. + * + * Cleanup is automatically handled through registering + * drmm_crtc_cleanup() with drmm_add_action(). The crtc structure should + * be allocated with drmm_kzalloc(). + * + * The @drm_crtc_funcs.destroy hook must be NULL. + * + * The @primary and @cursor planes are only relevant for legacy uAPI, see + * &drm_crtc.primary and &drm_crtc.cursor. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + const struct drm_crtc_funcs *funcs, + const char *name, ...) +{ + va_list ap; + int ret; + + WARN_ON(funcs && funcs->destroy); + + va_start(ap, name); + ret = __drm_crtc_init_with_planes(dev, crtc, primary, cursor, funcs, + name, ap); + va_end(ap); + if (ret) + return ret; + + ret = drmm_add_action_or_reset(dev, drmm_crtc_init_with_planes_cleanup, + crtc); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drmm_crtc_init_with_planes); + static void drmm_crtc_alloc_with_planes_cleanup(struct drm_device *dev, void *ptr) { diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index a70baea0636c..2babd5cffbf3 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -1229,6 +1229,12 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_plane *cursor, const struct drm_crtc_funcs *funcs, const char *name, ...); +int drmm_crtc_init_with_planes(struct drm_device *dev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + const struct drm_crtc_funcs *funcs, + const char *name, ...); void drm_crtc_cleanup(struct drm_crtc *crtc);
__printf(7, 8)
Hi Maxime
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
It seems only useful if you need it strictly ordered with drmm_kzalloc()?
Best regards Thomas
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_crtc.c | 70 ++++++++++++++++++++++++++++++++++++-- include/drm/drm_crtc.h | 6 ++++ 2 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index 26a77a735905..fd986a7dd4ad 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -341,9 +341,10 @@ static int __drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *
- The @primary and @cursor planes are only relevant for legacy uAPI, see
- &drm_crtc.primary and &drm_crtc.cursor.
- Note: consider using drmm_crtc_alloc_with_planes() instead of
- drm_crtc_init_with_planes() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
- Note: consider using drmm_crtc_alloc_with_planes() or
- drmm_crtc_init_with_planes() instead of drm_crtc_init_with_planes()
- to let the DRM managed resource infrastructure take care of cleanup
- and deallocation.
- Returns:
- Zero on success, error code on failure.
@@ -368,6 +369,69 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc, } EXPORT_SYMBOL(drm_crtc_init_with_planes);
+static void drmm_crtc_init_with_planes_cleanup(struct drm_device *dev,
void *ptr)
+{
- struct drm_crtc *crtc = ptr;
- drm_crtc_cleanup(crtc);
+}
+/**
- drmm_crtc_init_with_planes - Initialise a new CRTC object with
- specified primary and cursor planes.
- @dev: DRM device
- @crtc: CRTC object to init
- @primary: Primary plane for CRTC
- @cursor: Cursor plane for CRTC
- @funcs: callbacks for the new CRTC
- @name: printf style format string for the CRTC name, or NULL for default name
- Inits a new object created as base part of a driver crtc object. Drivers
- should use this function instead of drm_crtc_init(), which is only provided
- for backwards compatibility with drivers which do not yet support universal
- planes). For really simple hardware which has only 1 plane look at
- drm_simple_display_pipe_init() instead.
- Cleanup is automatically handled through registering
- drmm_crtc_cleanup() with drmm_add_action(). The crtc structure should
- be allocated with drmm_kzalloc().
- The @drm_crtc_funcs.destroy hook must be NULL.
- The @primary and @cursor planes are only relevant for legacy uAPI, see
- &drm_crtc.primary and &drm_crtc.cursor.
- Returns:
- Zero on success, error code on failure.
- */
+int drmm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_plane *primary,
struct drm_plane *cursor,
const struct drm_crtc_funcs *funcs,
const char *name, ...)
+{
- va_list ap;
- int ret;
- WARN_ON(funcs && funcs->destroy);
- va_start(ap, name);
- ret = __drm_crtc_init_with_planes(dev, crtc, primary, cursor, funcs,
name, ap);
- va_end(ap);
- if (ret)
return ret;
- ret = drmm_add_action_or_reset(dev, drmm_crtc_init_with_planes_cleanup,
crtc);
- if (ret)
return ret;
- return 0;
+} +EXPORT_SYMBOL(drmm_crtc_init_with_planes);
- static void drmm_crtc_alloc_with_planes_cleanup(struct drm_device *dev, void *ptr) {
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index a70baea0636c..2babd5cffbf3 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -1229,6 +1229,12 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_plane *cursor, const struct drm_crtc_funcs *funcs, const char *name, ...); +int drmm_crtc_init_with_planes(struct drm_device *dev,
struct drm_crtc *crtc,
struct drm_plane *primary,
struct drm_plane *cursor,
const struct drm_crtc_funcs *funcs,
const char *name, ...);
void drm_crtc_cleanup(struct drm_crtc *crtc);
__printf(7, 8)
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Maxime
Hi
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
Best regards Thomas
Maxime
On Tue, Jun 14, 2022 at 10:29:20AM +0200, Thomas Zimmermann wrote:
Hi
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
I'm a bit confused. drm_crtc_init_with_planes() at the moment has:
* Note: consider using drmm_crtc_alloc_with_planes() instead of * drm_crtc_init_with_planes() to let the DRM managed resource infrastructure * take care of cleanup and deallocation.
Just like drm_encoder_init(), drm_simple_encoder_init() and drm_universal_plane_init(), so all the functions we have a drmm_* helper for.
And we have a TODO-list item that heavily hints at using them: https://dri.freedesktop.org/docs/drm/gpu/todo.html#object-lifetime-fixes
So it looks like we're already well on the deprecation path?
Maxime
Hi Maxime
Am 14.06.22 um 11:04 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 10:29:20AM +0200, Thomas Zimmermann wrote:
Hi
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
I'm a bit confused. drm_crtc_init_with_planes() at the moment has:
- Note: consider using drmm_crtc_alloc_with_planes() instead of
- drm_crtc_init_with_planes() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
Just like drm_encoder_init(), drm_simple_encoder_init() and drm_universal_plane_init(), so all the functions we have a drmm_* helper for.
And we have a TODO-list item that heavily hints at using them: https://dri.freedesktop.org/docs/drm/gpu/todo.html#object-lifetime-fixes
So it looks like we're already well on the deprecation path?
AFAIU that TODO item is about replacing devm_kzalloc() with drmm_kzalloc(). It's not about the plain init functions, such as drm_crtc_init_with_planes() or drm_universal_plane_init(). Many simple drivers allocate their modesetting pipeline's components first and then build the pipeline with the drm_ functions. I don't think we can take that away from them.
The concern I have is that we're adding lots of helpers for all kind of scenarios and end up with a lot of duplication (and fragmentation among drivers). For example, drmm_crtc_alloc_with_planes() really isn't much used by anything. [1] Same for drmm_universal_plane_alloc(). [2]
Instead of adding new helpers, it would be better to build upon drmm_crtc_alloc_with_planes(), drmm_univeral_plane_alloc(), etc.
For example, a good starting point would be vc4_plane_init(). It could alloc with drmm_univeral_plane_alloc(), which would replace devm_kzalloc() [3] and drm_univeral_plane_alloc() [4] in one step. From what I understand, that's what your patchset wants to do. But it looks like you're effectively open-coding drmm_universl_plane_alloc().
With vc4_plane_init() correctly managed, the next candidate could be vc4_crtc_init(). You probably want to pull vc4_plane_init() [5] into callers. to get it out of the way. If you move calls to devm_kzalloc() [6] and drm_crtc_init_with_planes() [7] closer together, you can replace them with drmm_crtc_alloc_with_planes().
Best regards Thomas
[1] https://elixir.bootlin.com/linux/latest/C/ident/drmm_crtc_alloc_with_planes [2] https://elixir.bootlin.com/linux/latest/A/ident/drmm_universal_plane_alloc [3] https://elixir.bootlin.com/linux/v5.18.3/source/drivers/gpu/drm/vc4/vc4_plan... [4] https://elixir.bootlin.com/linux/v5.18.3/source/drivers/gpu/drm/vc4/vc4_plan... [5] https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/vc4/vc4_crtc.... [6] https://elixir.bootlin.com/linux/v5.18.3/source/drivers/gpu/drm/vc4/vc4_crtc... [7] https://elixir.bootlin.com/linux/v5.18.3/source/drivers/gpu/drm/vc4/vc4_crtc...
Maxime
On Tue, Jun 14, 2022 at 01:47:28PM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 11:04 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 10:29:20AM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register a CRTC is drmm_crtc_alloc_with_planes(), which will allocate the underlying structure and initialisation the CRTC.
However, we might want to separate the structure creation and the CRTC initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise a CRTC that would be passed as an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
I'm a bit confused. drm_crtc_init_with_planes() at the moment has:
- Note: consider using drmm_crtc_alloc_with_planes() instead of
- drm_crtc_init_with_planes() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
Just like drm_encoder_init(), drm_simple_encoder_init() and drm_universal_plane_init(), so all the functions we have a drmm_* helper for.
And we have a TODO-list item that heavily hints at using them: https://dri.freedesktop.org/docs/drm/gpu/todo.html#object-lifetime-fixes
So it looks like we're already well on the deprecation path?
AFAIU that TODO item is about replacing devm_kzalloc() with drmm_kzalloc(). It's not about the plain init functions, such as drm_crtc_init_with_planes() or drm_universal_plane_init(). Many simple drivers allocate their modesetting pipeline's components first and then build the pipeline with the drm_ functions. I don't think we can take that away from them.
Sure, that's exactly what those first patches are about? It allows to use a DRM managed initialization without disrupting the drivers structure too much?
The concern I have is that we're adding lots of helpers for all kind of scenarios and end up with a lot of duplication (and fragmentation among drivers).
I can see two: whether you want to allocate / init, or just init? We're not going to have more than that.
For example, drmm_crtc_alloc_with_planes() really isn't much used by anything. [1]
Not that I disagree here, but it might be that it isn't the most helpful helper?
In vc4 case, we just can't use it easily.
Our CRTC driver is shared between the "regular" CRTCs in the display path, and another instance dedicated to the writeback connector.
The shared stuff is initialized through vc4_crtc_init(): https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/vc4/vc4_crtc....
It initializes the structure, set up the planes, etc. Basically everything that our CRTC controller will be doing, and would be shared by both cases.
However, since the writeback and regular CRTC structures are different, we can't really make that function allocate the backing structure either.
We could do some compiler magic to pass the total size and the offset to that function, just like what drmm_crtc_alloc_with_planes is doing, but then we would have the same issue with the writeback stuff that needs to initialize the encoder and connector.
So we would need a drmm_encoder_init anyway.
Same for drmm_universal_plane_alloc(). [2]
Instead of adding new helpers, it would be better to build upon drmm_crtc_alloc_with_planes(), drmm_univeral_plane_alloc(), etc.
For example, a good starting point would be vc4_plane_init(). It could alloc with drmm_univeral_plane_alloc(), which would replace devm_kzalloc() [3] and drm_univeral_plane_alloc() [4] in one step. From what I understand, that's what your patchset wants to do. But it looks like you're effectively open-coding drmm_universl_plane_alloc().
Where I could use the alloc helper, I did. See the following patch that does exactly what you described: https://lore.kernel.org/dri-devel/20220610092924.754942-17-maxime@cerno.tech...
With vc4_plane_init() correctly managed, the next candidate could be vc4_crtc_init(). You probably want to pull vc4_plane_init() [5] into callers. to get it out of the way. If you move calls to devm_kzalloc() [6] and drm_crtc_init_with_planes() [7] closer together, you can replace them with drmm_crtc_alloc_with_planes().
See above
Maxime
Hi
Am 14.06.22 um 14:09 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 01:47:28PM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 11:04 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 10:29:20AM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard: > The DRM-managed function to register a CRTC is > drmm_crtc_alloc_with_planes(), which will allocate the underlying > structure and initialisation the CRTC. > > However, we might want to separate the structure creation and the CRTC > initialisation, for example if the structure is shared across multiple > DRM entities, for example an encoder and a connector. > > Let's create an helper to only initialise a CRTC that would be passed as > an argument.
Before I review all of thes patches, one question. it's yet not clear to me why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on IRC.)
If you're calling drmm_mode_config_init(), it will clean up all the CRTC, encoder connector, etc. data structures for you. For CRTC instances in kmalloced memory, wouldn't it be simpler to put the corresponding kfree into vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
I'm a bit confused. drm_crtc_init_with_planes() at the moment has:
- Note: consider using drmm_crtc_alloc_with_planes() instead of
- drm_crtc_init_with_planes() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
Just like drm_encoder_init(), drm_simple_encoder_init() and drm_universal_plane_init(), so all the functions we have a drmm_* helper for.
And we have a TODO-list item that heavily hints at using them: https://dri.freedesktop.org/docs/drm/gpu/todo.html#object-lifetime-fixes
So it looks like we're already well on the deprecation path?
AFAIU that TODO item is about replacing devm_kzalloc() with drmm_kzalloc(). It's not about the plain init functions, such as drm_crtc_init_with_planes() or drm_universal_plane_init(). Many simple drivers allocate their modesetting pipeline's components first and then build the pipeline with the drm_ functions. I don't think we can take that away from them.
Sure, that's exactly what those first patches are about? It allows to use a DRM managed initialization without disrupting the drivers structure too much?
The concern I have is that we're adding lots of helpers for all kind of scenarios and end up with a lot of duplication (and fragmentation among drivers).
I can see two: whether you want to allocate / init, or just init? We're not going to have more than that.
For example, drmm_crtc_alloc_with_planes() really isn't much used by anything. [1]
Not that I disagree here, but it might be that it isn't the most helpful helper?
In vc4 case, we just can't use it easily.
Our CRTC driver is shared between the "regular" CRTCs in the display path, and another instance dedicated to the writeback connector.
The shared stuff is initialized through vc4_crtc_init(): https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/vc4/vc4_crtc....
It initializes the structure, set up the planes, etc. Basically everything that our CRTC controller will be doing, and would be shared by both cases.
However, since the writeback and regular CRTC structures are different, we can't really make that function allocate the backing structure either.
It appears to me that it's a problem with how vc4 organizes its pipeline. That's why I suggested to move some of vc4's init code around to make such allocations more flexible.
We could do some compiler magic to pass the total size and the offset to that function, just like what drmm_crtc_alloc_with_planes is doing, but then we would have the same issue with the writeback stuff that needs to initialize the encoder and connector.
In vc4_crtc.c it should be possible to use drmm_crtc_alloc_with_planes(). In vc4_txp.c, the code apparently initializes struct vc4_txp, so it would be better to use a managed cleanup of struct vc4_txp.
See, helpers should be useful to many drivers. If we add them, we also add a resources and maintenance overhead to our libraries. And right now, these new functions appear to work around the design of the vc4 driver's data structures. If you want to keep them, maybe let's first merge them into vc4 (something like vc4_crtc_init_with_planes(), etc). If another driver with a use case comes along, we can still move them out easily.
Best regards Thomas
So we would need a drmm_encoder_init anyway.
Same for drmm_universal_plane_alloc(). [2]
Instead of adding new helpers, it would be better to build upon drmm_crtc_alloc_with_planes(), drmm_univeral_plane_alloc(), etc.
For example, a good starting point would be vc4_plane_init(). It could alloc with drmm_univeral_plane_alloc(), which would replace devm_kzalloc() [3] and drm_univeral_plane_alloc() [4] in one step. From what I understand, that's what your patchset wants to do. But it looks like you're effectively open-coding drmm_universl_plane_alloc().
Where I could use the alloc helper, I did. See the following patch that does exactly what you described: https://lore.kernel.org/dri-devel/20220610092924.754942-17-maxime@cerno.tech...
With vc4_plane_init() correctly managed, the next candidate could be vc4_crtc_init(). You probably want to pull vc4_plane_init() [5] into callers. to get it out of the way. If you move calls to devm_kzalloc() [6] and drm_crtc_init_with_planes() [7] closer together, you can replace them with drmm_crtc_alloc_with_planes().
See above
Maxime
On Wed, Jun 15, 2022 at 09:22:55AM +0200, Thomas Zimmermann wrote:
Hi
Am 14.06.22 um 14:09 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 01:47:28PM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 11:04 schrieb Maxime Ripard:
On Tue, Jun 14, 2022 at 10:29:20AM +0200, Thomas Zimmermann wrote:
Am 14.06.22 um 09:37 schrieb Maxime Ripard:
Hi Thomas,
On Mon, Jun 13, 2022 at 01:23:54PM +0200, Thomas Zimmermann wrote: > Am 10.06.22 um 11:28 schrieb Maxime Ripard: > > The DRM-managed function to register a CRTC is > > drmm_crtc_alloc_with_planes(), which will allocate the underlying > > structure and initialisation the CRTC. > > > > However, we might want to separate the structure creation and the CRTC > > initialisation, for example if the structure is shared across multiple > > DRM entities, for example an encoder and a connector. > > > > Let's create an helper to only initialise a CRTC that would be passed as > > an argument. > > Before I review all of thes patches, one question. it's yet not clear to me > why drm_crtc_init_with_planes() wouldn't work. (I know we discussed this on > IRC.) > > If you're calling drmm_mode_config_init(), it will clean up all the CRTC, > encoder connector, etc. data structures for you. For CRTC instances in > kmalloced memory, wouldn't it be simpler to put the corresponding kfree into > vc4_crtc_destroy()?
My intent was to remove as much of the lifetime handling and memory-management from drivers as possible.
My feeling is that while the solution you suggest would definitely work, it relies on drivers authors to know about this, and make the effort to convert the drivers themselves. And then we would have to review that, which we will probably miss (collectively), because it's a bit obscure.
While with either the drmm_alloc_* functions, or the new functions I introduce there, we can just deprecate the old ones, create a TODO entry and a coccinelle script and trust that it works properly.
Thanks for explaining the motivation.
I would not want to deprecate any of the existing functions, as they work for many drivers. The drmm_ functions add additional overhead that not everyone is willing to pay.
I'm a bit confused. drm_crtc_init_with_planes() at the moment has:
- Note: consider using drmm_crtc_alloc_with_planes() instead of
- drm_crtc_init_with_planes() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
Just like drm_encoder_init(), drm_simple_encoder_init() and drm_universal_plane_init(), so all the functions we have a drmm_* helper for.
And we have a TODO-list item that heavily hints at using them: https://dri.freedesktop.org/docs/drm/gpu/todo.html#object-lifetime-fixes
So it looks like we're already well on the deprecation path?
AFAIU that TODO item is about replacing devm_kzalloc() with drmm_kzalloc(). It's not about the plain init functions, such as drm_crtc_init_with_planes() or drm_universal_plane_init(). Many simple drivers allocate their modesetting pipeline's components first and then build the pipeline with the drm_ functions. I don't think we can take that away from them.
Sure, that's exactly what those first patches are about? It allows to use a DRM managed initialization without disrupting the drivers structure too much?
The concern I have is that we're adding lots of helpers for all kind of scenarios and end up with a lot of duplication (and fragmentation among drivers).
I can see two: whether you want to allocate / init, or just init? We're not going to have more than that.
For example, drmm_crtc_alloc_with_planes() really isn't much used by anything. [1]
Not that I disagree here, but it might be that it isn't the most helpful helper?
In vc4 case, we just can't use it easily.
Our CRTC driver is shared between the "regular" CRTCs in the display path, and another instance dedicated to the writeback connector.
The shared stuff is initialized through vc4_crtc_init(): https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/vc4/vc4_crtc....
It initializes the structure, set up the planes, etc. Basically everything that our CRTC controller will be doing, and would be shared by both cases.
However, since the writeback and regular CRTC structures are different, we can't really make that function allocate the backing structure either.
It appears to me that it's a problem with how vc4 organizes its pipeline. That's why I suggested to move some of vc4's init code around to make such allocations more flexible.
I mean, it's only a problem because the helpers aren't flexible enough. Reworking the code to allocate the CRTC in vc4_crtc_init() would create much more churn in the TXP driver. So sure, the core would be nice and tidy, but aren't helpers supposed to simplify drivers?
We could do some compiler magic to pass the total size and the offset to that function, just like what drmm_crtc_alloc_with_planes is doing, but then we would have the same issue with the writeback stuff that needs to initialize the encoder and connector.
In vc4_crtc.c it should be possible to use drmm_crtc_alloc_with_planes(). In vc4_txp.c, the code apparently initializes struct vc4_txp, so it would be better to use a managed cleanup of struct vc4_txp.
This only pushes the problem later on. We fixed the CRTC issue, but we have now the exact same situation with the encoder and connector.
See, helpers should be useful to many drivers. If we add them, we also add a resources and maintenance overhead to our libraries. And right now, these new functions appear to work around the design of the vc4 driver's data structures. If you want to keep them, maybe let's first merge them into vc4 (something like vc4_crtc_init_with_planes(), etc). If another driver with a use case comes along, we can still move them out easily.
Not that I disagree, but there's also the fact that people will start using helpers because they are available.
You mentioned drmm_crtc_alloc_with_planes(). It was introduced in 5.12 with a single user (ipuv3-crtc.c). And then, because it was available, in 5.17 was merged the Unisoc driver that was the second user of that function.
drmm_simple_encoder_alloc() and drmm_universal_plane_alloc() are in the same situation and we wouldn't have had that discussion if it was kept in the imx driver.
The helper being there allows driver authors to discover them easily, pointing out an issue that possibly wasn't obvious to the author, and we can also point during review that the helpers are there to be used.
None of that would be possible if we were to keep them in a driver, because no one but the author would know about it.
My feeling is that the rule you mention works great when you know that some deviation is going to happen. But we're replacing an init function that has been proved good enough here, so it's not rocket science really.
drmm_mutex_init() is a great example of that actually. You merged it recently with two users. We could have used the exact same argument that it belonged in those drivers because it wasn't generic enough or something. But it's trivial, so it was a good decision to merge it as a helper. And because you did so, I later found out that mutex_destroy() was supposed to be called in the first place, I converted vc4 to drmm_mutex_init(), and now that bug is fixed.
It wouldn't have been the case if you kept it inside the drivers.
Maxime
Hi
Am 15.06.22 um 10:32 schrieb Maxime Ripard: [...]
See, helpers should be useful to many drivers. If we add them, we also add a resources and maintenance overhead to our libraries. And right now, these new functions appear to work around the design of the vc4 driver's data structures. If you want to keep them, maybe let's first merge them into vc4 (something like vc4_crtc_init_with_planes(), etc). If another driver with a use case comes along, we can still move them out easily.
Not that I disagree, but there's also the fact that people will start using helpers because they are available.
You mentioned drmm_crtc_alloc_with_planes(). It was introduced in 5.12 with a single user (ipuv3-crtc.c). And then, because it was available, in 5.17 was merged the Unisoc driver that was the second user of that function.
OTOH, it actually took 5 releases to find another user. Maybe we need to look harder for possible reuse of helpers, but I wouldn't count 5 releases as a good track record.
drmm_simple_encoder_alloc() and drmm_universal_plane_alloc() are in the same situation and we wouldn't have had that discussion if it was kept in the imx driver.
The helper being there allows driver authors to discover them easily, pointing out an issue that possibly wasn't obvious to the author, and we can also point during review that the helpers are there to be used.
None of that would be possible if we were to keep them in a driver, because no one but the author would know about it.
My feeling is that the rule you mention works great when you know that some deviation is going to happen. But we're replacing an init function that has been proved good enough here, so it's not rocket science really.
drmm_mutex_init() is a great example of that actually. You merged it recently with two users. We could have used the exact same argument that it belonged in those drivers because it wasn't generic enough or something. But it's trivial, so it was a good decision to merge it as a helper. And because you did so, I later found out that mutex_destroy() was supposed to be called in the first place, I converted vc4 to drmm_mutex_init(), and now that bug is fixed.
But when I added it, there actually were two users. I would not have added drmm_mutex_init() if it was only useful for a single driver.
In other cases, we tend to push single-user helpers into the drivers. That happened several times with TTM. Code was moved into vmwgfx, because there where no other users.
Anyway, as you insist on using this helper, go for it. But please, at least reimplement drm_crtc_alloc_with_planes() on top of a shared internal implementation. AFAICT drm_crtc_alloc_with_planes() is drmm_kzalloc + drmm_crtc_init_with_planes(). Same for other related helpers in the other patches, if there are any.
Best regards Thomas
It wouldn't have been the case if you kept it inside the drivers.
Maxime
On Wed, Jun 15, 2022 at 12:34:46PM +0200, Thomas Zimmermann wrote:
Hi
Am 15.06.22 um 10:32 schrieb Maxime Ripard: [...]
See, helpers should be useful to many drivers. If we add them, we also add a resources and maintenance overhead to our libraries. And right now, these new functions appear to work around the design of the vc4 driver's data structures. If you want to keep them, maybe let's first merge them into vc4 (something like vc4_crtc_init_with_planes(), etc). If another driver with a use case comes along, we can still move them out easily.
Not that I disagree, but there's also the fact that people will start using helpers because they are available.
You mentioned drmm_crtc_alloc_with_planes(). It was introduced in 5.12 with a single user (ipuv3-crtc.c). And then, because it was available, in 5.17 was merged the Unisoc driver that was the second user of that function.
OTOH, it actually took 5 releases to find another user. Maybe we need to look harder for possible reuse of helpers, but I wouldn't count 5 releases as a good track record.
Indeed, but I'm not sure it's due to the helper itself. I'm fairly sure nobody really cared or knows about the lifetime issues solved by the drm-managed functions, and so nobody feels an urge to convert.
And one can't ask during review to use it if they're not aware of the helpers existence. Between 5.12 and 5.17, only hyperv and sprd were merged. 50% of the new drivers using it is not too bad.
drmm_simple_encoder_alloc() and drmm_universal_plane_alloc() are in the same situation and we wouldn't have had that discussion if it was kept in the imx driver.
The helper being there allows driver authors to discover them easily, pointing out an issue that possibly wasn't obvious to the author, and we can also point during review that the helpers are there to be used.
None of that would be possible if we were to keep them in a driver, because no one but the author would know about it.
My feeling is that the rule you mention works great when you know that some deviation is going to happen. But we're replacing an init function that has been proved good enough here, so it's not rocket science really.
drmm_mutex_init() is a great example of that actually. You merged it recently with two users. We could have used the exact same argument that it belonged in those drivers because it wasn't generic enough or something. But it's trivial, so it was a good decision to merge it as a helper. And because you did so, I later found out that mutex_destroy() was supposed to be called in the first place, I converted vc4 to drmm_mutex_init(), and now that bug is fixed.
But when I added it, there actually were two users. I would not have added drmm_mutex_init() if it was only useful for a single driver.
In other cases, we tend to push single-user helpers into the drivers. That happened several times with TTM. Code was moved into vmwgfx, because there where no other users.
Yeah, and I introduced some in that series too. It makes sense to have that restriction for stuff that we're not really sure about. But for strict equivalents to functions that already exists with the same API I'm not sure that restriction makes sense.
In fact, we also merged recently devm_drm_bridge_add with a single user and that was fine too, because that function has been there for a while and we know it's not going to change much.
Anyway, as you insist on using this helper, go for it. But please, at least reimplement drm_crtc_alloc_with_planes() on top of a shared internal implementation. AFAICT drm_crtc_alloc_with_planes() is drmm_kzalloc + drmm_crtc_init_with_planes().
Ack
Same for other related helpers in the other patches, if there are any.
drmm_encoder_alloc() and drmm_simple_encoder_alloc() are in the same situation, I'll fix those too.
Maxime
The DRM-managed function to register an encoder is drmm_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_encoder.c | 48 ++++++++++++++++++++++++++++++++--- include/drm/drm_encoder.h | 5 ++++ 2 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_encoder.c b/drivers/gpu/drm/drm_encoder.c index a940024c8087..4b7b82f8c6fa 100644 --- a/drivers/gpu/drm/drm_encoder.c +++ b/drivers/gpu/drm/drm_encoder.c @@ -148,9 +148,9 @@ static int __drm_encoder_init(struct drm_device *dev, * the encoder structure. The encoder structure should not be allocated with * devm_kzalloc(). * - * Note: consider using drmm_encoder_alloc() instead of drm_encoder_init() to - * let the DRM managed resource infrastructure take care of cleanup and - * deallocation. + * Note: consider using drmm_encoder_alloc() or drmm_encoder_init() + * instead of drm_encoder_init() to let the DRM managed resource + * infrastructure take care of cleanup and deallocation. * * Returns: * Zero on success, error code on failure. @@ -244,6 +244,48 @@ void *__drmm_encoder_alloc(struct drm_device *dev, size_t size, size_t offset, } EXPORT_SYMBOL(__drmm_encoder_alloc);
+/** + * drmm_encoder_init - Initialize a preallocated encoder + * @dev: drm device + * @encoder: the encoder to init + * @funcs: callbacks for this encoder (optional) + * @encoder_type: user visible type of the encoder + * @name: printf style format string for the encoder name, or NULL for default name + * + * Initializes a preallocated encoder. Encoder should be subclassed as + * part of driver encoder objects. Cleanup is automatically handled + * through registering drm_encoder_cleanup() with drmm_add_action(). The + * encoder structure should be allocated with drmm_kzalloc(). + * + * The @drm_encoder_funcs.destroy hook must be NULL. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_encoder_init(struct drm_device *dev, struct drm_encoder *encoder, + const struct drm_encoder_funcs *funcs, + int encoder_type, const char *name, ...) +{ + va_list ap; + int ret; + + if (WARN_ON(funcs && funcs->destroy)) + return -EINVAL; + + va_start(ap, name); + ret = __drm_encoder_init(dev, encoder, funcs, encoder_type, name, ap); + va_end(ap); + if (ret) + return ret; + + ret = drmm_add_action_or_reset(dev, drmm_encoder_alloc_release, encoder); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drmm_encoder_init); + static struct drm_crtc *drm_encoder_get_crtc(struct drm_encoder *encoder) { struct drm_connector *connector; diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h index 6e91a0280f31..6713fe1804e9 100644 --- a/include/drm/drm_encoder.h +++ b/include/drm/drm_encoder.h @@ -194,6 +194,11 @@ int drm_encoder_init(struct drm_device *dev, const struct drm_encoder_funcs *funcs, int encoder_type, const char *name, ...);
+int drmm_encoder_init(struct drm_device *dev, + struct drm_encoder *encoder, + const struct drm_encoder_funcs *funcs, + int encoder_type, const char *name, ...); + __printf(6, 7) void *__drmm_encoder_alloc(struct drm_device *dev, size_t size, size_t offset,
Unlike most of the other files in DRM, and Linux in general, the headers in drm_connector.c aren't sorted alphabetically. Let's fix that.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_connector.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 1c48d162c77e..353d83ae09d3 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -22,14 +22,14 @@
#include <drm/drm_auth.h> #include <drm/drm_connector.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_encoder.h> -#include <drm/drm_utils.h> -#include <drm/drm_print.h> -#include <drm/drm_drv.h> #include <drm/drm_file.h> +#include <drm/drm_print.h> #include <drm/drm_privacy_screen_consumer.h> #include <drm/drm_sysfs.h> +#include <drm/drm_utils.h>
#include <linux/uaccess.h>
Unlike encoders and CRTCs, the drm_connector_init() and drm_connector_init_with_ddc() don't mention how the cleanup is supposed to be done. Let's add it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_connector.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 353d83ae09d3..2a78a23836d8 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -222,6 +222,11 @@ void drm_connector_free_work_fn(struct work_struct *work) * Initialises a preallocated connector. Connectors should be * subclassed as part of driver connector objects. * + * At driver unload time the driver's &drm_connector_funcs.destroy hook + * should call drm_connector_unregister(), drm_connector_cleanup() and + * kfree() the connector structure. The connector structure should not + * be allocated with devm_kzalloc(). + * * Returns: * Zero on success, error code on failure. */ @@ -345,6 +350,11 @@ EXPORT_SYMBOL(drm_connector_init); * Initialises a preallocated connector. Connectors should be * subclassed as part of driver connector objects. * + * At driver unload time the driver's &drm_connector_funcs.destroy hook + * should call drm_connector_unregister(), drm_connector_cleanup() and + * kfree() the connector structure. The connector structure should not + * be allocated with devm_kzalloc(). + * * Ensures that the ddc field of the connector is correctly set. * * Returns:
Hi
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Unlike encoders and CRTCs, the drm_connector_init() and drm_connector_init_with_ddc() don't mention how the cleanup is supposed to be done. Let's add it.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_connector.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 353d83ae09d3..2a78a23836d8 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -222,6 +222,11 @@ void drm_connector_free_work_fn(struct work_struct *work)
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects.
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure. The connector structure should not
This sounds odd. Maybe
'... to release... the connector structure' ?
- be allocated with devm_kzalloc().
*/
- Returns:
- Zero on success, error code on failure.
@@ -345,6 +350,11 @@ EXPORT_SYMBOL(drm_connector_init);
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects.
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure. The connector structure should not
- be allocated with devm_kzalloc().
Same here.
Best regards Thomas
- Ensures that the ddc field of the connector is correctly set.
- Returns:
Hi Thomas,
Thanks for your review
On Mon, Jun 20, 2022 at 12:21:33PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Unlike encoders and CRTCs, the drm_connector_init() and drm_connector_init_with_ddc() don't mention how the cleanup is supposed to be done. Let's add it.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_connector.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 353d83ae09d3..2a78a23836d8 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -222,6 +222,11 @@ void drm_connector_free_work_fn(struct work_struct *work)
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects.
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure. The connector structure should not
This sounds odd. Maybe
'... to release... the connector structure' ?
I didn't really get what your suggestion was exactly
Is it
At driver unload time the driver's &drm_connector_funcs.destroy hook should call drm_connector_unregister(), drm_connector_cleanup() and kfree() to release the connector structure.
Or something else?
Thanks! Maxime
Hi
Am 20.06.22 um 14:18 schrieb Maxime Ripard:
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure.
This sentence sounds odd.
On Mon, Jun 20, 2022 at 04:19:43PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 14:18 schrieb Maxime Ripard:
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure.
This sentence sounds odd.
Yeah, I was using kfree as a verb which is probably where the weirdness comes from.
Would that be better:
At driver unload time the driver's &drm_connector_funcs.destroy hook should call drm_connector_unregister() and free the connector structure.
Maxime
Hi
Am 20.06.22 um 16:40 schrieb Maxime Ripard:
On Mon, Jun 20, 2022 at 04:19:43PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 14:18 schrieb Maxime Ripard:
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure.
This sentence sounds odd.
Yeah, I was using kfree as a verb which is probably where the weirdness comes from.
Would that be better:
At driver unload time the driver's &drm_connector_funcs.destroy hook should call drm_connector_unregister() and free the connector structure.
Yes. That's better.
Best regards Thomas
Maxime
Unlike other DRM entities, there's no helper to create a DRM-managed initialisation of a connector.
Let's create an helper to initialise a connector that would be passed as an argument, and handle the cleanup through a DRM-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_connector.c | 108 +++++++++++++++++++++++++------- include/drm/drm_connector.h | 4 ++ 2 files changed, 90 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 2a78a23836d8..f150270b519f 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -26,6 +26,7 @@ #include <drm/drm_edid.h> #include <drm/drm_encoder.h> #include <drm/drm_file.h> +#include <drm/drm_managed.h> #include <drm/drm_print.h> #include <drm/drm_privacy_screen_consumer.h> #include <drm/drm_sysfs.h> @@ -212,28 +213,10 @@ void drm_connector_free_work_fn(struct work_struct *work) } }
-/** - * drm_connector_init - Init a preallocated connector - * @dev: DRM device - * @connector: the connector to init - * @funcs: callbacks for this connector - * @connector_type: user visible type of the connector - * - * Initialises a preallocated connector. Connectors should be - * subclassed as part of driver connector objects. - * - * At driver unload time the driver's &drm_connector_funcs.destroy hook - * should call drm_connector_unregister(), drm_connector_cleanup() and - * kfree() the connector structure. The connector structure should not - * be allocated with devm_kzalloc(). - * - * Returns: - * Zero on success, error code on failure. - */ -int drm_connector_init(struct drm_device *dev, - struct drm_connector *connector, - const struct drm_connector_funcs *funcs, - int connector_type) +static int __drm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type) { struct drm_mode_config *config = &dev->mode_config; int ret; @@ -337,6 +320,39 @@ int drm_connector_init(struct drm_device *dev,
return ret; } + +/** + * drm_connector_init - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. + * + * At driver unload time the driver's &drm_connector_funcs.destroy hook + * should call drm_connector_unregister(), drm_connector_cleanup() and + * kfree() the connector structure. The connector structure should not + * be allocated with devm_kzalloc(). + * + * Note: consider using drmm_connector_init() instead of + * drm_connector_init() to let the DRM managed resource infrastructure + * take care of cleanup and deallocation. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type) +{ + if (WARN_ON(!(funcs && funcs->destroy))) + return -EINVAL; + + return __drm_connector_init(dev, connector, funcs, connector_type); +} EXPORT_SYMBOL(drm_connector_init);
/** @@ -379,6 +395,54 @@ int drm_connector_init_with_ddc(struct drm_device *dev, } EXPORT_SYMBOL(drm_connector_init_with_ddc);
+static void drm_connector_cleanup_action(struct drm_device *dev, + void *ptr) +{ + struct drm_connector *connector = ptr; + + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +/** + * drmm_connector_init - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. Cleanup is + * automatically handled through registering drm_connector_unregister() + * and drm_connector_cleanup() with drm_add_action(). The connector + * structure should be allocated with drmm_kzalloc(). + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type) +{ + int ret; + + if (WARN_ON(funcs && funcs->destroy)) + return -EINVAL; + + ret = __drm_connector_init(dev, connector, funcs, connector_type); + if (ret) + return ret; + + ret = drmm_add_action_or_reset(dev, drm_connector_cleanup_action, + connector); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drmm_connector_init); + /** * drm_connector_attach_edid_property - attach edid property. * @connector: the connector diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 3ac4bf87f257..35a6b6e944b7 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1672,6 +1672,10 @@ int drm_connector_init_with_ddc(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc); +int drmm_connector_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type); void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); void drm_connector_unregister(struct drm_connector *connector);
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Unlike other DRM entities, there's no helper to create a DRM-managed initialisation of a connector.
Let's create an helper to initialise a connector that would be passed as an argument, and handle the cleanup through a DRM-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/drm_connector.c | 108 +++++++++++++++++++++++++------- include/drm/drm_connector.h | 4 ++ 2 files changed, 90 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 2a78a23836d8..f150270b519f 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -26,6 +26,7 @@ #include <drm/drm_edid.h> #include <drm/drm_encoder.h> #include <drm/drm_file.h> +#include <drm/drm_managed.h> #include <drm/drm_print.h> #include <drm/drm_privacy_screen_consumer.h> #include <drm/drm_sysfs.h> @@ -212,28 +213,10 @@ void drm_connector_free_work_fn(struct work_struct *work) } }
-/**
- drm_connector_init - Init a preallocated connector
- @dev: DRM device
- @connector: the connector to init
- @funcs: callbacks for this connector
- @connector_type: user visible type of the connector
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects.
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure. The connector structure should not
- be allocated with devm_kzalloc().
- Returns:
- Zero on success, error code on failure.
- */
-int drm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
int connector_type)
+static int __drm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
{ struct drm_mode_config *config = &dev->mode_config; int ret;int connector_type)
@@ -337,6 +320,39 @@ int drm_connector_init(struct drm_device *dev,
return ret; }
+/**
- drm_connector_init - Init a preallocated connector
- @dev: DRM device
- @connector: the connector to init
- @funcs: callbacks for this connector
- @connector_type: user visible type of the connector
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects.
- At driver unload time the driver's &drm_connector_funcs.destroy hook
- should call drm_connector_unregister(), drm_connector_cleanup() and
- kfree() the connector structure. The connector structure should not
- be allocated with devm_kzalloc().
- Note: consider using drmm_connector_init() instead of
- drm_connector_init() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
- Returns:
- Zero on success, error code on failure.
- */
+int drm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
int connector_type)
+{
- if (WARN_ON(!(funcs && funcs->destroy)))
return -EINVAL;
- return __drm_connector_init(dev, connector, funcs, connector_type);
+} EXPORT_SYMBOL(drm_connector_init);
/** @@ -379,6 +395,54 @@ int drm_connector_init_with_ddc(struct drm_device *dev, } EXPORT_SYMBOL(drm_connector_init_with_ddc);
+static void drm_connector_cleanup_action(struct drm_device *dev,
void *ptr)
+{
- struct drm_connector *connector = ptr;
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
+}
+/**
- drmm_connector_init - Init a preallocated connector
- @dev: DRM device
- @connector: the connector to init
- @funcs: callbacks for this connector
- @connector_type: user visible type of the connector
- Initialises a preallocated connector. Connectors should be
- subclassed as part of driver connector objects. Cleanup is
- automatically handled through registering drm_connector_unregister()
- and drm_connector_cleanup() with drm_add_action(). The connector
- structure should be allocated with drmm_kzalloc().
- Returns:
- Zero on success, error code on failure.
- */
+int drmm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
int connector_type)
+{
- int ret;
- if (WARN_ON(funcs && funcs->destroy))
return -EINVAL;
- ret = __drm_connector_init(dev, connector, funcs, connector_type);
- if (ret)
return ret;
- ret = drmm_add_action_or_reset(dev, drm_connector_cleanup_action,
connector);
- if (ret)
return ret;
- return 0;
+} +EXPORT_SYMBOL(drmm_connector_init);
- /**
- drm_connector_attach_edid_property - attach edid property.
- @connector: the connector
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 3ac4bf87f257..35a6b6e944b7 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1672,6 +1672,10 @@ int drm_connector_init_with_ddc(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc); +int drmm_connector_init(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); void drm_connector_unregister(struct drm_connector *connector);int connector_type);
Let's create a DRM-managed variant of drm_connector_init_with_ddc that will take care of an action of the connector cleanup.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_connector.c | 72 ++++++++++++++++++++++++++++----- include/drm/drm_connector.h | 5 +++ 2 files changed, 67 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index f150270b519f..f577e5a739f1 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -355,6 +355,30 @@ int drm_connector_init(struct drm_device *dev, } EXPORT_SYMBOL(drm_connector_init);
+typedef int (*connector_init_t)(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type); + +static int __drm_connector_init_with_ddc(struct drm_device *dev, + struct drm_connector *connector, + connector_init_t init_func, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + int ret; + + ret = init_func(dev, connector, funcs, connector_type); + if (ret) + return ret; + + /* provide ddc symlink in sysfs */ + connector->ddc = ddc; + + return ret; +} + /** * drm_connector_init_with_ddc - Init a preallocated connector * @dev: DRM device @@ -373,6 +397,10 @@ EXPORT_SYMBOL(drm_connector_init); * * Ensures that the ddc field of the connector is correctly set. * + * Note: consider using drmm_connector_init_with_ddc() instead of + * drm_connector_init_with_ddc() to let the DRM managed resource + * infrastructure take care of cleanup and deallocation. + * * Returns: * Zero on success, error code on failure. */ @@ -382,16 +410,9 @@ int drm_connector_init_with_ddc(struct drm_device *dev, int connector_type, struct i2c_adapter *ddc) { - int ret; - - ret = drm_connector_init(dev, connector, funcs, connector_type); - if (ret) - return ret; - - /* provide ddc symlink in sysfs */ - connector->ddc = ddc; - - return ret; + return __drm_connector_init_with_ddc(dev, connector, + drm_connector_init, + funcs, connector_type, ddc); } EXPORT_SYMBOL(drm_connector_init_with_ddc);
@@ -443,6 +464,37 @@ int drmm_connector_init(struct drm_device *dev, } EXPORT_SYMBOL(drmm_connector_init);
+/** + * drmm_connector_init_with_ddc - Init a preallocated connector + * @dev: DRM device + * @connector: the connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: pointer to the associated ddc adapter + * + * Initialises a preallocated connector. Connectors should be + * subclassed as part of driver connector objects. Cleanup is + * automatically handled through registering drm_connector_unregister() + * and drm_connector_cleanup() with drm_add_action(). The connector + * structure should be allocated with drmm_kzalloc(). + * + * Ensures that the ddc field of the connector is correctly set. + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_init_with_ddc(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + return __drm_connector_init_with_ddc(dev, connector, + drmm_connector_init, + funcs, connector_type, ddc); +} +EXPORT_SYMBOL(drmm_connector_init_with_ddc); + /** * drm_connector_attach_edid_property - attach edid property. * @connector: the connector diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 35a6b6e944b7..2565541f2c10 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1676,6 +1676,11 @@ int drmm_connector_init(struct drm_device *dev, struct drm_connector *connector, const struct drm_connector_funcs *funcs, int connector_type); +int drmm_connector_init_with_ddc(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc); void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); void drm_connector_unregister(struct drm_connector *connector);
Let's create a DRM-managed variant of drmm_writeback_connector_init that will take care of an action of the encoder and connector cleanup.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_writeback.c | 136 ++++++++++++++++++++++++-------- include/drm/drm_writeback.h | 5 ++ 2 files changed, 108 insertions(+), 33 deletions(-)
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c index dccf4504f1bb..0b00921f3379 100644 --- a/drivers/gpu/drm/drm_writeback.c +++ b/drivers/gpu/drm/drm_writeback.c @@ -149,32 +149,27 @@ static const struct drm_encoder_funcs drm_writeback_encoder_funcs = { .destroy = drm_encoder_cleanup, };
-/** - * drm_writeback_connector_init - Initialize a writeback connector and its properties - * @dev: DRM device - * @wb_connector: Writeback connector to initialize - * @con_funcs: Connector funcs vtable - * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder - * @formats: Array of supported pixel formats for the writeback engine - * @n_formats: Length of the formats array - * - * This function creates the writeback-connector-specific properties if they - * have not been already created, initializes the connector as - * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property - * values. It will also create an internal encoder associated with the - * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for - * the encoder helper. - * - * Drivers should always use this function instead of drm_connector_init() to - * set up writeback connectors. - * - * Returns: 0 on success, or a negative error code - */ -int drm_writeback_connector_init(struct drm_device *dev, - struct drm_writeback_connector *wb_connector, - const struct drm_connector_funcs *con_funcs, - const struct drm_encoder_helper_funcs *enc_helper_funcs, - const u32 *formats, int n_formats) +typedef int (*encoder_init_t)(struct drm_device *dev, + struct drm_encoder *encoder, + const struct drm_encoder_funcs *funcs, + int encoder_type, + const char *name, ...); + +typedef int (*connector_init_t)(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type); + +static int writeback_init(struct drm_device *dev, + struct drm_writeback_connector *wb_connector, + connector_init_t conn_init, + void (*conn_destroy)(struct drm_connector *connector), + encoder_init_t enc_init, + void (*enc_destroy)(struct drm_encoder *encoder), + const struct drm_connector_funcs *con_funcs, + const struct drm_encoder_funcs *enc_funcs, + const struct drm_encoder_helper_funcs *enc_helper_funcs, + const u32 *formats, int n_formats) { struct drm_property_blob *blob; struct drm_connector *connector = &wb_connector->base; @@ -190,16 +185,16 @@ int drm_writeback_connector_init(struct drm_device *dev, return PTR_ERR(blob);
drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs); - ret = drm_encoder_init(dev, &wb_connector->encoder, - &drm_writeback_encoder_funcs, - DRM_MODE_ENCODER_VIRTUAL, NULL); + ret = enc_init(dev, &wb_connector->encoder, + enc_funcs, + DRM_MODE_ENCODER_VIRTUAL, NULL); if (ret) goto fail;
connector->interlace_allowed = 0;
- ret = drm_connector_init(dev, connector, con_funcs, - DRM_MODE_CONNECTOR_WRITEBACK); + ret = conn_init(dev, connector, con_funcs, + DRM_MODE_CONNECTOR_WRITEBACK); if (ret) goto connector_fail;
@@ -231,15 +226,90 @@ int drm_writeback_connector_init(struct drm_device *dev, return 0;
attach_fail: - drm_connector_cleanup(connector); + if (conn_destroy) + conn_destroy(connector); connector_fail: - drm_encoder_cleanup(&wb_connector->encoder); + if (enc_destroy) + enc_destroy(&wb_connector->encoder); fail: drm_property_blob_put(blob); return ret; } + +/** + * drm_writeback_connector_init - Initialize a writeback connector and its properties + * @dev: DRM device + * @wb_connector: Writeback connector to initialize + * @con_funcs: Connector funcs vtable + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder + * @formats: Array of supported pixel formats for the writeback engine + * @n_formats: Length of the formats array + * + * This function creates the writeback-connector-specific properties if they + * have not been already created, initializes the connector as + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property + * values. It will also create an internal encoder associated with the + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for + * the encoder helper. + * + * Drivers should always use this function instead of drm_connector_init() to + * set up writeback connectors. + * + * Returns: 0 on success, or a negative error code + */ +int drm_writeback_connector_init(struct drm_device *dev, + struct drm_writeback_connector *wb_connector, + const struct drm_connector_funcs *con_funcs, + const struct drm_encoder_helper_funcs *enc_helper_funcs, + const u32 *formats, int n_formats) +{ + return writeback_init(dev, wb_connector, + drm_connector_init, drm_connector_cleanup, + drm_encoder_init, drm_encoder_cleanup, + con_funcs, + &drm_writeback_encoder_funcs, enc_helper_funcs, + formats, n_formats); +} EXPORT_SYMBOL(drm_writeback_connector_init);
+/** + * drmm_writeback_connector_init - Initialize a writeback connector and its properties + * @dev: DRM device + * @wb_connector: Writeback connector to initialize + * @con_funcs: Connector funcs vtable + * @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder + * @formats: Array of supported pixel formats for the writeback engine + * @n_formats: Length of the formats array + * + * This function creates the writeback-connector-specific properties if they + * have not been already created, initializes the connector as + * type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property + * values. It will also create an internal encoder associated with the + * drm_writeback_connector and set it to use the @enc_helper_funcs vtable for + * the encoder helper. + * + * The writeback connector is DRM-managed and won't need any cleanup. + * + * Drivers should always use this function instead of drm_connector_init() to + * set up writeback connectors. + * + * Returns: 0 on success, or a negative error code + */ +int drmm_writeback_connector_init(struct drm_device *dev, + struct drm_writeback_connector *wb_connector, + const struct drm_connector_funcs *con_funcs, + const struct drm_encoder_helper_funcs *enc_helper_funcs, + const u32 *formats, int n_formats) +{ + return writeback_init(dev, wb_connector, + drmm_connector_init, NULL, + drmm_encoder_init, NULL, + con_funcs, + NULL, enc_helper_funcs, + formats, n_formats); +} +EXPORT_SYMBOL(drmm_writeback_connector_init); + int drm_writeback_set_fb(struct drm_connector_state *conn_state, struct drm_framebuffer *fb) { diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h index 9697d2714d2a..cc259ae44734 100644 --- a/include/drm/drm_writeback.h +++ b/include/drm/drm_writeback.h @@ -151,6 +151,11 @@ int drm_writeback_connector_init(struct drm_device *dev, const struct drm_connector_funcs *con_funcs, const struct drm_encoder_helper_funcs *enc_helper_funcs, const u32 *formats, int n_formats); +int drmm_writeback_connector_init(struct drm_device *dev, + struct drm_writeback_connector *wb_connector, + const struct drm_connector_funcs *con_funcs, + const struct drm_encoder_helper_funcs *enc_helper_funcs, + const u32 *formats, int n_formats);
int drm_writeback_set_fb(struct drm_connector_state *conn_state, struct drm_framebuffer *fb);
Hi
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Let's create a DRM-managed variant of drmm_writeback_connector_init that will take care of an action of the encoder and connector cleanup.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/drm_writeback.c | 136 ++++++++++++++++++++++++-------- include/drm/drm_writeback.h | 5 ++ 2 files changed, 108 insertions(+), 33 deletions(-)
diff --git a/drivers/gpu/drm/drm_writeback.c b/drivers/gpu/drm/drm_writeback.c index dccf4504f1bb..0b00921f3379 100644 --- a/drivers/gpu/drm/drm_writeback.c +++ b/drivers/gpu/drm/drm_writeback.c @@ -149,32 +149,27 @@ static const struct drm_encoder_funcs drm_writeback_encoder_funcs = { .destroy = drm_encoder_cleanup, };
-/**
- drm_writeback_connector_init - Initialize a writeback connector and its properties
- @dev: DRM device
- @wb_connector: Writeback connector to initialize
- @con_funcs: Connector funcs vtable
- @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
- @formats: Array of supported pixel formats for the writeback engine
- @n_formats: Length of the formats array
- This function creates the writeback-connector-specific properties if they
- have not been already created, initializes the connector as
- type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
- values. It will also create an internal encoder associated with the
- drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
- the encoder helper.
- Drivers should always use this function instead of drm_connector_init() to
- set up writeback connectors.
- Returns: 0 on success, or a negative error code
- */
-int drm_writeback_connector_init(struct drm_device *dev,
struct drm_writeback_connector *wb_connector,
const struct drm_connector_funcs *con_funcs,
const struct drm_encoder_helper_funcs *enc_helper_funcs,
const u32 *formats, int n_formats)
+typedef int (*encoder_init_t)(struct drm_device *dev,
struct drm_encoder *encoder,
const struct drm_encoder_funcs *funcs,
int encoder_type,
const char *name, ...);
+typedef int (*connector_init_t)(struct drm_device *dev,
struct drm_connector *connector,
const struct drm_connector_funcs *funcs,
int connector_type);
This code has menawhile changed in drm-tip, which makes it harder to give a good review for such refactoring.
But generally, I'd do the connector and encoder initialization in drmm_writeback_connector_init() and give the initialized encoders to an internal helper that does the common init steps. That avoids such indirections with functions pointers.
Best regards Thomas
+static int writeback_init(struct drm_device *dev,
struct drm_writeback_connector *wb_connector,
connector_init_t conn_init,
void (*conn_destroy)(struct drm_connector *connector),
encoder_init_t enc_init,
void (*enc_destroy)(struct drm_encoder *encoder),
const struct drm_connector_funcs *con_funcs,
const struct drm_encoder_funcs *enc_funcs,
const struct drm_encoder_helper_funcs *enc_helper_funcs,
{ struct drm_property_blob *blob; struct drm_connector *connector = &wb_connector->base;const u32 *formats, int n_formats)
@@ -190,16 +185,16 @@ int drm_writeback_connector_init(struct drm_device *dev, return PTR_ERR(blob);
drm_encoder_helper_add(&wb_connector->encoder, enc_helper_funcs);
- ret = drm_encoder_init(dev, &wb_connector->encoder,
&drm_writeback_encoder_funcs,
DRM_MODE_ENCODER_VIRTUAL, NULL);
ret = enc_init(dev, &wb_connector->encoder,
enc_funcs,
DRM_MODE_ENCODER_VIRTUAL, NULL);
if (ret) goto fail;
connector->interlace_allowed = 0;
- ret = drm_connector_init(dev, connector, con_funcs,
DRM_MODE_CONNECTOR_WRITEBACK);
- ret = conn_init(dev, connector, con_funcs,
if (ret) goto connector_fail;DRM_MODE_CONNECTOR_WRITEBACK);
@@ -231,15 +226,90 @@ int drm_writeback_connector_init(struct drm_device *dev, return 0;
attach_fail:
- drm_connector_cleanup(connector);
- if (conn_destroy)
connector_fail:conn_destroy(connector);
- drm_encoder_cleanup(&wb_connector->encoder);
- if (enc_destroy)
fail: drm_property_blob_put(blob); return ret; }enc_destroy(&wb_connector->encoder);
+/**
- drm_writeback_connector_init - Initialize a writeback connector and its properties
- @dev: DRM device
- @wb_connector: Writeback connector to initialize
- @con_funcs: Connector funcs vtable
- @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
- @formats: Array of supported pixel formats for the writeback engine
- @n_formats: Length of the formats array
- This function creates the writeback-connector-specific properties if they
- have not been already created, initializes the connector as
- type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
- values. It will also create an internal encoder associated with the
- drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
- the encoder helper.
- Drivers should always use this function instead of drm_connector_init() to
- set up writeback connectors.
- Returns: 0 on success, or a negative error code
- */
+int drm_writeback_connector_init(struct drm_device *dev,
struct drm_writeback_connector *wb_connector,
const struct drm_connector_funcs *con_funcs,
const struct drm_encoder_helper_funcs *enc_helper_funcs,
const u32 *formats, int n_formats)
+{
- return writeback_init(dev, wb_connector,
drm_connector_init, drm_connector_cleanup,
drm_encoder_init, drm_encoder_cleanup,
con_funcs,
&drm_writeback_encoder_funcs, enc_helper_funcs,
formats, n_formats);
+} EXPORT_SYMBOL(drm_writeback_connector_init);
+/**
- drmm_writeback_connector_init - Initialize a writeback connector and its properties
- @dev: DRM device
- @wb_connector: Writeback connector to initialize
- @con_funcs: Connector funcs vtable
- @enc_helper_funcs: Encoder helper funcs vtable to be used by the internal encoder
- @formats: Array of supported pixel formats for the writeback engine
- @n_formats: Length of the formats array
- This function creates the writeback-connector-specific properties if they
- have not been already created, initializes the connector as
- type DRM_MODE_CONNECTOR_WRITEBACK, and correctly initializes the property
- values. It will also create an internal encoder associated with the
- drm_writeback_connector and set it to use the @enc_helper_funcs vtable for
- the encoder helper.
- The writeback connector is DRM-managed and won't need any cleanup.
- Drivers should always use this function instead of drm_connector_init() to
- set up writeback connectors.
- Returns: 0 on success, or a negative error code
- */
+int drmm_writeback_connector_init(struct drm_device *dev,
struct drm_writeback_connector *wb_connector,
const struct drm_connector_funcs *con_funcs,
const struct drm_encoder_helper_funcs *enc_helper_funcs,
const u32 *formats, int n_formats)
+{
- return writeback_init(dev, wb_connector,
drmm_connector_init, NULL,
drmm_encoder_init, NULL,
con_funcs,
NULL, enc_helper_funcs,
formats, n_formats);
+} +EXPORT_SYMBOL(drmm_writeback_connector_init);
- int drm_writeback_set_fb(struct drm_connector_state *conn_state, struct drm_framebuffer *fb) {
diff --git a/include/drm/drm_writeback.h b/include/drm/drm_writeback.h index 9697d2714d2a..cc259ae44734 100644 --- a/include/drm/drm_writeback.h +++ b/include/drm/drm_writeback.h @@ -151,6 +151,11 @@ int drm_writeback_connector_init(struct drm_device *dev, const struct drm_connector_funcs *con_funcs, const struct drm_encoder_helper_funcs *enc_helper_funcs, const u32 *formats, int n_formats); +int drmm_writeback_connector_init(struct drm_device *dev,
struct drm_writeback_connector *wb_connector,
const struct drm_connector_funcs *con_funcs,
const struct drm_encoder_helper_funcs *enc_helper_funcs,
const u32 *formats, int n_formats);
int drm_writeback_set_fb(struct drm_connector_state *conn_state, struct drm_framebuffer *fb);
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/drm_simple_kms_helper.c | 46 +++++++++++++++++++++++-- include/drm/drm_simple_kms_helper.h | 3 ++ 2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c index 72989ed1baba..876870dd98e5 100644 --- a/drivers/gpu/drm/drm_simple_kms_helper.c +++ b/drivers/gpu/drm/drm_simple_kms_helper.c @@ -58,9 +58,10 @@ static const struct drm_encoder_funcs drm_simple_encoder_funcs_cleanup = { * stored in the device structure. Free the encoder's memory as part of * the device release function. * - * Note: consider using drmm_simple_encoder_alloc() instead of - * drm_simple_encoder_init() to let the DRM managed resource infrastructure - * take care of cleanup and deallocation. + * Note: consider using drmm_simple_encoder_alloc() or + * drmm_simple_encoder_init() instead of drm_simple_encoder_init() to + * let the DRM managed resource infrastructure take care of cleanup and + * deallocation. * * Returns: * Zero on success, error code on failure. @@ -75,6 +76,45 @@ int drm_simple_encoder_init(struct drm_device *dev, } EXPORT_SYMBOL(drm_simple_encoder_init);
+static void drmm_simple_encoder_cleanup(struct drm_device *dev, void *ptr) +{ + struct drm_encoder *encoder = ptr; + + drm_encoder_cleanup(encoder); +} + +/** + * drmm_simple_encoder_init - Initialize a preallocated encoder with + * basic functionality. + * @dev: drm device + * @encoder: the encoder to initialize + * @encoder_type: user visible type of the encoder + * + * Initialises a preallocated encoder that has no further functionality. + * Settings for possible CRTC and clones are left to their initial + * values. The encoder will be cleaned up automatically using a + * DRM-managed action. + * + * The structure containing the encoder's memory should be allocated + * using drmm_kzalloc(). + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_simple_encoder_init(struct drm_device *dev, + struct drm_encoder *encoder, + int encoder_type) +{ + int ret; + + ret = drm_encoder_init(dev, encoder, NULL, encoder_type, NULL); + if (ret) + return ret; + + return drmm_add_action_or_reset(dev, drmm_simple_encoder_cleanup, encoder); +} +EXPORT_SYMBOL(drmm_simple_encoder_init); + void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size, size_t offset, int encoder_type) { diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h index 0b3647e614dd..20456f4712f0 100644 --- a/include/drm/drm_simple_kms_helper.h +++ b/include/drm/drm_simple_kms_helper.h @@ -241,6 +241,9 @@ int drm_simple_display_pipe_init(struct drm_device *dev, int drm_simple_encoder_init(struct drm_device *dev, struct drm_encoder *encoder, int encoder_type); +int drmm_simple_encoder_init(struct drm_device *dev, + struct drm_encoder *encoder, + int encoder_type);
void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size, size_t offset, int encoder_type);
Hi
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake. It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
Best regards Thomas
drivers/gpu/drm/drm_simple_kms_helper.c | 46 +++++++++++++++++++++++-- include/drm/drm_simple_kms_helper.h | 3 ++ 2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c index 72989ed1baba..876870dd98e5 100644 --- a/drivers/gpu/drm/drm_simple_kms_helper.c +++ b/drivers/gpu/drm/drm_simple_kms_helper.c @@ -58,9 +58,10 @@ static const struct drm_encoder_funcs drm_simple_encoder_funcs_cleanup = {
- stored in the device structure. Free the encoder's memory as part of
- the device release function.
- Note: consider using drmm_simple_encoder_alloc() instead of
- drm_simple_encoder_init() to let the DRM managed resource infrastructure
- take care of cleanup and deallocation.
- Note: consider using drmm_simple_encoder_alloc() or
- drmm_simple_encoder_init() instead of drm_simple_encoder_init() to
- let the DRM managed resource infrastructure take care of cleanup and
- deallocation.
- Returns:
- Zero on success, error code on failure.
@@ -75,6 +76,45 @@ int drm_simple_encoder_init(struct drm_device *dev, } EXPORT_SYMBOL(drm_simple_encoder_init);
+static void drmm_simple_encoder_cleanup(struct drm_device *dev, void *ptr) +{
- struct drm_encoder *encoder = ptr;
- drm_encoder_cleanup(encoder);
+}
+/**
- drmm_simple_encoder_init - Initialize a preallocated encoder with
basic functionality.
- @dev: drm device
- @encoder: the encoder to initialize
- @encoder_type: user visible type of the encoder
- Initialises a preallocated encoder that has no further functionality.
- Settings for possible CRTC and clones are left to their initial
- values. The encoder will be cleaned up automatically using a
- DRM-managed action.
- The structure containing the encoder's memory should be allocated
- using drmm_kzalloc().
- Returns:
- Zero on success, error code on failure.
- */
+int drmm_simple_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
int encoder_type)
+{
- int ret;
- ret = drm_encoder_init(dev, encoder, NULL, encoder_type, NULL);
- if (ret)
return ret;
- return drmm_add_action_or_reset(dev, drmm_simple_encoder_cleanup, encoder);
+} +EXPORT_SYMBOL(drmm_simple_encoder_init);
- void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size, size_t offset, int encoder_type) {
diff --git a/include/drm/drm_simple_kms_helper.h b/include/drm/drm_simple_kms_helper.h index 0b3647e614dd..20456f4712f0 100644 --- a/include/drm/drm_simple_kms_helper.h +++ b/include/drm/drm_simple_kms_helper.h @@ -241,6 +241,9 @@ int drm_simple_display_pipe_init(struct drm_device *dev, int drm_simple_encoder_init(struct drm_device *dev, struct drm_encoder *encoder, int encoder_type); +int drmm_simple_encoder_init(struct drm_device *dev,
struct drm_encoder *encoder,
int encoder_type);
void *__drmm_simple_encoder_alloc(struct drm_device *dev, size_t size, size_t offset, int encoder_type);
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Maxime
Hi
Am 20.06.22 um 15:48 schrieb Maxime Ripard:
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
The simple-encoder stuff is an interface that no one really needs. Compared to open-coding the function, it's barely an improvement in LOCs, but nothing else.
Back when I added drm_simple_encoder_init(), I wanted to simplify the many drivers that initialized the encoder with a cleanup callback and nothing else. IIRC it was an improvement back then. But now we already have a related drmm_ helper and a drmm_alloc_ helper. If I had only added the macro back then, we could use the regular encoder helpers.
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Not necessary. It's not super important.
Best regards Thomas
Maxime
On Mon, Jun 20, 2022 at 04:25:38PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 15:48 schrieb Maxime Ripard:
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
The simple-encoder stuff is an interface that no one really needs. Compared to open-coding the function, it's barely an improvement in LOCs, but nothing else.
Back when I added drm_simple_encoder_init(), I wanted to simplify the many drivers that initialized the encoder with a cleanup callback and nothing else. IIRC it was an improvement back then. But now we already have a related drmm_ helper and a drmm_alloc_ helper. If I had only added the macro back then, we could use the regular encoder helpers.
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Not necessary. It's not super important.
The corollary is though :)
If I understand you right, it means that you'd rather have a destroy callback everywhere instead of calling the _cleanup function through a drm-managed callback, and let drm_dev_unregister do its job?
If so, it means that we shouldn't be following the drmm_.*_alloc functions and should drop all the new ones from this series.
Maxime
Hi
Am 20.06.22 um 16:39 schrieb Maxime Ripard:
On Mon, Jun 20, 2022 at 04:25:38PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 15:48 schrieb Maxime Ripard:
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
The simple-encoder stuff is an interface that no one really needs. Compared to open-coding the function, it's barely an improvement in LOCs, but nothing else.
Back when I added drm_simple_encoder_init(), I wanted to simplify the many drivers that initialized the encoder with a cleanup callback and nothing else. IIRC it was an improvement back then. But now we already have a related drmm_ helper and a drmm_alloc_ helper. If I had only added the macro back then, we could use the regular encoder helpers.
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Not necessary. It's not super important.
The corollary is though :)
If I understand you right, it means that you'd rather have a destroy callback everywhere instead of calling the _cleanup function through a drm-managed callback, and let drm_dev_unregister do its job?
If so, it means that we shouldn't be following the drmm_.*_alloc functions and should drop all the new ones from this series.
No, no. What I'm saying is that simple-encoder is a pointless mid-layer. There's nothing that couldn't easily be achieved with the regular encoder functions.
Best regards Thomas
Maxime
Hi
Am 20.06.22 um 16:45 schrieb Thomas Zimmermann:
Hi
Am 20.06.22 um 16:39 schrieb Maxime Ripard:
On Mon, Jun 20, 2022 at 04:25:38PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 15:48 schrieb Maxime Ripard:
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The DRM-managed function to register an encoder is drmm_simple_encoder_alloc() and its variants, which will allocate the underlying structure and initialisation the encoder.
However, we might want to separate the structure creation and the encoder initialisation, for example if the structure is shared across multiple DRM entities, for example an encoder and a connector.
Let's create an helper to only initialise an encoder that would be passed as an argument.
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
The simple-encoder stuff is an interface that no one really needs. Compared to open-coding the function, it's barely an improvement in LOCs, but nothing else.
Back when I added drm_simple_encoder_init(), I wanted to simplify the many drivers that initialized the encoder with a cleanup callback and nothing else. IIRC it was an improvement back then. But now we already have a related drmm_ helper and a drmm_alloc_ helper. If I had only added the macro back then, we could use the regular encoder helpers.
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ Â Â Â .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Not necessary. It's not super important.
The corollary is though :)
If I understand you right, it means that you'd rather have a destroy callback everywhere instead of calling the _cleanup function through a drm-managed callback, and let drm_dev_unregister do its job?
If so, it means that we shouldn't be following the drmm_.*_alloc functions and should drop all the new ones from this series.
No, no. What I'm saying is that simple-encoder is a pointless mid-layer. There's nothing that couldn't easily be achieved with the regular encoder functions.
I guess that if you want to change something here, you could add drmm_encoder_init() instead and convert vc4. That function might be more useful for other drivers in the long run.
Best regards Thomas
Best regards Thomas
Maxime
On Mon, Jun 20, 2022 at 09:04:11PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 16:45 schrieb Thomas Zimmermann:
Hi
Am 20.06.22 um 16:39 schrieb Maxime Ripard:
On Mon, Jun 20, 2022 at 04:25:38PM +0200, Thomas Zimmermann wrote:
Hi
Am 20.06.22 um 15:48 schrieb Maxime Ripard:
Hi,
On Mon, Jun 20, 2022 at 12:44:24PM +0200, Thomas Zimmermann wrote:
Am 10.06.22 um 11:28 schrieb Maxime Ripard: > The DRM-managed function to register an encoder is > drmm_simple_encoder_alloc() and its variants, which will allocate the > underlying structure and initialisation the encoder. > > However, we might want to separate the structure > creation and the encoder > initialisation, for example if the structure is > shared across multiple DRM > entities, for example an encoder and a connector. > > Let's create an helper to only initialise an encoder > that would be passed > as an argument. >
There's nothing wrong with this patch, but I have to admit that adding drm_simple_encoder_init() et al was a mistake.
Why do you think it was a mistake?
The simple-encoder stuff is an interface that no one really needs. Compared to open-coding the function, it's barely an improvement in LOCs, but nothing else.
Back when I added drm_simple_encoder_init(), I wanted to simplify the many drivers that initialized the encoder with a cleanup callback and nothing else. IIRC it was an improvement back then. But now we already have a related drmm_ helper and a drmm_alloc_ helper. If I had only added the macro back then, we could use the regular encoder helpers.
It would have been better to add an initializer macro like
#define DRM_STATIC_ENCODER_DEFAULT_FUNCS \ Â Â Â .destroy = drm_encoder_cleanup
It's way more flexible and similarly easy to use. Anyway...
We can still have this. It would simplify this series so I could definitely squeeze that patch in and add a TODO item / deprecation notice for simple encoders if you think it's needed
Not necessary. It's not super important.
The corollary is though :)
If I understand you right, it means that you'd rather have a destroy callback everywhere instead of calling the _cleanup function through a drm-managed callback, and let drm_dev_unregister do its job?
If so, it means that we shouldn't be following the drmm_.*_alloc functions and should drop all the new ones from this series.
No, no. What I'm saying is that simple-encoder is a pointless mid-layer. There's nothing that couldn't easily be achieved with the regular encoder functions.
I guess that if you want to change something here, you could add drmm_encoder_init() instead and convert vc4. That function might be more useful for other drivers in the long run.
I already had that patch in the series. I've dropped this one and converted everyone to a !simple encoder.
Thanks! maxime
Unlike what can be found for other entities, there's no DRM-managed function to create a panel_bridge instance from a panel.
Let's introduce one.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/bridge/panel.c | 39 ++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 2 ++ 2 files changed, 41 insertions(+)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 0ee563eb2b6f..07d720aa38c6 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -8,6 +8,7 @@ #include <drm/drm_bridge.h> #include <drm/drm_connector.h> #include <drm/drm_encoder.h> +#include <drm/drm_managed.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_of.h> #include <drm/drm_panel.h> @@ -333,6 +334,44 @@ struct drm_bridge *devm_drm_panel_bridge_add_typed(struct device *dev, } EXPORT_SYMBOL(devm_drm_panel_bridge_add_typed);
+static void drmm_drm_panel_bridge_release(struct drm_device *drm, void *ptr) +{ + struct drm_bridge *bridge = ptr; + + drm_panel_bridge_remove(bridge); +} + +/** + * drmm_panel_bridge_add - Creates a DRM-managed &drm_bridge and + * &drm_connector that just calls the + * appropriate functions from &drm_panel. + * + * @dev: DRM device to tie the bridge lifetime to + * @panel: The drm_panel being wrapped. Must be non-NULL. + * + * This is the DRM-managed version of drm_panel_bridge_add() which + * automatically calls drm_panel_bridge_remove() when @dev is cleaned + * up. + */ +struct drm_bridge *drmm_panel_bridge_add(struct drm_device *drm, + struct drm_panel *panel) +{ + struct drm_bridge *bridge; + int ret; + + bridge = drm_panel_bridge_add_typed(panel, panel->connector_type); + if (IS_ERR(bridge)) + return bridge; + + ret = drmm_add_action_or_reset(drm, drmm_drm_panel_bridge_release, + bridge); + if (ret) + return ERR_PTR(ret); + + return bridge; +} +EXPORT_SYMBOL(drmm_panel_bridge_add); + /** * drm_panel_bridge_connector - return the connector for the panel bridge * @bridge: The drm_bridge. diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 42aec8612f37..8100a15dd9c2 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -927,6 +927,8 @@ struct drm_bridge *devm_drm_panel_bridge_add(struct device *dev, struct drm_bridge *devm_drm_panel_bridge_add_typed(struct device *dev, struct drm_panel *panel, u32 connector_type); +struct drm_bridge *drmm_panel_bridge_add(struct drm_device *drm, + struct drm_panel *panel); struct drm_connector *drm_panel_bridge_connector(struct drm_bridge *bridge); #endif
Unlike what can be found for other DRM entities, we don't have a DRM-managed function equivalent to devm_drm_of_get_bridge().
Let's create it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/bridge/panel.c | 35 ++++++++++++++++++++++++++++++++++ include/drm/drm_bridge.h | 2 ++ 2 files changed, 37 insertions(+)
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c index 07d720aa38c6..0bf824ca1f25 100644 --- a/drivers/gpu/drm/bridge/panel.c +++ b/drivers/gpu/drm/bridge/panel.c @@ -425,4 +425,39 @@ struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, return bridge; } EXPORT_SYMBOL(devm_drm_of_get_bridge); + +/** + * drmm_of_get_bridge - Return next bridge in the chain + * @dev: device to tie the bridge lifetime to + * @np: device tree node containing encoder output ports + * @port: port in the device tree node + * @endpoint: endpoint in the device tree node + * + * Given a DT node's port and endpoint number, finds the connected node + * and returns the associated bridge if any, or creates and returns a + * drm panel bridge instance if a panel is connected. + * + * Returns a pointer to the bridge if successful, or an error pointer + * otherwise. + */ +struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, + struct device_node *np, + u32 port, u32 endpoint) +{ + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(np, port, endpoint, + &panel, &bridge); + if (ret) + return ERR_PTR(ret); + + if (panel) + bridge = drmm_panel_bridge_add(drm, panel); + + return bridge; +} +EXPORT_SYMBOL(drmm_of_get_bridge); + #endif diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 8100a15dd9c2..ddb92e745b2e 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -935,6 +935,8 @@ struct drm_connector *drm_panel_bridge_connector(struct drm_bridge *bridge); #if defined(CONFIG_OF) && defined(CONFIG_DRM_PANEL_BRIDGE) struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, struct device_node *node, u32 port, u32 endpoint); +struct drm_bridge *drmm_of_get_bridge(struct drm_device *drm, struct device_node *node, + u32 port, u32 endpoint); #else static inline struct drm_bridge *devm_drm_of_get_bridge(struct device *dev, struct device_node *node,
While we were using the component framework to deal with all the DRM subdevices, we were not calling component_unbind_all().
This leads to none of the subdevices freeing up their resources as part of their unbind() or device managed hooks.
Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.c | 14 ++++++++++++-- drivers/gpu/drm/vc4/vc4_drv.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 162bc18e7497..031f2cdd658d 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -209,6 +209,13 @@ static void vc4_match_add_drivers(struct device *dev, } }
+static void vc4_component_unbind_all(void *ptr) +{ + struct vc4_dev *vc4 = ptr; + + component_unbind_all(vc4->dev, &vc4->base); +} + static int vc4_drm_bind(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -230,6 +237,7 @@ static int vc4_drm_bind(struct device *dev) vc4 = devm_drm_dev_alloc(dev, &vc4_drm_driver, struct vc4_dev, base); if (IS_ERR(vc4)) return PTR_ERR(vc4); + vc4->dev = dev;
drm = &vc4->base; platform_set_drvdata(pdev, drm); @@ -276,6 +284,10 @@ static int vc4_drm_bind(struct device *dev) if (ret) return ret;
+ ret = devm_add_action_or_reset(dev, vc4_component_unbind_all, vc4); + if (ret) + return ret; + ret = vc4_plane_create_additional_planes(drm); if (ret) goto unbind_all; @@ -296,8 +308,6 @@ static int vc4_drm_bind(struct device *dev) return 0;
unbind_all: - component_unbind_all(dev, drm); - return ret; }
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 15e0c2ac3940..aa4c5910ea05 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -73,6 +73,7 @@ struct vc4_perfmon {
struct vc4_dev { struct drm_device base; + struct device *dev;
unsigned int irq;
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
While we were using the component framework to deal with all the DRM subdevices, we were not calling component_unbind_all().
This leads to none of the subdevices freeing up their resources as part of their unbind() or device managed hooks.
Fixes: c8b75bca92cb ("drm/vc4: Add KMS support for Raspberry Pi.") Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_drv.c | 14 ++++++++++++-- drivers/gpu/drm/vc4/vc4_drv.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 162bc18e7497..031f2cdd658d 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -209,6 +209,13 @@ static void vc4_match_add_drivers(struct device *dev, } }
+static void vc4_component_unbind_all(void *ptr) +{
struct vc4_dev *vc4 = ptr;
component_unbind_all(vc4->dev, &vc4->base);
+}
static int vc4_drm_bind(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -230,6 +237,7 @@ static int vc4_drm_bind(struct device *dev) vc4 = devm_drm_dev_alloc(dev, &vc4_drm_driver, struct vc4_dev, base); if (IS_ERR(vc4)) return PTR_ERR(vc4);
vc4->dev = dev; drm = &vc4->base; platform_set_drvdata(pdev, drm);
@@ -276,6 +284,10 @@ static int vc4_drm_bind(struct device *dev) if (ret) return ret;
ret = devm_add_action_or_reset(dev, vc4_component_unbind_all, vc4);
if (ret)
return ret;
ret = vc4_plane_create_additional_planes(drm); if (ret) goto unbind_all;
@@ -296,8 +308,6 @@ static int vc4_drm_bind(struct device *dev) return 0;
unbind_all:
component_unbind_all(dev, drm);
return ret;
}
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 15e0c2ac3940..aa4c5910ea05 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -73,6 +73,7 @@ struct vc4_perfmon {
struct vc4_dev { struct drm_device base;
struct device *dev; unsigned int irq;
-- 2.36.1
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HVS driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.h | 1 + drivers/gpu/drm/vc4/vc4_hvs.c | 106 +++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index aa4c5910ea05..080deae55f64 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -317,6 +317,7 @@ struct vc4_v3d { };
struct vc4_hvs { + struct drm_device *dev; struct platform_device *pdev; void __iomem *regs; u32 __iomem *dlist; diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2a58fc421cf6..483053e7b14f 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -25,6 +25,7 @@ #include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_vblank.h>
#include "vc4_drv.h" @@ -66,11 +67,15 @@ static const struct debugfs_reg32 hvs_regs[] = {
void vc4_hvs_dump_state(struct vc4_hvs *hvs) { + struct drm_device *drm = hvs->dev; struct drm_printer p = drm_info_printer(&hvs->pdev->dev); - int i; + int idx, i;
drm_print_regset32(&p, &hvs->regset);
+ if (!drm_dev_enter(drm, &idx)) + return; + DRM_INFO("HVS ctx:\n"); for (i = 0; i < 64; i += 4) { DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n", @@ -80,6 +85,8 @@ void vc4_hvs_dump_state(struct vc4_hvs *hvs) readl((u32 __iomem *)hvs->dlist + i + 2), readl((u32 __iomem *)hvs->dlist + i + 3)); } + + drm_dev_exit(idx); }
static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) @@ -132,14 +139,18 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) { - int ret, i; + struct drm_device *drm = hvs->dev; + int idx, ret, i; u32 __iomem *dst_kernel;
+ if (!drm_dev_enter(drm, &idx)) + return -ENODEV; + ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); if (ret) { DRM_ERROR("Failed to allocate space for filter kernel: %d\n", ret); - return ret; + goto err_dev_exit; }
dst_kernel = hvs->dlist + space->start; @@ -153,16 +164,26 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, } }
+ drm_dev_exit(idx); return 0; + +err_dev_exit: + drm_dev_exit(idx); + return ret; }
static void vc4_hvs_lut_load(struct vc4_hvs *hvs, struct vc4_crtc *vc4_crtc) { + struct drm_device *drm = hvs->dev; struct drm_crtc *crtc = &vc4_crtc->base; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); + int idx; u32 i;
+ if (!drm_dev_enter(drm, &idx)) + return; + /* The LUT memory is laid out with each HVS channel in order, * each of which takes 256 writes for R, 256 for G, then 256 * for B. @@ -177,6 +198,8 @@ static void vc4_hvs_lut_load(struct vc4_hvs *hvs, HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]); for (i = 0; i < crtc->gamma_size; i++) HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]); + + drm_dev_exit(idx); }
static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, @@ -198,7 +221,12 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) { + struct drm_device *drm = hvs->dev; u8 field = 0; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return 0;
switch (fifo) { case 0: @@ -215,6 +243,7 @@ u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) break; }
+ drm_dev_exit(idx); return field; }
@@ -226,6 +255,12 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) if (!hvs->hvs5) return output;
+ /* + * NOTE: We should probably use drm_dev_enter()/drm_dev_exit() + * here, but this function is only used during the DRM device + * initialization, so we should be fine. + */ + switch (output) { case 0: return 0; @@ -273,12 +308,17 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, struct drm_display_mode *mode, bool oneshot) { + struct drm_device *drm = hvs->dev; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); unsigned int chan = vc4_crtc_state->assigned_channel; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; u32 dispbkgndx; u32 dispctrl; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV;
HVS_WRITE(SCALER_DISPCTRLX(chan), 0); HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET); @@ -320,13 +360,21 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, */ vc4_hvs_lut_load(hvs, vc4_crtc);
+ drm_dev_exit(idx); + return 0; }
void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) { + struct drm_device *drm = hvs->dev; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE) - return; + goto out;
HVS_WRITE(SCALER_DISPCTRLX(chan), HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET); @@ -343,6 +391,9 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != SCALER_DISPSTATX_EMPTY); + +out: + drm_dev_exit(idx); }
int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -384,9 +435,15 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state); + int idx; + + if (!drm_dev_enter(dev, &idx)) + return;
HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), vc4_state->mm.start); + + drm_dev_exit(idx); }
static void vc4_hvs_update_dlist(struct drm_crtc *crtc) @@ -471,6 +528,10 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, bool enable_bg_fill = false; u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; u32 __iomem *dlist_next = dlist_start; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return;
if (debug_dump_regs) { DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc)); @@ -541,26 +602,44 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc)); vc4_hvs_dump_state(hvs); } + + drm_dev_exit(idx); }
void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) { - u32 dispctrl = HVS_READ(SCALER_DISPCTRL); + struct drm_device *drm = hvs->dev; + u32 dispctrl; + int idx;
+ if (!drm_dev_enter(drm, &idx)) + return; + + dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel);
HVS_WRITE(SCALER_DISPCTRL, dispctrl); + + drm_dev_exit(idx); }
void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) { - u32 dispctrl = HVS_READ(SCALER_DISPCTRL); + struct drm_device *drm = hvs->dev; + u32 dispctrl; + int idx;
+ if (!drm_dev_enter(drm, &idx)) + return; + + dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel);
HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EUFLOW(channel)); HVS_WRITE(SCALER_DISPCTRL, dispctrl); + + drm_dev_exit(idx); }
static void vc4_hvs_report_underrun(struct drm_device *dev) @@ -581,6 +660,17 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) u32 control; u32 status;
+ /* + * NOTE: We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + status = HVS_READ(SCALER_DISPSTAT); control = HVS_READ(SCALER_DISPCTRL);
@@ -613,10 +703,10 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) u32 dispctrl; u32 reg;
- hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL); + hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); if (!hvs) return -ENOMEM; - + hvs->dev = drm; hvs->pdev = pdev;
if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
Hi Maxime
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HVS driver.
The framework appears to rely on the remove function calling drm_dev_unplug instead of drm_dev_unregister, but I haven't seen a patch that makes that change in the vc4 driver. Have I missed it, or is there some other route to set the unplugged flag that drm_dev_enter/exit are relying on?
Dave
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_drv.h | 1 + drivers/gpu/drm/vc4/vc4_hvs.c | 106 +++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index aa4c5910ea05..080deae55f64 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -317,6 +317,7 @@ struct vc4_v3d { };
struct vc4_hvs {
struct drm_device *dev; struct platform_device *pdev; void __iomem *regs; u32 __iomem *dlist;
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2a58fc421cf6..483053e7b14f 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -25,6 +25,7 @@ #include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_vblank.h>
#include "vc4_drv.h" @@ -66,11 +67,15 @@ static const struct debugfs_reg32 hvs_regs[] = {
void vc4_hvs_dump_state(struct vc4_hvs *hvs) {
struct drm_device *drm = hvs->dev; struct drm_printer p = drm_info_printer(&hvs->pdev->dev);
int i;
int idx, i; drm_print_regset32(&p, &hvs->regset);
if (!drm_dev_enter(drm, &idx))
return;
DRM_INFO("HVS ctx:\n"); for (i = 0; i < 64; i += 4) { DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
@@ -80,6 +85,8 @@ void vc4_hvs_dump_state(struct vc4_hvs *hvs) readl((u32 __iomem *)hvs->dlist + i + 2), readl((u32 __iomem *)hvs->dlist + i + 3)); }
drm_dev_exit(idx);
}
static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) @@ -132,14 +139,18 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) {
int ret, i;
struct drm_device *drm = hvs->dev;
int idx, ret, i; u32 __iomem *dst_kernel;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); if (ret) { DRM_ERROR("Failed to allocate space for filter kernel: %d\n", ret);
return ret;
goto err_dev_exit; } dst_kernel = hvs->dlist + space->start;
@@ -153,16 +164,26 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, } }
drm_dev_exit(idx); return 0;
+err_dev_exit:
drm_dev_exit(idx);
return ret;
}
static void vc4_hvs_lut_load(struct vc4_hvs *hvs, struct vc4_crtc *vc4_crtc) {
struct drm_device *drm = hvs->dev; struct drm_crtc *crtc = &vc4_crtc->base; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx; u32 i;
if (!drm_dev_enter(drm, &idx))
return;
/* The LUT memory is laid out with each HVS channel in order, * each of which takes 256 writes for R, 256 for G, then 256 * for B.
@@ -177,6 +198,8 @@ static void vc4_hvs_lut_load(struct vc4_hvs *hvs, HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]); for (i = 0; i < crtc->gamma_size; i++) HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
drm_dev_exit(idx);
}
static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, @@ -198,7 +221,12 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) {
struct drm_device *drm = hvs->dev; u8 field = 0;
int idx;
if (!drm_dev_enter(drm, &idx))
return 0; switch (fifo) { case 0:
@@ -215,6 +243,7 @@ u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) break; }
drm_dev_exit(idx); return field;
}
@@ -226,6 +255,12 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) if (!hvs->hvs5) return output;
/*
* NOTE: We should probably use drm_dev_enter()/drm_dev_exit()
* here, but this function is only used during the DRM device
* initialization, so we should be fine.
*/
switch (output) { case 0: return 0;
@@ -273,12 +308,17 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, struct drm_display_mode *mode, bool oneshot) {
struct drm_device *drm = hvs->dev; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); unsigned int chan = vc4_crtc_state->assigned_channel; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; u32 dispbkgndx; u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV; HVS_WRITE(SCALER_DISPCTRLX(chan), 0); HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
@@ -320,13 +360,21 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, */ vc4_hvs_lut_load(hvs, vc4_crtc);
drm_dev_exit(idx);
return 0;
}
void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) {
struct drm_device *drm = hvs->dev;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
return;
goto out; HVS_WRITE(SCALER_DISPCTRLX(chan), HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET);
@@ -343,6 +391,9 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != SCALER_DISPSTATX_EMPTY);
+out:
drm_dev_exit(idx);
}
int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -384,9 +435,15 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx;
if (!drm_dev_enter(dev, &idx))
return; HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), vc4_state->mm.start);
drm_dev_exit(idx);
}
static void vc4_hvs_update_dlist(struct drm_crtc *crtc) @@ -471,6 +528,10 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, bool enable_bg_fill = false; u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; u32 __iomem *dlist_next = dlist_start;
int idx;
if (!drm_dev_enter(dev, &idx))
return; if (debug_dump_regs) { DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
@@ -541,26 +602,44 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc)); vc4_hvs_dump_state(hvs); }
drm_dev_exit(idx);
}
void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) {
u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel); HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx);
}
void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) {
u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel); HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EUFLOW(channel)); HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx);
}
static void vc4_hvs_report_underrun(struct drm_device *dev) @@ -581,6 +660,17 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) u32 control; u32 status;
/*
* NOTE: We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
status = HVS_READ(SCALER_DISPSTAT); control = HVS_READ(SCALER_DISPCTRL);
@@ -613,10 +703,10 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) u32 dispctrl; u32 reg;
hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL);
hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); if (!hvs) return -ENOMEM;
hvs->dev = drm; hvs->pdev = pdev; if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
-- 2.36.1
On Tue, 14 Jun 2022 at 16:11, Dave Stevenson dave.stevenson@raspberrypi.com wrote:
Hi Maxime
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HVS driver.
The framework appears to rely on the remove function calling drm_dev_unplug instead of drm_dev_unregister, but I haven't seen a patch that makes that change in the vc4 driver. Have I missed it, or is there some other route to set the unplugged flag that drm_dev_enter/exit are relying on?
Dave
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_drv.h | 1 + drivers/gpu/drm/vc4/vc4_hvs.c | 106 +++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index aa4c5910ea05..080deae55f64 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -317,6 +317,7 @@ struct vc4_v3d { };
struct vc4_hvs {
struct drm_device *dev; struct platform_device *pdev; void __iomem *regs; u32 __iomem *dlist;
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2a58fc421cf6..483053e7b14f 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -25,6 +25,7 @@ #include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_vblank.h>
#include "vc4_drv.h" @@ -66,11 +67,15 @@ static const struct debugfs_reg32 hvs_regs[] = {
void vc4_hvs_dump_state(struct vc4_hvs *hvs) {
struct drm_device *drm = hvs->dev; struct drm_printer p = drm_info_printer(&hvs->pdev->dev);
int i;
int idx, i; drm_print_regset32(&p, &hvs->regset);
if (!drm_dev_enter(drm, &idx))
return;
DRM_INFO("HVS ctx:\n"); for (i = 0; i < 64; i += 4) { DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
@@ -80,6 +85,8 @@ void vc4_hvs_dump_state(struct vc4_hvs *hvs) readl((u32 __iomem *)hvs->dlist + i + 2), readl((u32 __iomem *)hvs->dlist + i + 3)); }
drm_dev_exit(idx);
}
static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) @@ -132,14 +139,18 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) {
int ret, i;
struct drm_device *drm = hvs->dev;
int idx, ret, i; u32 __iomem *dst_kernel;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
vc4_hvs_upload_linear_kernel is only called from vc4_hvs_bind, so unless bind and unbind calls can be concurrent, then there's no need for protection here.
ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); if (ret) { DRM_ERROR("Failed to allocate space for filter kernel: %d\n", ret);
return ret;
goto err_dev_exit; } dst_kernel = hvs->dlist + space->start;
@@ -153,16 +164,26 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, } }
drm_dev_exit(idx); return 0;
+err_dev_exit:
drm_dev_exit(idx);
return ret;
}
static void vc4_hvs_lut_load(struct vc4_hvs *hvs, struct vc4_crtc *vc4_crtc) {
struct drm_device *drm = hvs->dev; struct drm_crtc *crtc = &vc4_crtc->base; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx; u32 i;
if (!drm_dev_enter(drm, &idx))
return;
/* The LUT memory is laid out with each HVS channel in order, * each of which takes 256 writes for R, 256 for G, then 256 * for B.
@@ -177,6 +198,8 @@ static void vc4_hvs_lut_load(struct vc4_hvs *hvs, HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]); for (i = 0; i < crtc->gamma_size; i++) HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
drm_dev_exit(idx);
}
static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs, @@ -198,7 +221,12 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) {
struct drm_device *drm = hvs->dev; u8 field = 0;
int idx;
if (!drm_dev_enter(drm, &idx))
return 0; switch (fifo) { case 0:
@@ -215,6 +243,7 @@ u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) break; }
drm_dev_exit(idx); return field;
}
@@ -226,6 +255,12 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) if (!hvs->hvs5) return output;
/*
* NOTE: We should probably use drm_dev_enter()/drm_dev_exit()
* here, but this function is only used during the DRM device
* initialization, so we should be fine.
*/
switch (output) { case 0: return 0;
@@ -273,12 +308,17 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, struct drm_display_mode *mode, bool oneshot) {
struct drm_device *drm = hvs->dev; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); unsigned int chan = vc4_crtc_state->assigned_channel; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; u32 dispbkgndx; u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV; HVS_WRITE(SCALER_DISPCTRLX(chan), 0); HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
@@ -320,13 +360,21 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, */ vc4_hvs_lut_load(hvs, vc4_crtc);
drm_dev_exit(idx);
return 0;
}
void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) {
struct drm_device *drm = hvs->dev;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
return;
goto out; HVS_WRITE(SCALER_DISPCTRLX(chan), HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET);
@@ -343,6 +391,9 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != SCALER_DISPSTATX_EMPTY);
+out:
drm_dev_exit(idx);
}
int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -384,9 +435,15 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx;
if (!drm_dev_enter(dev, &idx))
return; HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), vc4_state->mm.start);
drm_dev_exit(idx);
}
static void vc4_hvs_update_dlist(struct drm_crtc *crtc) @@ -471,6 +528,10 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, bool enable_bg_fill = false; u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; u32 __iomem *dlist_next = dlist_start;
int idx;
if (!drm_dev_enter(dev, &idx))
return; if (debug_dump_regs) { DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
@@ -541,26 +602,44 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc)); vc4_hvs_dump_state(hvs); }
drm_dev_exit(idx);
}
void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) {
u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel); HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx);
}
void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) {
u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel); HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EUFLOW(channel)); HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx);
}
static void vc4_hvs_report_underrun(struct drm_device *dev) @@ -581,6 +660,17 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) u32 control; u32 status;
/*
* NOTE: We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
status = HVS_READ(SCALER_DISPSTAT); control = HVS_READ(SCALER_DISPCTRL);
@@ -613,10 +703,10 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) u32 dispctrl; u32 reg;
hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL);
hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); if (!hvs) return -ENOMEM;
hvs->dev = drm; hvs->pdev = pdev; if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
-- 2.36.1
Hi,
On Tue, Jun 14, 2022 at 05:59:15PM +0100, Dave Stevenson wrote:
@@ -132,14 +139,18 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) {
int ret, i;
struct drm_device *drm = hvs->dev;
int idx, ret, i; u32 __iomem *dst_kernel;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
vc4_hvs_upload_linear_kernel is only called from vc4_hvs_bind, so unless bind and unbind calls can be concurrent, then there's no need for protection here.
Indeed. I've removed those changes and added a comment
Thanks! Maxime
Hi Dave,
On Tue, Jun 14, 2022 at 04:11:20PM +0100, Dave Stevenson wrote:
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HVS driver.
The framework appears to rely on the remove function calling drm_dev_unplug instead of drm_dev_unregister, but I haven't seen a patch that makes that change in the vc4 driver. Have I missed it, or is there some other route to set the unplugged flag that drm_dev_enter/exit are relying on?
You're right, we should have called drm_dev_unplug. I fixed it up (and the fallouts)
Thanks! Maxime
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HVS driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/vc4/vc4_drv.h | 1 + drivers/gpu/drm/vc4/vc4_hvs.c | 106 +++++++++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index aa4c5910ea05..080deae55f64 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -317,6 +317,7 @@ struct vc4_v3d { };
struct vc4_hvs {
- struct drm_device *dev; struct platform_device *pdev; void __iomem *regs; u32 __iomem *dlist;
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2a58fc421cf6..483053e7b14f 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -25,6 +25,7 @@ #include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_vblank.h>
#include "vc4_drv.h" @@ -66,11 +67,15 @@ static const struct debugfs_reg32 hvs_regs[] = {
void vc4_hvs_dump_state(struct vc4_hvs *hvs) {
- struct drm_device *drm = hvs->dev; struct drm_printer p = drm_info_printer(&hvs->pdev->dev);
- int i;
int idx, i;
drm_print_regset32(&p, &hvs->regset);
if (!drm_dev_enter(drm, &idx))
return;
DRM_INFO("HVS ctx:\n"); for (i = 0; i < 64; i += 4) { DRM_INFO("0x%08x (%s): 0x%08x 0x%08x 0x%08x 0x%08x\n",
@@ -80,6 +85,8 @@ void vc4_hvs_dump_state(struct vc4_hvs *hvs) readl((u32 __iomem *)hvs->dlist + i + 2), readl((u32 __iomem *)hvs->dlist + i + 3)); }
drm_dev_exit(idx); }
static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data)
@@ -132,14 +139,18 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, struct drm_mm_node *space, const u32 *kernel) {
- int ret, i;
struct drm_device *drm = hvs->dev;
int idx, ret, i; u32 __iomem *dst_kernel;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
ret = drm_mm_insert_node(&hvs->dlist_mm, space, VC4_KERNEL_DWORDS); if (ret) { DRM_ERROR("Failed to allocate space for filter kernel: %d\n", ret);
return ret;
goto err_dev_exit;
}
dst_kernel = hvs->dlist + space->start;
@@ -153,16 +164,26 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs, } }
- drm_dev_exit(idx); return 0;
+err_dev_exit:
drm_dev_exit(idx);
return ret; }
static void vc4_hvs_lut_load(struct vc4_hvs *hvs, struct vc4_crtc *vc4_crtc) {
struct drm_device *drm = hvs->dev; struct drm_crtc *crtc = &vc4_crtc->base; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx; u32 i;
if (!drm_dev_enter(drm, &idx))
return;
/* The LUT memory is laid out with each HVS channel in order,
- each of which takes 256 writes for R, 256 for G, then 256
- for B.
@@ -177,6 +198,8 @@ static void vc4_hvs_lut_load(struct vc4_hvs *hvs, HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_g[i]); for (i = 0; i < crtc->gamma_size; i++) HVS_WRITE(SCALER_GAMDATA, vc4_crtc->lut_b[i]);
drm_dev_exit(idx); }
static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
@@ -198,7 +221,12 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) {
struct drm_device *drm = hvs->dev; u8 field = 0;
int idx;
if (!drm_dev_enter(drm, &idx))
return 0;
switch (fifo) { case 0:
@@ -215,6 +243,7 @@ u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo) break; }
- drm_dev_exit(idx); return field; }
@@ -226,6 +255,12 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) if (!hvs->hvs5) return output;
- /*
* NOTE: We should probably use drm_dev_enter()/drm_dev_exit()
* here, but this function is only used during the DRM device
* initialization, so we should be fine.
*/
- switch (output) { case 0: return 0;
@@ -273,12 +308,17 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output) static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, struct drm_display_mode *mode, bool oneshot) {
struct drm_device *drm = hvs->dev; struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state); unsigned int chan = vc4_crtc_state->assigned_channel; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; u32 dispbkgndx; u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
HVS_WRITE(SCALER_DISPCTRLX(chan), 0); HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
@@ -320,13 +360,21 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc, */ vc4_hvs_lut_load(hvs, vc4_crtc);
drm_dev_exit(idx);
return 0; }
void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) {
struct drm_device *drm = hvs->dev;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
if (HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE)
return;
goto out;
HVS_WRITE(SCALER_DISPCTRLX(chan), HVS_READ(SCALER_DISPCTRLX(chan)) | SCALER_DISPCTRLX_RESET);
@@ -343,6 +391,9 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan) WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) & (SCALER_DISPSTATX_FULL | SCALER_DISPSTATX_EMPTY)) != SCALER_DISPSTATX_EMPTY);
+out:
drm_dev_exit(idx); }
int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
@@ -384,9 +435,15 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc) struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_hvs *hvs = vc4->hvs; struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
int idx;
if (!drm_dev_enter(dev, &idx))
return;
HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel), vc4_state->mm.start);
drm_dev_exit(idx); }
static void vc4_hvs_update_dlist(struct drm_crtc *crtc)
@@ -471,6 +528,10 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, bool enable_bg_fill = false; u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start; u32 __iomem *dlist_next = dlist_start;
int idx;
if (!drm_dev_enter(dev, &idx))
return;
if (debug_dump_regs) { DRM_INFO("CRTC %d HVS before:\n", drm_crtc_index(crtc));
@@ -541,26 +602,44 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, DRM_INFO("CRTC %d HVS after:\n", drm_crtc_index(crtc)); vc4_hvs_dump_state(hvs); }
drm_dev_exit(idx); }
void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel) {
- u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl &= ~SCALER_DISPCTRL_DSPEISLUR(channel);
HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx); }
void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel) {
- u32 dispctrl = HVS_READ(SCALER_DISPCTRL);
struct drm_device *drm = hvs->dev;
u32 dispctrl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
dispctrl = HVS_READ(SCALER_DISPCTRL); dispctrl |= SCALER_DISPCTRL_DSPEISLUR(channel);
HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EUFLOW(channel)); HVS_WRITE(SCALER_DISPCTRL, dispctrl);
drm_dev_exit(idx); }
static void vc4_hvs_report_underrun(struct drm_device *dev)
@@ -581,6 +660,17 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) u32 control; u32 status;
- /*
* NOTE: We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
- status = HVS_READ(SCALER_DISPSTAT); control = HVS_READ(SCALER_DISPCTRL);
@@ -613,10 +703,10 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) u32 dispctrl; u32 reg;
- hvs = devm_kzalloc(&pdev->dev, sizeof(*hvs), GFP_KERNEL);
- hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL); if (!hvs) return -ENOMEM;
hvs->dev = drm; hvs->pdev = pdev;
if (of_device_is_compatible(pdev->dev.of_node, "brcm,bcm2711-hvs"))
When the HVS driver is unbound, a lot of memory allocations in the LBM and DLIST RAM are still assigned to planes that are still allocated.
Thus, we hit a warning when calling drm_mm_takedown() since the memory pool is not completely free of allocations.
Let's free all the currently live entries before calling drm_mm_takedown().
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hvs.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 483053e7b14f..b0906bb96c32 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -834,11 +834,18 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_hvs *hvs = vc4->hvs; + struct drm_mm_node *node, *next;
if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);
+ drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm) + drm_mm_remove_node(node); + drm_mm_takedown(&vc4->hvs->dlist_mm); + + drm_mm_for_each_node_safe(node, next, &vc4->hvs->lbm_mm) + drm_mm_remove_node(node); drm_mm_takedown(&vc4->hvs->lbm_mm);
clk_disable_unprepare(hvs->core_clk);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
When the HVS driver is unbound, a lot of memory allocations in the LBM and DLIST RAM are still assigned to planes that are still allocated.
Thus, we hit a warning when calling drm_mm_takedown() since the memory pool is not completely free of allocations.
Let's free all the currently live entries before calling drm_mm_takedown().
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_hvs.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 483053e7b14f..b0906bb96c32 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -834,11 +834,18 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master, struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_hvs *hvs = vc4->hvs;
struct drm_mm_node *node, *next; if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter)) drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);
drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm)
drm_mm_remove_node(node);
drm_mm_takedown(&vc4->hvs->dlist_mm);
drm_mm_for_each_node_safe(node, next, &vc4->hvs->lbm_mm)
drm_mm_remove_node(node); drm_mm_takedown(&vc4->hvs->lbm_mm); clk_disable_unprepare(hvs->core_clk);
-- 2.36.1
vc4_plane_init() currently initialises the plane with no possible CRTCs, and will expect the caller to set it up by itself.
Let's change that logic a bit to follow the syntax of drm_universal_plane_init() and pass the possible CRTCs bitmask as an argument to the function instead.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_crtc.c | 2 +- drivers/gpu/drm/vc4/vc4_drv.h | 3 ++- drivers/gpu/drm/vc4/vc4_plane.c | 15 +++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 59b20c8f132b..840a93484bb1 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1138,7 +1138,7 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, * requirement of the plane configuration, and reject ones * that will take too much. */ - primary_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_PRIMARY); + primary_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_PRIMARY, 0); if (IS_ERR(primary_plane)) { dev_err(drm->dev, "failed to construct primary plane\n"); return PTR_ERR(primary_plane); diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 080deae55f64..5125ca1a8158 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -952,7 +952,8 @@ int vc4_kms_load(struct drm_device *dev);
/* vc4_plane.c */ struct drm_plane *vc4_plane_init(struct drm_device *dev, - enum drm_plane_type type); + enum drm_plane_type type, + unsigned int possible_crtcs); int vc4_plane_create_additional_planes(struct drm_device *dev); u32 vc4_plane_write_dlist(struct drm_plane *plane, u32 __iomem *dlist); u32 vc4_plane_dlist_size(const struct drm_plane_state *state); diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index b3438f4a81ce..17dab470ecdf 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -1451,7 +1451,8 @@ static const struct drm_plane_funcs vc4_plane_funcs = { };
struct drm_plane *vc4_plane_init(struct drm_device *dev, - enum drm_plane_type type) + enum drm_plane_type type, + unsigned int possible_crtcs) { struct drm_plane *plane = NULL; struct vc4_plane *vc4_plane; @@ -1483,7 +1484,7 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, }
plane = &vc4_plane->base; - ret = drm_universal_plane_init(dev, plane, 0, + ret = drm_universal_plane_init(dev, plane, possible_crtcs, &vc4_plane_funcs, formats, num_formats, modifiers, type, NULL); @@ -1528,13 +1529,11 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) */ for (i = 0; i < 16; i++) { struct drm_plane *plane = - vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY); + vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY, + GENMASK(drm->mode_config.num_crtc - 1, 0));
if (IS_ERR(plane)) continue; - - plane->possible_crtcs = - GENMASK(drm->mode_config.num_crtc - 1, 0); }
drm_for_each_crtc(crtc, drm) { @@ -1542,9 +1541,9 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) * since we overlay planes on the CRTC in the order they were * initialized. */ - cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR); + cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR, + drm_crtc_mask(crtc)); if (!IS_ERR(cursor_plane)) { - cursor_plane->possible_crtcs = drm_crtc_mask(crtc); crtc->cursor = cursor_plane; } }
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
vc4_plane_init() currently initialises the plane with no possible CRTCs, and will expect the caller to set it up by itself.
Let's change that logic a bit to follow the syntax of drm_universal_plane_init() and pass the possible CRTCs bitmask as an argument to the function instead.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_crtc.c | 2 +- drivers/gpu/drm/vc4/vc4_drv.h | 3 ++- drivers/gpu/drm/vc4/vc4_plane.c | 15 +++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 59b20c8f132b..840a93484bb1 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1138,7 +1138,7 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, * requirement of the plane configuration, and reject ones * that will take too much. */
primary_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
primary_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_PRIMARY, 0); if (IS_ERR(primary_plane)) { dev_err(drm->dev, "failed to construct primary plane\n"); return PTR_ERR(primary_plane);
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 080deae55f64..5125ca1a8158 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -952,7 +952,8 @@ int vc4_kms_load(struct drm_device *dev);
/* vc4_plane.c */ struct drm_plane *vc4_plane_init(struct drm_device *dev,
enum drm_plane_type type);
enum drm_plane_type type,
unsigned int possible_crtcs);
A nit pick. possible_crtcs in struct drm_plane is a uint32_t , not an unsigned int. It would never matter on a Pi as unsigned int will never be 16bit, but avoids the oddity.
Otherwise: Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
int vc4_plane_create_additional_planes(struct drm_device *dev); u32 vc4_plane_write_dlist(struct drm_plane *plane, u32 __iomem *dlist); u32 vc4_plane_dlist_size(const struct drm_plane_state *state); diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index b3438f4a81ce..17dab470ecdf 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -1451,7 +1451,8 @@ static const struct drm_plane_funcs vc4_plane_funcs = { };
struct drm_plane *vc4_plane_init(struct drm_device *dev,
enum drm_plane_type type)
enum drm_plane_type type,
unsigned int possible_crtcs)
{ struct drm_plane *plane = NULL; struct vc4_plane *vc4_plane; @@ -1483,7 +1484,7 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, }
plane = &vc4_plane->base;
ret = drm_universal_plane_init(dev, plane, 0,
ret = drm_universal_plane_init(dev, plane, possible_crtcs, &vc4_plane_funcs, formats, num_formats, modifiers, type, NULL);
@@ -1528,13 +1529,11 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) */ for (i = 0; i < 16; i++) { struct drm_plane *plane =
vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY);
vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
GENMASK(drm->mode_config.num_crtc - 1, 0)); if (IS_ERR(plane)) continue;
plane->possible_crtcs =
GENMASK(drm->mode_config.num_crtc - 1, 0); } drm_for_each_crtc(crtc, drm) {
@@ -1542,9 +1541,9 @@ int vc4_plane_create_additional_planes(struct drm_device *drm) * since we overlay planes on the CRTC in the order they were * initialized. */
cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR);
cursor_plane = vc4_plane_init(drm, DRM_PLANE_TYPE_CURSOR,
drm_crtc_mask(crtc)); if (!IS_ERR(cursor_plane)) {
cursor_plane->possible_crtcs = drm_crtc_mask(crtc); crtc->cursor = cursor_plane; } }
-- 2.36.1
Let's switch to drmm_universal_plane_alloc() for our plane allocation and initialisation to make the driver a bit simpler.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_crtc.c | 12 +----------- drivers/gpu/drm/vc4/vc4_plane.c | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 840a93484bb1..7163f924b48b 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1176,7 +1176,6 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) const struct vc4_pv_data *pv_data; struct vc4_crtc *vc4_crtc; struct drm_crtc *crtc; - struct drm_plane *destroy_plane, *temp; int ret;
vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL); @@ -1211,7 +1210,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) IRQF_SHARED, "vc4 crtc", vc4_crtc); if (ret) - goto err_destroy_planes; + return ret;
platform_set_drvdata(pdev, vc4_crtc);
@@ -1219,15 +1218,6 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) &vc4_crtc->regset);
return 0; - -err_destroy_planes: - list_for_each_entry_safe(destroy_plane, temp, - &drm->mode_config.plane_list, head) { - if (destroy_plane->possible_crtcs == drm_crtc_mask(crtc)) - destroy_plane->funcs->destroy(destroy_plane); - } - - return ret; }
static void vc4_crtc_unbind(struct device *dev, struct device *master, diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 17dab470ecdf..673c963f5c5a 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -1442,8 +1442,6 @@ static bool vc4_format_mod_supported(struct drm_plane *plane, static const struct drm_plane_funcs vc4_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane, - .destroy = drm_plane_cleanup, - .set_property = NULL, .reset = vc4_plane_reset, .atomic_duplicate_state = vc4_plane_duplicate_state, .atomic_destroy_state = vc4_plane_destroy_state, @@ -1454,11 +1452,10 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, enum drm_plane_type type, unsigned int possible_crtcs) { - struct drm_plane *plane = NULL; + struct drm_plane *plane; struct vc4_plane *vc4_plane; u32 formats[ARRAY_SIZE(hvs_formats)]; int num_formats = 0; - int ret = 0; unsigned i; bool hvs5 = of_device_is_compatible(dev->dev->of_node, "brcm,bcm2711-vc5"); @@ -1471,11 +1468,6 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, DRM_FORMAT_MOD_INVALID };
- vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane), - GFP_KERNEL); - if (!vc4_plane) - return ERR_PTR(-ENOMEM); - for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) { if (!hvs_formats[i].hvs5_only || hvs5) { formats[num_formats] = hvs_formats[i].drm; @@ -1483,13 +1475,14 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, } }
+ vc4_plane = drmm_universal_plane_alloc(dev, struct vc4_plane, base, + possible_crtcs, + &vc4_plane_funcs, + formats, num_formats, + modifiers, type, NULL); + if (IS_ERR(vc4_plane)) + return ERR_CAST(vc4_plane); plane = &vc4_plane->base; - ret = drm_universal_plane_init(dev, plane, possible_crtcs, - &vc4_plane_funcs, - formats, num_formats, - modifiers, type, NULL); - if (ret) - return ERR_PTR(ret);
drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Let's switch to drmm_universal_plane_alloc() for our plane allocation and initialisation to make the driver a bit simpler.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_crtc.c | 12 +----------- drivers/gpu/drm/vc4/vc4_plane.c | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 840a93484bb1..7163f924b48b 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1176,7 +1176,6 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) const struct vc4_pv_data *pv_data; struct vc4_crtc *vc4_crtc; struct drm_crtc *crtc;
struct drm_plane *destroy_plane, *temp; int ret; vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
@@ -1211,7 +1210,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) IRQF_SHARED, "vc4 crtc", vc4_crtc); if (ret)
goto err_destroy_planes;
return ret; platform_set_drvdata(pdev, vc4_crtc);
@@ -1219,15 +1218,6 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) &vc4_crtc->regset);
return 0;
-err_destroy_planes:
list_for_each_entry_safe(destroy_plane, temp,
&drm->mode_config.plane_list, head) {
if (destroy_plane->possible_crtcs == drm_crtc_mask(crtc))
destroy_plane->funcs->destroy(destroy_plane);
}
return ret;
}
static void vc4_crtc_unbind(struct device *dev, struct device *master, diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 17dab470ecdf..673c963f5c5a 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -1442,8 +1442,6 @@ static bool vc4_format_mod_supported(struct drm_plane *plane, static const struct drm_plane_funcs vc4_plane_funcs = { .update_plane = drm_atomic_helper_update_plane, .disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.set_property = NULL, .reset = vc4_plane_reset, .atomic_duplicate_state = vc4_plane_duplicate_state, .atomic_destroy_state = vc4_plane_destroy_state,
@@ -1454,11 +1452,10 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, enum drm_plane_type type, unsigned int possible_crtcs) {
struct drm_plane *plane = NULL;
struct drm_plane *plane; struct vc4_plane *vc4_plane; u32 formats[ARRAY_SIZE(hvs_formats)]; int num_formats = 0;
int ret = 0; unsigned i; bool hvs5 = of_device_is_compatible(dev->dev->of_node, "brcm,bcm2711-vc5");
@@ -1471,11 +1468,6 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, DRM_FORMAT_MOD_INVALID };
vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane),
GFP_KERNEL);
if (!vc4_plane)
return ERR_PTR(-ENOMEM);
for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) { if (!hvs_formats[i].hvs5_only || hvs5) { formats[num_formats] = hvs_formats[i].drm;
@@ -1483,13 +1475,14 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, } }
vc4_plane = drmm_universal_plane_alloc(dev, struct vc4_plane, base,
possible_crtcs,
&vc4_plane_funcs,
formats, num_formats,
modifiers, type, NULL);
if (IS_ERR(vc4_plane))
return ERR_CAST(vc4_plane); plane = &vc4_plane->base;
ret = drm_universal_plane_init(dev, plane, possible_crtcs,
&vc4_plane_funcs,
formats, num_formats,
modifiers, type, NULL);
if (ret)
return ERR_PTR(ret); drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
-- 2.36.1
All the CRTCs, including the TXP, have a debugfs file and name so we can consolidate it into vc4_crtc_data.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_crtc.c | 18 +++++++++--------- drivers/gpu/drm/vc4/vc4_drv.h | 4 ++-- drivers/gpu/drm/vc4/vc4_txp.c | 1 + 3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 7163f924b48b..1f7e987e68aa 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -978,10 +978,10 @@ static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
static const struct vc4_pv_data bcm2835_pv0_data = { .base = { + .debugfs_name = "crtc0_regs", .hvs_available_channels = BIT(0), .hvs_output = 0, }, - .debugfs_name = "crtc0_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -992,10 +992,10 @@ static const struct vc4_pv_data bcm2835_pv0_data = {
static const struct vc4_pv_data bcm2835_pv1_data = { .base = { + .debugfs_name = "crtc1_regs", .hvs_available_channels = BIT(2), .hvs_output = 2, }, - .debugfs_name = "crtc1_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -1006,10 +1006,10 @@ static const struct vc4_pv_data bcm2835_pv1_data = {
static const struct vc4_pv_data bcm2835_pv2_data = { .base = { + .debugfs_name = "crtc2_regs", .hvs_available_channels = BIT(1), .hvs_output = 1, }, - .debugfs_name = "crtc2_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -1020,10 +1020,10 @@ static const struct vc4_pv_data bcm2835_pv2_data = {
static const struct vc4_pv_data bcm2711_pv0_data = { .base = { + .debugfs_name = "crtc0_regs", .hvs_available_channels = BIT(0), .hvs_output = 0, }, - .debugfs_name = "crtc0_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -1034,10 +1034,10 @@ static const struct vc4_pv_data bcm2711_pv0_data = {
static const struct vc4_pv_data bcm2711_pv1_data = { .base = { + .debugfs_name = "crtc1_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 3, }, - .debugfs_name = "crtc1_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -1048,10 +1048,10 @@ static const struct vc4_pv_data bcm2711_pv1_data = {
static const struct vc4_pv_data bcm2711_pv2_data = { .base = { + .debugfs_name = "crtc2_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 4, }, - .debugfs_name = "crtc2_regs", .fifo_depth = 256, .pixels_per_clock = 2, .encoder_types = { @@ -1061,10 +1061,10 @@ static const struct vc4_pv_data bcm2711_pv2_data = {
static const struct vc4_pv_data bcm2711_pv3_data = { .base = { + .debugfs_name = "crtc3_regs", .hvs_available_channels = BIT(1), .hvs_output = 1, }, - .debugfs_name = "crtc3_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = { @@ -1074,10 +1074,10 @@ static const struct vc4_pv_data bcm2711_pv3_data = {
static const struct vc4_pv_data bcm2711_pv4_data = { .base = { + .debugfs_name = "crtc4_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 5, }, - .debugfs_name = "crtc4_regs", .fifo_depth = 64, .pixels_per_clock = 2, .encoder_types = { @@ -1214,7 +1214,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
platform_set_drvdata(pdev, vc4_crtc);
- vc4_debugfs_add_regset32(drm, pv_data->debugfs_name, + vc4_debugfs_add_regset32(drm, pv_data->base.debugfs_name, &vc4_crtc->regset);
return 0; diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 5125ca1a8158..9a53ace85d95 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -457,6 +457,8 @@ to_vc4_encoder(struct drm_encoder *encoder) }
struct vc4_crtc_data { + const char *debugfs_name; + /* Bitmask of channels (FIFOs) of the HVS that the output can source from */ unsigned int hvs_available_channels;
@@ -474,8 +476,6 @@ struct vc4_pv_data { u8 pixels_per_clock;
enum vc4_encoder_type encoder_types[4]; - const char *debugfs_name; - };
struct vc4_crtc { diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 82beb8c159f2..e983ff7c5e13 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -460,6 +460,7 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data) }
static const struct vc4_crtc_data vc4_txp_crtc_data = { + .debugfs_name = "txp_regs", .hvs_available_channels = BIT(2), .hvs_output = 2, };
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
All the CRTCs, including the TXP, have a debugfs file and name so we can consolidate it into vc4_crtc_data.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
I was sort of expecting the vc4_debugfs_add_regset32 call to move to vc4_crtc_init so that it would be a common call for crtcs and txp, but that doesn't appear to happen in this set. The debugfs_name for the txp is therefore actually unused.
drivers/gpu/drm/vc4/vc4_crtc.c | 18 +++++++++--------- drivers/gpu/drm/vc4/vc4_drv.h | 4 ++-- drivers/gpu/drm/vc4/vc4_txp.c | 1 + 3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 7163f924b48b..1f7e987e68aa 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -978,10 +978,10 @@ static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
static const struct vc4_pv_data bcm2835_pv0_data = { .base = {
.debugfs_name = "crtc0_regs", .hvs_available_channels = BIT(0), .hvs_output = 0, },
.debugfs_name = "crtc0_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -992,10 +992,10 @@ static const struct vc4_pv_data bcm2835_pv0_data = {
static const struct vc4_pv_data bcm2835_pv1_data = { .base = {
.debugfs_name = "crtc1_regs", .hvs_available_channels = BIT(2), .hvs_output = 2, },
.debugfs_name = "crtc1_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -1006,10 +1006,10 @@ static const struct vc4_pv_data bcm2835_pv1_data = {
static const struct vc4_pv_data bcm2835_pv2_data = { .base = {
.debugfs_name = "crtc2_regs", .hvs_available_channels = BIT(1), .hvs_output = 1, },
.debugfs_name = "crtc2_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -1020,10 +1020,10 @@ static const struct vc4_pv_data bcm2835_pv2_data = {
static const struct vc4_pv_data bcm2711_pv0_data = { .base = {
.debugfs_name = "crtc0_regs", .hvs_available_channels = BIT(0), .hvs_output = 0, },
.debugfs_name = "crtc0_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -1034,10 +1034,10 @@ static const struct vc4_pv_data bcm2711_pv0_data = {
static const struct vc4_pv_data bcm2711_pv1_data = { .base = {
.debugfs_name = "crtc1_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 3, },
.debugfs_name = "crtc1_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -1048,10 +1048,10 @@ static const struct vc4_pv_data bcm2711_pv1_data = {
static const struct vc4_pv_data bcm2711_pv2_data = { .base = {
.debugfs_name = "crtc2_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 4, },
.debugfs_name = "crtc2_regs", .fifo_depth = 256, .pixels_per_clock = 2, .encoder_types = {
@@ -1061,10 +1061,10 @@ static const struct vc4_pv_data bcm2711_pv2_data = {
static const struct vc4_pv_data bcm2711_pv3_data = { .base = {
.debugfs_name = "crtc3_regs", .hvs_available_channels = BIT(1), .hvs_output = 1, },
.debugfs_name = "crtc3_regs", .fifo_depth = 64, .pixels_per_clock = 1, .encoder_types = {
@@ -1074,10 +1074,10 @@ static const struct vc4_pv_data bcm2711_pv3_data = {
static const struct vc4_pv_data bcm2711_pv4_data = { .base = {
.debugfs_name = "crtc4_regs", .hvs_available_channels = BIT(0) | BIT(1) | BIT(2), .hvs_output = 5, },
.debugfs_name = "crtc4_regs", .fifo_depth = 64, .pixels_per_clock = 2, .encoder_types = {
@@ -1214,7 +1214,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
platform_set_drvdata(pdev, vc4_crtc);
vc4_debugfs_add_regset32(drm, pv_data->debugfs_name,
vc4_debugfs_add_regset32(drm, pv_data->base.debugfs_name, &vc4_crtc->regset); return 0;
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 5125ca1a8158..9a53ace85d95 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -457,6 +457,8 @@ to_vc4_encoder(struct drm_encoder *encoder) }
struct vc4_crtc_data {
const char *debugfs_name;
/* Bitmask of channels (FIFOs) of the HVS that the output can source from */ unsigned int hvs_available_channels;
@@ -474,8 +476,6 @@ struct vc4_pv_data { u8 pixels_per_clock;
enum vc4_encoder_type encoder_types[4];
const char *debugfs_name;
};
struct vc4_crtc { diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 82beb8c159f2..e983ff7c5e13 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -460,6 +460,7 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data) }
static const struct vc4_crtc_data vc4_txp_crtc_data = {
.debugfs_name = "txp_regs", .hvs_available_channels = BIT(2), .hvs_output = 2,
};
2.36.1
Hi Dave,
On Tue, Jun 14, 2022 at 04:57:45PM +0100, Dave Stevenson wrote:
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
All the CRTCs, including the TXP, have a debugfs file and name so we can consolidate it into vc4_crtc_data.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
I was sort of expecting the vc4_debugfs_add_regset32 call to move to vc4_crtc_init so that it would be a common call for crtcs and txp, but that doesn't appear to happen in this set. The debugfs_name for the txp is therefore actually unused.
As of this patch, you're right
This is later changed by some other patch though: https://lore.kernel.org/all/20220610092924.754942-60-maxime@cerno.tech/
Maxime
On Thu, 16 Jun 2022 at 10:41, Maxime Ripard maxime@cerno.tech wrote:
Hi Dave,
On Tue, Jun 14, 2022 at 04:57:45PM +0100, Dave Stevenson wrote:
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
All the CRTCs, including the TXP, have a debugfs file and name so we can consolidate it into vc4_crtc_data.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
I was sort of expecting the vc4_debugfs_add_regset32 call to move to vc4_crtc_init so that it would be a common call for crtcs and txp, but that doesn't appear to happen in this set. The debugfs_name for the txp is therefore actually unused.
As of this patch, you're right
This is later changed by some other patch though: https://lore.kernel.org/all/20220610092924.754942-60-maxime@cerno.tech/
Indeed, and that cleans it all up. Perfect.
Dave
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 1f7e987e68aa..c74fa3d07561 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1178,7 +1178,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) struct drm_crtc *crtc; int ret;
- vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL); + vc4_crtc = drmm_kzalloc(drm, sizeof(*vc4_crtc), GFP_KERNEL); if (!vc4_crtc) return -ENOMEM; crtc = &vc4_crtc->base;
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_crtc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 1f7e987e68aa..c74fa3d07561 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -1178,7 +1178,7 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data) struct drm_crtc *crtc; int ret;
vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
vc4_crtc = drmm_kzalloc(drm, sizeof(*vc4_crtc), GFP_KERNEL); if (!vc4_crtc) return -ENOMEM; crtc = &vc4_crtc->base;
-- 2.36.1
The current code will call drm_crtc_cleanup() when the device is unbound. However, by then, there might still be some references held to that CRTC, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_crtc.c | 18 +++++++----------- drivers/gpu/drm/vc4/vc4_drv.h | 1 - drivers/gpu/drm/vc4/vc4_txp.c | 1 - 3 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index c74fa3d07561..24de4706b61a 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -205,11 +205,6 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, return ret; }
-void vc4_crtc_destroy(struct drm_crtc *crtc) -{ - drm_crtc_cleanup(crtc); -} - static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format) { const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc); @@ -953,7 +948,6 @@ void vc4_crtc_reset(struct drm_crtc *crtc)
static const struct drm_crtc_funcs vc4_crtc_funcs = { .set_config = drm_atomic_helper_set_config, - .destroy = vc4_crtc_destroy, .page_flip = vc4_page_flip, .set_property = NULL, .cursor_set = NULL, /* handled by drm_mode_cursor_universal */ @@ -1131,6 +1125,7 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, struct drm_crtc *crtc = &vc4_crtc->base; struct drm_plane *primary_plane; unsigned int i; + int ret;
/* For now, we create just the primary and the legacy cursor * planes. We should be able to stack more planes on easily, @@ -1144,10 +1139,13 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, return PTR_ERR(primary_plane); }
- spin_lock_init(&vc4_crtc->irq_lock); - drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, - crtc_funcs, NULL); + ret = drmm_crtc_init_with_planes(drm, crtc, primary_plane, NULL, + crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, crtc_helper_funcs); + spin_lock_init(&vc4_crtc->irq_lock);
if (!vc4->hvs->hvs5) { drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r)); @@ -1226,8 +1224,6 @@ static void vc4_crtc_unbind(struct device *dev, struct device *master, struct platform_device *pdev = to_platform_device(dev); struct vc4_crtc *vc4_crtc = dev_get_drvdata(dev);
- vc4_crtc_destroy(&vc4_crtc->base); - CRTC_WRITE(PV_INTEN, 0);
platform_set_drvdata(pdev, NULL); diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 9a53ace85d95..fff3772be2d4 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -845,7 +845,6 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc); int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, const struct drm_crtc_funcs *crtc_funcs, const struct drm_crtc_helper_funcs *crtc_helper_funcs); -void vc4_crtc_destroy(struct drm_crtc *crtc); int vc4_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index e983ff7c5e13..f306e05ac5b2 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -383,7 +383,6 @@ static void vc4_txp_disable_vblank(struct drm_crtc *crtc) {}
static const struct drm_crtc_funcs vc4_txp_crtc_funcs = { .set_config = drm_atomic_helper_set_config, - .destroy = vc4_crtc_destroy, .page_flip = vc4_page_flip, .reset = vc4_crtc_reset, .atomic_duplicate_state = vc4_crtc_duplicate_state,
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The current code will call drm_crtc_cleanup() when the device is unbound. However, by then, there might still be some references held to that CRTC, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_crtc.c | 18 +++++++----------- drivers/gpu/drm/vc4/vc4_drv.h | 1 - drivers/gpu/drm/vc4/vc4_txp.c | 1 - 3 files changed, 7 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index c74fa3d07561..24de4706b61a 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -205,11 +205,6 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc, return ret; }
-void vc4_crtc_destroy(struct drm_crtc *crtc) -{
drm_crtc_cleanup(crtc);
-}
static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format) { const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc); @@ -953,7 +948,6 @@ void vc4_crtc_reset(struct drm_crtc *crtc)
static const struct drm_crtc_funcs vc4_crtc_funcs = { .set_config = drm_atomic_helper_set_config,
.destroy = vc4_crtc_destroy, .page_flip = vc4_page_flip, .set_property = NULL, .cursor_set = NULL, /* handled by drm_mode_cursor_universal */
@@ -1131,6 +1125,7 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, struct drm_crtc *crtc = &vc4_crtc->base; struct drm_plane *primary_plane; unsigned int i;
int ret; /* For now, we create just the primary and the legacy cursor * planes. We should be able to stack more planes on easily,
@@ -1144,10 +1139,13 @@ int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, return PTR_ERR(primary_plane); }
spin_lock_init(&vc4_crtc->irq_lock);
drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL,
crtc_funcs, NULL);
ret = drmm_crtc_init_with_planes(drm, crtc, primary_plane, NULL,
crtc_funcs, NULL);
if (ret)
return ret;
drm_crtc_helper_add(crtc, crtc_helper_funcs);
spin_lock_init(&vc4_crtc->irq_lock);
Moving the spin_lock_init appears to be cosmetic and unrelated, but otherwise:
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
if (!vc4->hvs->hvs5) { drm_mode_crtc_set_gamma_size(crtc, ARRAY_SIZE(vc4_crtc->lut_r));
@@ -1226,8 +1224,6 @@ static void vc4_crtc_unbind(struct device *dev, struct device *master, struct platform_device *pdev = to_platform_device(dev); struct vc4_crtc *vc4_crtc = dev_get_drvdata(dev);
vc4_crtc_destroy(&vc4_crtc->base);
CRTC_WRITE(PV_INTEN, 0); platform_set_drvdata(pdev, NULL);
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 9a53ace85d95..fff3772be2d4 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -845,7 +845,6 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc); int vc4_crtc_init(struct drm_device *drm, struct vc4_crtc *vc4_crtc, const struct drm_crtc_funcs *crtc_funcs, const struct drm_crtc_helper_funcs *crtc_helper_funcs); -void vc4_crtc_destroy(struct drm_crtc *crtc); int vc4_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index e983ff7c5e13..f306e05ac5b2 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -383,7 +383,6 @@ static void vc4_txp_disable_vblank(struct drm_crtc *crtc) {}
static const struct drm_crtc_funcs vc4_txp_crtc_funcs = { .set_config = drm_atomic_helper_set_config,
.destroy = vc4_crtc_destroy, .page_flip = vc4_page_flip, .reset = vc4_crtc_reset, .atomic_duplicate_state = vc4_crtc_duplicate_state,
-- 2.36.1
There's no user for that pointer so let's just get rid of it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 7 ------- drivers/gpu/drm/vc4/vc4_drv.h | 1 - 2 files changed, 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index c180eb60bee8..f2b46c524919 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -249,7 +249,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_dpi *dpi; struct vc4_dpi_encoder *vc4_dpi_encoder; int ret; @@ -308,8 +307,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, dpi);
- vc4->dpi = dpi; - vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset);
return 0; @@ -323,8 +320,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) static void vc4_dpi_unbind(struct device *dev, struct device *master, void *data) { - struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_dpi *dpi = dev_get_drvdata(dev);
drm_of_panel_bridge_remove(dev->of_node, 0, 0); @@ -332,8 +327,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, drm_encoder_cleanup(dpi->encoder);
clk_disable_unprepare(dpi->core_clock); - - vc4->dpi = NULL; }
static const struct component_ops vc4_dpi_ops = { diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index fff3772be2d4..846f3cda179a 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -79,7 +79,6 @@ struct vc4_dev {
struct vc4_hvs *hvs; struct vc4_v3d *v3d; - struct vc4_dpi *dpi; struct vc4_vec *vec; struct vc4_txp *txp;
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
There's no user for that pointer so let's just get rid of it.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 7 ------- drivers/gpu/drm/vc4/vc4_drv.h | 1 - 2 files changed, 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index c180eb60bee8..f2b46c524919 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -249,7 +249,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_dpi *dpi; struct vc4_dpi_encoder *vc4_dpi_encoder; int ret;
@@ -308,8 +307,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, dpi);
vc4->dpi = dpi;
vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset); return 0;
@@ -323,8 +320,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) static void vc4_dpi_unbind(struct device *dev, struct device *master, void *data) {
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_dpi *dpi = dev_get_drvdata(dev); drm_of_panel_bridge_remove(dev->of_node, 0, 0);
@@ -332,8 +327,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, drm_encoder_cleanup(dpi->encoder);
clk_disable_unprepare(dpi->core_clock);
vc4->dpi = NULL;
}
static const struct component_ops vc4_dpi_ops = { diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index fff3772be2d4..846f3cda179a 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -79,7 +79,6 @@ struct vc4_dev {
struct vc4_hvs *hvs; struct vc4_v3d *v3d;
struct vc4_dpi *dpi; struct vc4_vec *vec; struct vc4_txp *txp;
-- 2.36.1
The VC4 DPI driver private structure contains only a pointer to the encoder it implements. This makes the overall structure somewhat inconsistent with the rest of the driver, and complicates its initialisation without any apparent gain.
Let's embed the drm_encoder structure (through the vc4_encoder one) into struct vc4_dpi to fix both issues.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 49 ++++++++++++----------------------- 1 file changed, 16 insertions(+), 33 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index f2b46c524919..c88e8e397730 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -83,10 +83,10 @@
/* General DPI hardware state. */ struct vc4_dpi { + struct vc4_encoder encoder; + struct platform_device *pdev;
- struct drm_encoder *encoder; - void __iomem *regs;
struct clk *pixel_clock; @@ -95,21 +95,15 @@ struct vc4_dpi { struct debugfs_regset32 regset; };
+static inline struct vc4_dpi * +to_vc4_dpi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct vc4_dpi, encoder.base); +} + #define DPI_READ(offset) readl(dpi->regs + (offset)) #define DPI_WRITE(offset, val) writel(val, dpi->regs + (offset))
-/* VC4 DPI encoder KMS struct */ -struct vc4_dpi_encoder { - struct vc4_encoder base; - struct vc4_dpi *dpi; -}; - -static inline struct vc4_dpi_encoder * -to_vc4_dpi_encoder(struct drm_encoder *encoder) -{ - return container_of(encoder, struct vc4_dpi_encoder, base.base); -} - static const struct debugfs_reg32 dpi_regs[] = { VC4_REG32(DPI_C), VC4_REG32(DPI_ID), @@ -117,8 +111,7 @@ static const struct debugfs_reg32 dpi_regs[] = {
static void vc4_dpi_encoder_disable(struct drm_encoder *encoder) { - struct vc4_dpi_encoder *vc4_encoder = to_vc4_dpi_encoder(encoder); - struct vc4_dpi *dpi = vc4_encoder->dpi; + struct vc4_dpi *dpi = to_vc4_dpi(encoder);
clk_disable_unprepare(dpi->pixel_clock); } @@ -127,8 +120,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) { struct drm_device *dev = encoder->dev; struct drm_display_mode *mode = &encoder->crtc->mode; - struct vc4_dpi_encoder *vc4_encoder = to_vc4_dpi_encoder(encoder); - struct vc4_dpi *dpi = vc4_encoder->dpi; + struct vc4_dpi *dpi = to_vc4_dpi(encoder); struct drm_connector_list_iter conn_iter; struct drm_connector *connector = NULL, *connector_scan; u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE; @@ -242,7 +234,7 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) return PTR_ERR(bridge); }
- return drm_bridge_attach(dpi->encoder, bridge, NULL, 0); + return drm_bridge_attach(&dpi->encoder.base, bridge, NULL, 0); }
static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) @@ -250,21 +242,12 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dpi *dpi; - struct vc4_dpi_encoder *vc4_dpi_encoder; int ret;
dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); if (!dpi) return -ENOMEM; - - vc4_dpi_encoder = devm_kzalloc(dev, sizeof(*vc4_dpi_encoder), - GFP_KERNEL); - if (!vc4_dpi_encoder) - return -ENOMEM; - vc4_dpi_encoder->base.type = VC4_ENCODER_TYPE_DPI; - vc4_dpi_encoder->dpi = dpi; - dpi->encoder = &vc4_dpi_encoder->base.base; - + dpi->encoder.type = VC4_ENCODER_TYPE_DPI; dpi->pdev = pdev; dpi->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(dpi->regs)) @@ -298,8 +281,8 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) DRM_ERROR("Failed to turn on core clock: %d\n", ret);
- drm_simple_encoder_init(drm, dpi->encoder, DRM_MODE_ENCODER_DPI); - drm_encoder_helper_add(dpi->encoder, &vc4_dpi_encoder_helper_funcs); + drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); + drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
ret = vc4_dpi_init_bridge(dpi); if (ret) @@ -312,7 +295,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) return 0;
err_destroy_encoder: - drm_encoder_cleanup(dpi->encoder); + drm_encoder_cleanup(&dpi->encoder.base); clk_disable_unprepare(dpi->core_clock); return ret; } @@ -324,7 +307,7 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master,
drm_of_panel_bridge_remove(dev->of_node, 0, 0);
- drm_encoder_cleanup(dpi->encoder); + drm_encoder_cleanup(&dpi->encoder.base);
clk_disable_unprepare(dpi->core_clock); }
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The VC4 DPI driver private structure contains only a pointer to the encoder it implements. This makes the overall structure somewhat inconsistent with the rest of the driver, and complicates its initialisation without any apparent gain.
Let's embed the drm_encoder structure (through the vc4_encoder one) into struct vc4_dpi to fix both issues.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 49 ++++++++++++----------------------- 1 file changed, 16 insertions(+), 33 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index f2b46c524919..c88e8e397730 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -83,10 +83,10 @@
/* General DPI hardware state. */ struct vc4_dpi {
struct vc4_encoder encoder;
struct platform_device *pdev;
struct drm_encoder *encoder;
void __iomem *regs; struct clk *pixel_clock;
@@ -95,21 +95,15 @@ struct vc4_dpi { struct debugfs_regset32 regset; };
+static inline struct vc4_dpi * +to_vc4_dpi(struct drm_encoder *encoder) +{
return container_of(encoder, struct vc4_dpi, encoder.base);
+}
#define DPI_READ(offset) readl(dpi->regs + (offset)) #define DPI_WRITE(offset, val) writel(val, dpi->regs + (offset))
-/* VC4 DPI encoder KMS struct */ -struct vc4_dpi_encoder {
struct vc4_encoder base;
struct vc4_dpi *dpi;
-};
-static inline struct vc4_dpi_encoder * -to_vc4_dpi_encoder(struct drm_encoder *encoder) -{
return container_of(encoder, struct vc4_dpi_encoder, base.base);
-}
static const struct debugfs_reg32 dpi_regs[] = { VC4_REG32(DPI_C), VC4_REG32(DPI_ID), @@ -117,8 +111,7 @@ static const struct debugfs_reg32 dpi_regs[] = {
static void vc4_dpi_encoder_disable(struct drm_encoder *encoder) {
struct vc4_dpi_encoder *vc4_encoder = to_vc4_dpi_encoder(encoder);
struct vc4_dpi *dpi = vc4_encoder->dpi;
struct vc4_dpi *dpi = to_vc4_dpi(encoder); clk_disable_unprepare(dpi->pixel_clock);
} @@ -127,8 +120,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) { struct drm_device *dev = encoder->dev; struct drm_display_mode *mode = &encoder->crtc->mode;
struct vc4_dpi_encoder *vc4_encoder = to_vc4_dpi_encoder(encoder);
struct vc4_dpi *dpi = vc4_encoder->dpi;
struct vc4_dpi *dpi = to_vc4_dpi(encoder); struct drm_connector_list_iter conn_iter; struct drm_connector *connector = NULL, *connector_scan; u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE;
@@ -242,7 +234,7 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) return PTR_ERR(bridge); }
return drm_bridge_attach(dpi->encoder, bridge, NULL, 0);
return drm_bridge_attach(&dpi->encoder.base, bridge, NULL, 0);
}
static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) @@ -250,21 +242,12 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dpi *dpi;
struct vc4_dpi_encoder *vc4_dpi_encoder; int ret; dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); if (!dpi) return -ENOMEM;
vc4_dpi_encoder = devm_kzalloc(dev, sizeof(*vc4_dpi_encoder),
GFP_KERNEL);
if (!vc4_dpi_encoder)
return -ENOMEM;
vc4_dpi_encoder->base.type = VC4_ENCODER_TYPE_DPI;
vc4_dpi_encoder->dpi = dpi;
dpi->encoder = &vc4_dpi_encoder->base.base;
dpi->encoder.type = VC4_ENCODER_TYPE_DPI; dpi->pdev = pdev; dpi->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(dpi->regs))
@@ -298,8 +281,8 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) DRM_ERROR("Failed to turn on core clock: %d\n", ret);
drm_simple_encoder_init(drm, dpi->encoder, DRM_MODE_ENCODER_DPI);
drm_encoder_helper_add(dpi->encoder, &vc4_dpi_encoder_helper_funcs);
drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI);
drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs); ret = vc4_dpi_init_bridge(dpi); if (ret)
@@ -312,7 +295,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) return 0;
err_destroy_encoder:
drm_encoder_cleanup(dpi->encoder);
drm_encoder_cleanup(&dpi->encoder.base); clk_disable_unprepare(dpi->core_clock); return ret;
} @@ -324,7 +307,7 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master,
drm_of_panel_bridge_remove(dev->of_node, 0, 0);
drm_encoder_cleanup(dpi->encoder);
drm_encoder_cleanup(&dpi->encoder.base); clk_disable_unprepare(dpi->core_clock);
}
2.36.1
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index c88e8e397730..d1eaafb43bd1 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -244,9 +244,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) struct vc4_dpi *dpi; int ret;
- dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL); + dpi = drmm_kzalloc(drm, sizeof(*dpi), GFP_KERNEL); if (!dpi) return -ENOMEM; + dpi->encoder.type = VC4_ENCODER_TYPE_DPI; dpi->pdev = pdev; dpi->regs = vc4_ioremap_regs(pdev, 0);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index c88e8e397730..d1eaafb43bd1 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -244,9 +244,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) struct vc4_dpi *dpi; int ret;
dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL);
dpi = drmm_kzalloc(drm, sizeof(*dpi), GFP_KERNEL); if (!dpi) return -ENOMEM;
dpi->encoder.type = VC4_ENCODER_TYPE_DPI; dpi->pdev = pdev; dpi->regs = vc4_ioremap_regs(pdev, 0);
-- 2.36.1
If we fail to enable the DPI clock, we just ignore the error and moves forward. Let's return an error instead.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index d1eaafb43bd1..658e0aa9e2e1 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -270,6 +270,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) DRM_ERROR("Failed to get core clock: %d\n", ret); return ret; } + dpi->pixel_clock = devm_clk_get(dev, "pixel"); if (IS_ERR(dpi->pixel_clock)) { ret = PTR_ERR(dpi->pixel_clock); @@ -279,8 +280,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) }
ret = clk_prepare_enable(dpi->core_clock); - if (ret) + if (ret) { DRM_ERROR("Failed to turn on core clock: %d\n", ret); + return ret; + }
drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
If we fail to enable the DPI clock, we just ignore the error and moves forward. Let's return an error instead.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index d1eaafb43bd1..658e0aa9e2e1 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -270,6 +270,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) DRM_ERROR("Failed to get core clock: %d\n", ret); return ret; }
dpi->pixel_clock = devm_clk_get(dev, "pixel"); if (IS_ERR(dpi->pixel_clock)) { ret = PTR_ERR(dpi->pixel_clock);
@@ -279,8 +280,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) }
ret = clk_prepare_enable(dpi->core_clock);
if (ret)
if (ret) { DRM_ERROR("Failed to turn on core clock: %d\n", ret);
return ret;
} drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
-- 2.36.1
Since we have a managed call to create our panel_bridge instance, the call to drm_of_panel_bridge_remove() at unbind is both redundant and dangerous since it might lead to a use-after-free.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 2 -- 1 file changed, 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 658e0aa9e2e1..5a6cdea7bf7b 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -309,8 +309,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, { struct vc4_dpi *dpi = dev_get_drvdata(dev);
- drm_of_panel_bridge_remove(dev->of_node, 0, 0); - drm_encoder_cleanup(&dpi->encoder.base);
clk_disable_unprepare(dpi->core_clock);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Since we have a managed call to create our panel_bridge instance, the call to drm_of_panel_bridge_remove() at unbind is both redundant and dangerous since it might lead to a use-after-free.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 2 -- 1 file changed, 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 658e0aa9e2e1..5a6cdea7bf7b 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -309,8 +309,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, { struct vc4_dpi *dpi = dev_get_drvdata(dev);
drm_of_panel_bridge_remove(dev->of_node, 0, 0);
drm_encoder_cleanup(&dpi->encoder.base); clk_disable_unprepare(dpi->core_clock);
-- 2.36.1
Adding a device-managed action will make the error path easier, so let's create one to disable our clock.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 5a6cdea7bf7b..4e24dbad77f2 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -237,6 +237,13 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) return drm_bridge_attach(&dpi->encoder.base, bridge, NULL, 0); }
+static void vc4_dpi_disable_clock(void *ptr) +{ + struct vc4_dpi *dpi = ptr; + + clk_disable_unprepare(dpi->core_clock); +} + static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -285,6 +292,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) return ret; }
+ ret = devm_add_action_or_reset(dev, vc4_dpi_disable_clock, dpi); + if (ret) + return ret; + drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
@@ -300,7 +311,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
err_destroy_encoder: drm_encoder_cleanup(&dpi->encoder.base); - clk_disable_unprepare(dpi->core_clock); return ret; }
@@ -310,8 +320,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, struct vc4_dpi *dpi = dev_get_drvdata(dev);
drm_encoder_cleanup(&dpi->encoder.base); - - clk_disable_unprepare(dpi->core_clock); }
static const struct component_ops vc4_dpi_ops = {
Hi Maxime.
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Adding a device-managed action will make the error path easier, so let's create one to disable our clock.
The DPI block has two clocks (core and pixel), and this is only affecting one of them (the core clock). (I'm actually puzzling over what it's wanting to do with the core clock here as it's only enabling it rather than setting a rate. I think it may be redundant).
With that text amended: Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
Dave
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_dpi.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 5a6cdea7bf7b..4e24dbad77f2 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -237,6 +237,13 @@ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) return drm_bridge_attach(&dpi->encoder.base, bridge, NULL, 0); }
+static void vc4_dpi_disable_clock(void *ptr) +{
struct vc4_dpi *dpi = ptr;
clk_disable_unprepare(dpi->core_clock);
+}
static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -285,6 +292,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) return ret; }
ret = devm_add_action_or_reset(dev, vc4_dpi_disable_clock, dpi);
if (ret)
return ret;
drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
@@ -300,7 +311,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
err_destroy_encoder: drm_encoder_cleanup(&dpi->encoder.base);
clk_disable_unprepare(dpi->core_clock); return ret;
}
@@ -310,8 +320,6 @@ static void vc4_dpi_unbind(struct device *dev, struct device *master, struct vc4_dpi *dpi = dev_get_drvdata(dev);
drm_encoder_cleanup(&dpi->encoder.base);
clk_disable_unprepare(dpi->core_clock);
}
static const struct component_ops vc4_dpi_ops = {
2.36.1
Hi Dave,
On Tue, Jun 14, 2022 at 05:47:28PM +0100, Dave Stevenson wrote:
Hi Maxime.
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Adding a device-managed action will make the error path easier, so let's create one to disable our clock.
The DPI block has two clocks (core and pixel), and this is only affecting one of them (the core clock).
Thanks for the suggestion, I've amended the commit message.
(I'm actually puzzling over what it's wanting to do with the core clock here as it's only enabling it rather than setting a rate. I think it may be redundant).
Could it be that it a "bus" clock that we need it to access the registers?
Maxime
On Thu, 16 Jun 2022 at 09:38, Maxime Ripard maxime@cerno.tech wrote:
Hi Dave,
On Tue, Jun 14, 2022 at 05:47:28PM +0100, Dave Stevenson wrote:
Hi Maxime.
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Adding a device-managed action will make the error path easier, so let's create one to disable our clock.
The DPI block has two clocks (core and pixel), and this is only affecting one of them (the core clock).
Thanks for the suggestion, I've amended the commit message.
(I'm actually puzzling over what it's wanting to do with the core clock here as it's only enabling it rather than setting a rate. I think it may be redundant).
Could it be that it a "bus" clock that we need it to access the registers?
No idea. Normally it's the power domain that is required to access registers. AFAIK the core clock is never turned off, so the request for it is just a little odd. It is what it is though, so fine to leave it alone.
Dave
On Thu, Jun 16, 2022 at 10:47:56AM +0100, Dave Stevenson wrote:
On Thu, 16 Jun 2022 at 09:38, Maxime Ripard maxime@cerno.tech wrote:
Hi Dave,
On Tue, Jun 14, 2022 at 05:47:28PM +0100, Dave Stevenson wrote:
Hi Maxime.
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Adding a device-managed action will make the error path easier, so let's create one to disable our clock.
The DPI block has two clocks (core and pixel), and this is only affecting one of them (the core clock).
Thanks for the suggestion, I've amended the commit message.
(I'm actually puzzling over what it's wanting to do with the core clock here as it's only enabling it rather than setting a rate. I think it may be redundant).
Could it be that it a "bus" clock that we need it to access the registers?
No idea. Normally it's the power domain that is required to access registers.
For HDMI at least, the power domain being off will make a read return some bogus value, but the core clock being off will just make the CPU stall. I can only assume it would be the same for the DPI controller?
AFAIK the core clock is never turned off, so the request for it is just a little odd.
Even if the clock driver never shuts it off, I think it still has value to follow the Clock Framework conventions. We might have a different clock policy in the future, and it would throw people reading the DPI driver off.
Maxime
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 4e24dbad77f2..8a50de2c40d9 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -296,35 +296,25 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); + ret = drmm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); + if (ret) + return ret; + drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs);
ret = vc4_dpi_init_bridge(dpi); if (ret) - goto err_destroy_encoder; + return ret;
dev_set_drvdata(dev, dpi);
vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset);
return 0; - -err_destroy_encoder: - drm_encoder_cleanup(&dpi->encoder.base); - return ret; -} - -static void vc4_dpi_unbind(struct device *dev, struct device *master, - void *data) -{ - struct vc4_dpi *dpi = dev_get_drvdata(dev); - - drm_encoder_cleanup(&dpi->encoder.base); }
static const struct component_ops vc4_dpi_ops = { .bind = vc4_dpi_bind, - .unbind = vc4_dpi_unbind, };
static int vc4_dpi_dev_probe(struct platform_device *pdev)
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 4e24dbad77f2..8a50de2c40d9 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -296,35 +296,25 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
drm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI);
ret = drmm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI);
if (ret)
return ret;
drm_encoder_helper_add(&dpi->encoder.base, &vc4_dpi_encoder_helper_funcs); ret = vc4_dpi_init_bridge(dpi); if (ret)
goto err_destroy_encoder;
return ret; dev_set_drvdata(dev, dpi); vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset); return 0;
-err_destroy_encoder:
drm_encoder_cleanup(&dpi->encoder.base);
return ret;
-}
-static void vc4_dpi_unbind(struct device *dev, struct device *master,
void *data)
-{
struct vc4_dpi *dpi = dev_get_drvdata(dev);
drm_encoder_cleanup(&dpi->encoder.base);
}
static const struct component_ops vc4_dpi_ops = { .bind = vc4_dpi_bind,
.unbind = vc4_dpi_unbind,
};
static int vc4_dpi_dev_probe(struct platform_device *pdev)
2.36.1
The current code uses a device-managed function to retrieve the next bridge downstream.
However, that means that it will be removed at unbind time, where the DRM device is still very much live and might still have some applications that still have it open.
Switch to a DRM-managed variant to clean everything up once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 8a50de2c40d9..9950761449cf 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -220,10 +220,11 @@ static const struct of_device_id vc4_dpi_dt_match[] = { */ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) { + struct drm_device *drm = dpi->encoder.base.dev; struct device *dev = &dpi->pdev->dev; struct drm_bridge *bridge;
- bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0); + bridge = drmm_of_get_bridge(drm, dev->of_node, 0, 0); if (IS_ERR(bridge)) { /* If nothing was connected in the DT, that's not an * error.
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The current code uses a device-managed function to retrieve the next bridge downstream.
However, that means that it will be removed at unbind time, where the DRM device is still very much live and might still have some applications that still have it open.
Switch to a DRM-managed variant to clean everything up once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 8a50de2c40d9..9950761449cf 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -220,10 +220,11 @@ static const struct of_device_id vc4_dpi_dt_match[] = { */ static int vc4_dpi_init_bridge(struct vc4_dpi *dpi) {
struct drm_device *drm = dpi->encoder.base.dev; struct device *dev = &dpi->pdev->dev; struct drm_bridge *bridge;
bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
bridge = drmm_of_get_bridge(drm, dev->of_node, 0, 0); if (IS_ERR(bridge)) { /* If nothing was connected in the DT, that's not an * error.
-- 2.36.1
Our current code now mixes some resources whose lifetime are tied to the device (clocks, IO mappings, etc.) and some that are tied to the DRM device (encoder, bridge).
The device one will be freed at unbind time, but the DRM one will only be freed when the last user of the DRM device closes its file handle.
So we end up with a time window during which we can call the encoder hooks, but we don't have access to the underlying resources and device.
Let's protect all those sections with drm_dev_enter() and drm_dev_exit() so that we bail out if we are during that window.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dpi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 9950761449cf..ea3d20651f43 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -13,6 +13,7 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_of.h> #include <drm/drm_panel.h> @@ -111,9 +112,16 @@ static const struct debugfs_reg32 dpi_regs[] = {
static void vc4_dpi_encoder_disable(struct drm_encoder *encoder) { + struct drm_device *dev = encoder->dev; struct vc4_dpi *dpi = to_vc4_dpi(encoder); + int idx; + + if (!drm_dev_enter(dev, &idx)) + return;
clk_disable_unprepare(dpi->pixel_clock); + + drm_dev_exit(idx); }
static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) @@ -124,6 +132,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) struct drm_connector_list_iter conn_iter; struct drm_connector *connector = NULL, *connector_scan; u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE; + int idx; int ret;
/* Look up the connector attached to DPI so we can get the @@ -184,6 +193,9 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) dpi_c |= DPI_VSYNC_DISABLE;
+ if (!drm_dev_enter(dev, &idx)) + return; + DPI_WRITE(DPI_C, dpi_c);
ret = clk_set_rate(dpi->pixel_clock, mode->clock * 1000); @@ -193,6 +205,8 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) ret = clk_prepare_enable(dpi->pixel_clock); if (ret) DRM_ERROR("Failed to set clock rate: %d\n", ret); + + drm_dev_exit(idx); }
static enum drm_mode_status vc4_dpi_encoder_mode_valid(struct drm_encoder *encoder,
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
Our current code now mixes some resources whose lifetime are tied to the device (clocks, IO mappings, etc.) and some that are tied to the DRM device (encoder, bridge).
The device one will be freed at unbind time, but the DRM one will only be freed when the last user of the DRM device closes its file handle.
So we end up with a time window during which we can call the encoder hooks, but we don't have access to the underlying resources and device.
Let's protect all those sections with drm_dev_enter() and drm_dev_exit() so that we bail out if we are during that window.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dpi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index 9950761449cf..ea3d20651f43 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -13,6 +13,7 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_of.h> #include <drm/drm_panel.h> @@ -111,9 +112,16 @@ static const struct debugfs_reg32 dpi_regs[] = {
static void vc4_dpi_encoder_disable(struct drm_encoder *encoder) {
struct drm_device *dev = encoder->dev; struct vc4_dpi *dpi = to_vc4_dpi(encoder);
int idx;
if (!drm_dev_enter(dev, &idx))
return; clk_disable_unprepare(dpi->pixel_clock);
drm_dev_exit(idx);
}
static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) @@ -124,6 +132,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) struct drm_connector_list_iter conn_iter; struct drm_connector *connector = NULL, *connector_scan; u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE;
int idx; int ret; /* Look up the connector attached to DPI so we can get the
@@ -184,6 +193,9 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) dpi_c |= DPI_VSYNC_DISABLE;
if (!drm_dev_enter(dev, &idx))
return;
DPI_WRITE(DPI_C, dpi_c); ret = clk_set_rate(dpi->pixel_clock, mode->clock * 1000);
@@ -193,6 +205,8 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) ret = clk_prepare_enable(dpi->pixel_clock); if (ret) DRM_ERROR("Failed to set clock rate: %d\n", ret);
drm_dev_exit(idx);
}
static enum drm_mode_status vc4_dpi_encoder_mode_valid(struct drm_encoder *encoder,
2.36.1
The VC4 DSI driver private structure contains only a pointer to the encoder it implements. This makes the overall structure somewhat inconsistent with the rest of the driver, and complicates its initialisation without any apparent gain.
Let's embed the drm_encoder structure (through the vc4_encoder one) into struct vc4_dsi to fix both issues.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dsi.c | 58 +++++++++++++---------------------- 1 file changed, 22 insertions(+), 36 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 98308a17e4ed..dbb3f6fb39b4 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -507,10 +507,11 @@ struct vc4_dsi_variant {
/* General DSI hardware state. */ struct vc4_dsi { - struct platform_device *pdev; - + struct vc4_encoder encoder; struct mipi_dsi_host dsi_host; - struct drm_encoder *encoder; + + struct platform_device *pdev; + struct drm_bridge *bridge; struct list_head bridge_chain;
@@ -558,6 +559,12 @@ struct vc4_dsi {
#define host_to_dsi(host) container_of(host, struct vc4_dsi, dsi_host)
+static inline struct vc4_dsi * +to_vc4_dsi(struct drm_encoder *encoder) +{ + return container_of(encoder, struct vc4_dsi, encoder.base); +} + static inline void dsi_dma_workaround_write(struct vc4_dsi *dsi, u32 offset, u32 val) { @@ -602,18 +609,6 @@ dsi_dma_workaround_write(struct vc4_dsi *dsi, u32 offset, u32 val) DSI_WRITE(dsi->variant->port ? DSI1_##offset : DSI0_##offset, val) #define DSI_PORT_BIT(bit) (dsi->variant->port ? DSI1_##bit : DSI0_##bit)
-/* VC4 DSI encoder KMS struct */ -struct vc4_dsi_encoder { - struct vc4_encoder base; - struct vc4_dsi *dsi; -}; - -static inline struct vc4_dsi_encoder * -to_vc4_dsi_encoder(struct drm_encoder *encoder) -{ - return container_of(encoder, struct vc4_dsi_encoder, base.base); -} - static const struct debugfs_reg32 dsi0_regs[] = { VC4_REG32(DSI0_CTRL), VC4_REG32(DSI0_STAT), @@ -753,8 +748,7 @@ dsi_esc_timing(u32 ns)
static void vc4_dsi_encoder_disable(struct drm_encoder *encoder) { - struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder); - struct vc4_dsi *dsi = vc4_encoder->dsi; + struct vc4_dsi *dsi = to_vc4_dsi(encoder); struct device *dev = &dsi->pdev->dev; struct drm_bridge *iter;
@@ -794,8 +788,7 @@ static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { - struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder); - struct vc4_dsi *dsi = vc4_encoder->dsi; + struct vc4_dsi *dsi = to_vc4_dsi(encoder); struct clk *phy_parent = clk_get_parent(dsi->pll_phy_clock); unsigned long parent_rate = clk_get_rate(phy_parent); unsigned long pixel_clock_hz = mode->clock * 1000; @@ -832,8 +825,7 @@ static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder, static void vc4_dsi_encoder_enable(struct drm_encoder *encoder) { struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; - struct vc4_dsi_encoder *vc4_encoder = to_vc4_dsi_encoder(encoder); - struct vc4_dsi *dsi = vc4_encoder->dsi; + struct vc4_dsi *dsi = to_vc4_dsi(encoder); struct device *dev = &dsi->pdev->dev; bool debug_dump_regs = false; struct drm_bridge *iter; @@ -1492,21 +1484,14 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dsi *dsi = dev_get_drvdata(dev); - struct vc4_dsi_encoder *vc4_dsi_encoder; + struct drm_encoder *encoder = &dsi->encoder.base; dma_cap_mask_t dma_mask; int ret;
dsi->variant = of_device_get_match_data(dev);
- vc4_dsi_encoder = devm_kzalloc(dev, sizeof(*vc4_dsi_encoder), - GFP_KERNEL); - if (!vc4_dsi_encoder) - return -ENOMEM; - INIT_LIST_HEAD(&dsi->bridge_chain); - vc4_dsi_encoder->base.type = VC4_ENCODER_TYPE_DSI1; - vc4_dsi_encoder->dsi = dsi; - dsi->encoder = &vc4_dsi_encoder->base.base; + dsi->encoder.type = VC4_ENCODER_TYPE_DSI1;
dsi->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(dsi->regs)) @@ -1614,10 +1599,10 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- drm_simple_encoder_init(drm, dsi->encoder, DRM_MODE_ENCODER_DSI); - drm_encoder_helper_add(dsi->encoder, &vc4_dsi_encoder_helper_funcs); + drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI); + drm_encoder_helper_add(encoder, &vc4_dsi_encoder_helper_funcs);
- ret = drm_bridge_attach(dsi->encoder, dsi->bridge, NULL, 0); + ret = drm_bridge_attach(encoder, dsi->bridge, NULL, 0); if (ret) return ret; /* Disable the atomic helper calls into the bridge. We @@ -1625,7 +1610,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) * from our driver, since we need to sequence them within the * encoder's enable/disable paths. */ - list_splice_init(&dsi->encoder->bridge_chain, &dsi->bridge_chain); + list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
vc4_debugfs_add_regset32(drm, dsi->variant->debugfs_name, &dsi->regset);
@@ -1638,6 +1623,7 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, void *data) { struct vc4_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder.base;
pm_runtime_disable(dev);
@@ -1645,8 +1631,8 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, * Restore the bridge_chain so the bridge detach procedure can happen * normally. */ - list_splice_init(&dsi->bridge_chain, &dsi->encoder->bridge_chain); - drm_encoder_cleanup(dsi->encoder); + list_splice_init(&dsi->bridge_chain, &encoder->bridge_chain); + drm_encoder_cleanup(encoder); }
static const struct component_ops vc4_dsi_ops = {
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dsi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index dbb3f6fb39b4..bcaf87b43cbd 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1599,7 +1599,10 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI); + ret = drmm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI); + if (ret) + return ret; + drm_encoder_helper_add(encoder, &vc4_dsi_encoder_helper_funcs);
ret = drm_bridge_attach(encoder, dsi->bridge, NULL, 0); @@ -1632,7 +1635,6 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, * normally. */ list_splice_init(&dsi->bridge_chain, &encoder->bridge_chain); - drm_encoder_cleanup(encoder); }
static const struct component_ops vc4_dsi_ops = {
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dsi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index dbb3f6fb39b4..bcaf87b43cbd 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1599,7 +1599,10 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI);
ret = drmm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI);
if (ret)
return ret;
drm_encoder_helper_add(encoder, &vc4_dsi_encoder_helper_funcs); ret = drm_bridge_attach(encoder, dsi->bridge, NULL, 0);
@@ -1632,7 +1635,6 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, * normally. */ list_splice_init(&dsi->bridge_chain, &encoder->bridge_chain);
drm_encoder_cleanup(encoder);
}
static const struct component_ops vc4_dsi_ops = {
2.36.1
The current code uses a device-managed function to retrieve the next bridge downstream.
However, that means that it will be removed at unbind time, where the DRM device is still very much live and might still have some applications that still have it open.
Switch to a DRM-managed variant to clean everything up once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index bcaf87b43cbd..10533a2a41b3 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1584,7 +1584,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) return ret; }
- dsi->bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0); + dsi->bridge = drmm_of_get_bridge(drm, dev->of_node, 0, 0); if (IS_ERR(dsi->bridge)) return PTR_ERR(dsi->bridge);
On Fri, 10 Jun 2022 at 10:30, Maxime Ripard maxime@cerno.tech wrote:
The current code uses a device-managed function to retrieve the next bridge downstream.
However, that means that it will be removed at unbind time, where the DRM device is still very much live and might still have some applications that still have it open.
Switch to a DRM-managed variant to clean everything up once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Reviewed-by: Dave Stevenson dave.stevenson@raspberrypi.com
drivers/gpu/drm/vc4/vc4_dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index bcaf87b43cbd..10533a2a41b3 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1584,7 +1584,7 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) return ret; }
dsi->bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
dsi->bridge = drmm_of_get_bridge(drm, dev->of_node, 0, 0); if (IS_ERR(dsi->bridge)) return PTR_ERR(dsi->bridge);
-- 2.36.1
The vc4_dsi structure is currently allocated through a device-managed allocation. This can lead to use-after-free issues however in the unbinding path since the DRM entities will stick around, but the underlying structure has been freed.
However, we can't just fix it by using a DRM-managed allocation like we did for the other drivers since the DSI case is a bit more intricate.
Indeed, the structure will be allocated at probe time, when we don't have a DRM device yet, to be able to register the DSI bus driver. We will then reuse it at bind time to register our KMS entities in the framework.
In order to work around both constraints, we can use a kref to track the users of the structure (DSI host, and KMS), and then put our structure when the DSI host will have been unregistered, and through a DRM-managed action that will execute once we won't need the KMS entities anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dsi.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 10533a2a41b3..282537f27b8e 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -510,6 +510,8 @@ struct vc4_dsi { struct vc4_encoder encoder; struct mipi_dsi_host dsi_host;
+ struct kref kref; + struct platform_device *pdev;
struct drm_bridge *bridge; @@ -1479,6 +1481,15 @@ vc4_dsi_init_phy_clocks(struct vc4_dsi *dsi) dsi->clk_onecell); }
+static void vc4_dsi_release(struct kref *kref); + +static void vc4_dsi_put(struct drm_device *drm, void *ptr) +{ + struct vc4_dsi *dsi = ptr; + + kref_put(&dsi->kref, &vc4_dsi_release); +} + static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -1488,6 +1499,12 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) dma_cap_mask_t dma_mask; int ret;
+ kref_get(&dsi->kref); + + ret = drmm_add_action_or_reset(drm, vc4_dsi_put, dsi); + if (ret) + return ret; + dsi->variant = of_device_get_match_data(dev);
INIT_LIST_HEAD(&dsi->bridge_chain); @@ -1642,16 +1659,25 @@ static const struct component_ops vc4_dsi_ops = { .unbind = vc4_dsi_unbind, };
+static void vc4_dsi_release(struct kref *kref) +{ + struct vc4_dsi *dsi = + container_of(kref, struct vc4_dsi, kref); + + kfree(dsi); +} + static int vc4_dsi_dev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct vc4_dsi *dsi;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + dsi = kzalloc(sizeof(*dsi), GFP_KERNEL); if (!dsi) return -ENOMEM; dev_set_drvdata(dev, dsi);
+ kref_init(&dsi->kref); dsi->pdev = pdev; dsi->dsi_host.ops = &vc4_dsi_host_ops; dsi->dsi_host.dev = dev; @@ -1666,6 +1692,7 @@ static int vc4_dsi_dev_remove(struct platform_device *pdev) struct vc4_dsi *dsi = dev_get_drvdata(dev);
mipi_dsi_host_unregister(&dsi->dsi_host); + kref_put(&dsi->kref, &vc4_dsi_release); return 0; }
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The vc4_dsi structure is currently allocated through a device-managed allocation. This can lead to use-after-free issues however in the unbinding path since the DRM entities will stick around, but the underlying structure has been freed.
However, we can't just fix it by using a DRM-managed allocation like we did for the other drivers since the DSI case is a bit more intricate.
Indeed, the structure will be allocated at probe time, when we don't have a DRM device yet, to be able to register the DSI bus driver. We will then reuse it at bind time to register our KMS entities in the framework.
In order to work around both constraints, we can use a kref to track the users of the structure (DSI host, and KMS), and then put our structure when the DSI host will have been unregistered, and through a DRM-managed action that will execute once we won't need the KMS entities anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_dsi.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 10533a2a41b3..282537f27b8e 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -510,6 +510,8 @@ struct vc4_dsi { struct vc4_encoder encoder; struct mipi_dsi_host dsi_host;
struct kref kref;
struct platform_device *pdev;
struct drm_bridge *bridge;
@@ -1479,6 +1481,15 @@ vc4_dsi_init_phy_clocks(struct vc4_dsi *dsi) dsi->clk_onecell); }
+static void vc4_dsi_release(struct kref *kref);
+static void vc4_dsi_put(struct drm_device *drm, void *ptr) +{
- struct vc4_dsi *dsi = ptr;
- kref_put(&dsi->kref, &vc4_dsi_release);
+}
- static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev);
@@ -1488,6 +1499,12 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) dma_cap_mask_t dma_mask; int ret;
kref_get(&dsi->kref);
ret = drmm_add_action_or_reset(drm, vc4_dsi_put, dsi);
if (ret)
return ret;
dsi->variant = of_device_get_match_data(dev);
INIT_LIST_HEAD(&dsi->bridge_chain);
@@ -1642,16 +1659,25 @@ static const struct component_ops vc4_dsi_ops = { .unbind = vc4_dsi_unbind, };
+static void vc4_dsi_release(struct kref *kref) +{
- struct vc4_dsi *dsi =
container_of(kref, struct vc4_dsi, kref);
- kfree(dsi);
+}
- static int vc4_dsi_dev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct vc4_dsi *dsi;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
dsi = kzalloc(sizeof(*dsi), GFP_KERNEL); if (!dsi) return -ENOMEM; dev_set_drvdata(dev, dsi);
kref_init(&dsi->kref); dsi->pdev = pdev; dsi->dsi_host.ops = &vc4_dsi_host_ops; dsi->dsi_host.dev = dev;
@@ -1666,6 +1692,7 @@ static int vc4_dsi_dev_remove(struct platform_device *pdev) struct vc4_dsi *dsi = dev_get_drvdata(dev);
mipi_dsi_host_unregister(&dsi->dsi_host);
- kref_put(&dsi->kref, &vc4_dsi_release);
Maybe vc4_dsi_put() ?
return 0; }
Am 20.06.22 um 12:55 schrieb Thomas Zimmermann:
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
The vc4_dsi structure is currently allocated through a device-managed allocation. This can lead to use-after-free issues however in the unbinding path since the DRM entities will stick around, but the underlying structure has been freed.
However, we can't just fix it by using a DRM-managed allocation like we did for the other drivers since the DSI case is a bit more intricate.
Indeed, the structure will be allocated at probe time, when we don't have a DRM device yet, to be able to register the DSI bus driver. We will then reuse it at bind time to register our KMS entities in the framework.
In order to work around both constraints, we can use a kref to track the users of the structure (DSI host, and KMS), and then put our structure when the DSI host will have been unregistered, and through a DRM-managed action that will execute once we won't need the KMS entities anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_dsi.c | 29 ++++++++++++++++++++++++++++- Â 1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 10533a2a41b3..282537f27b8e 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -510,6 +510,8 @@ struct vc4_dsi { Â Â Â Â Â struct vc4_encoder encoder; Â Â Â Â Â struct mipi_dsi_host dsi_host; +Â Â Â struct kref kref;
struct platform_device *pdev; Â Â Â Â Â struct drm_bridge *bridge; @@ -1479,6 +1481,15 @@ vc4_dsi_init_phy_clocks(struct vc4_dsi *dsi) Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â dsi->clk_onecell); Â } +static void vc4_dsi_release(struct kref *kref);
+static void vc4_dsi_put(struct drm_device *drm, void *ptr) +{ +Â Â Â struct vc4_dsi *dsi = ptr;
+Â Â Â kref_put(&dsi->kref, &vc4_dsi_release); +}
static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) Â { Â Â Â Â Â struct platform_device *pdev = to_platform_device(dev); @@ -1488,6 +1499,12 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) Â Â Â Â Â dma_cap_mask_t dma_mask; Â Â Â Â Â int ret; +Â Â Â kref_get(&dsi->kref);
+Â Â Â ret = drmm_add_action_or_reset(drm, vc4_dsi_put, dsi); +Â Â Â if (ret) +Â Â Â Â Â Â Â return ret;
dsi->variant = of_device_get_match_data(dev); Â Â Â Â Â INIT_LIST_HEAD(&dsi->bridge_chain); @@ -1642,16 +1659,25 @@ static const struct component_ops vc4_dsi_ops = { Â Â Â Â Â .unbind = vc4_dsi_unbind, Â }; +static void vc4_dsi_release(struct kref *kref) +{ +Â Â Â struct vc4_dsi *dsi = +Â Â Â Â Â Â Â container_of(kref, struct vc4_dsi, kref);
+Â Â Â kfree(dsi); +}
static int vc4_dsi_dev_probe(struct platform_device *pdev) Â { Â Â Â Â Â struct device *dev = &pdev->dev; Â Â Â Â Â struct vc4_dsi *dsi; -Â Â Â dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); +Â Â Â dsi = kzalloc(sizeof(*dsi), GFP_KERNEL); Â Â Â Â Â if (!dsi) Â Â Â Â Â Â Â Â Â return -ENOMEM; Â Â Â Â Â dev_set_drvdata(dev, dsi); +Â Â Â kref_init(&dsi->kref); Â Â Â Â Â dsi->pdev = pdev; Â Â Â Â Â dsi->dsi_host.ops = &vc4_dsi_host_ops; Â Â Â Â Â dsi->dsi_host.dev = dev; @@ -1666,6 +1692,7 @@ static int vc4_dsi_dev_remove(struct platform_device *pdev) Â Â Â Â Â struct vc4_dsi *dsi = dev_get_drvdata(dev); Â Â Â Â Â mipi_dsi_host_unregister(&dsi->dsi_host); +Â Â Â kref_put(&dsi->kref, &vc4_dsi_release);
Maybe vc4_dsi_put() ?
No, wait. That's the release function.
It's confusing. I'd rename vc4_dsi_put() to vc4_dsi_release_action() and wrap those kref_get/kref_put calls in small helpers named vc4_dsi_get/vc4_dsi_put. That's more aligned to the usual naming conventions, I'd say.
Best regards Thomas
return 0; Â }
devm_pm_runtime_enable() simplifies the driver a bit since it will call pm_runtime_disable() automatically through a device-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_dsi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 282537f27b8e..741db2dce8ab 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1622,6 +1622,10 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
drm_encoder_helper_add(encoder, &vc4_dsi_encoder_helper_funcs);
+ ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + ret = drm_bridge_attach(encoder, dsi->bridge, NULL, 0); if (ret) return ret; @@ -1634,8 +1638,6 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
vc4_debugfs_add_regset32(drm, dsi->variant->debugfs_name, &dsi->regset);
- pm_runtime_enable(dev); - return 0; }
@@ -1645,8 +1647,6 @@ static void vc4_dsi_unbind(struct device *dev, struct device *master, struct vc4_dsi *dsi = dev_get_drvdata(dev); struct drm_encoder *encoder = &dsi->encoder.base;
- pm_runtime_disable(dev); - /* * Restore the bridge_chain so the bridge detach procedure can happen * normally.
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 6aadb65eb640..eb8ff7b258d1 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2833,9 +2833,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) struct device_node *ddc_node; int ret;
- vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL); + vc4_hdmi = drmm_kzalloc(drm, sizeof(*vc4_hdmi), GFP_KERNEL); if (!vc4_hdmi) return -ENOMEM; + mutex_init(&vc4_hdmi->mutex); spin_lock_init(&vc4_hdmi->hw_lock); INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
Am 10.06.22 um 11:28 schrieb Maxime Ripard:
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main
'most likely source'
device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
drivers/gpu/drm/vc4/vc4_hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 6aadb65eb640..eb8ff7b258d1 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2833,9 +2833,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) struct device_node *ddc_node; int ret;
- vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL);
- vc4_hdmi = drmm_kzalloc(drm, sizeof(*vc4_hdmi), GFP_KERNEL); if (!vc4_hdmi) return -ENOMEM;
- mutex_init(&vc4_hdmi->mutex); spin_lock_init(&vc4_hdmi->hw_lock); INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index eb8ff7b258d1..e5b6e35f57f6 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2921,12 +2921,15 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); }
- drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + ret = drmm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + goto err_put_runtime_pm; + drm_encoder_helper_add(encoder, &vc4_hdmi_encoder_helper_funcs);
ret = vc4_hdmi_connector_init(drm, vc4_hdmi); if (ret) - goto err_destroy_encoder; + goto err_put_runtime_pm;
ret = vc4_hdmi_hotplug_init(vc4_hdmi); if (ret) @@ -2954,8 +2957,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) vc4_hdmi_hotplug_exit(vc4_hdmi); err_destroy_conn: vc4_hdmi_connector_destroy(&vc4_hdmi->connector); -err_destroy_encoder: - drm_encoder_cleanup(encoder); +err_put_runtime_pm: pm_runtime_put_sync(dev); pm_runtime_disable(dev); err_put_ddc: @@ -2997,7 +2999,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, 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);
pm_runtime_disable(dev);
The current code will call drm_connector_unregister() and drm_connector_cleanup() when the device is unbound. However, by then, there might still be some references held to that connector, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index e5b6e35f57f6..63d9a91f5038 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -255,12 +255,6 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force) return connector_status_disconnected; }
-static void vc4_hdmi_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) { struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); @@ -368,7 +362,6 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector) static const struct drm_connector_funcs vc4_hdmi_connector_funcs = { .detect = vc4_hdmi_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = vc4_hdmi_connector_destroy, .reset = vc4_hdmi_connector_reset, .atomic_duplicate_state = vc4_hdmi_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -386,10 +379,13 @@ static int vc4_hdmi_connector_init(struct drm_device *dev, struct drm_encoder *encoder = &vc4_hdmi->encoder.base; int ret;
- drm_connector_init_with_ddc(dev, connector, - &vc4_hdmi_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA, - vc4_hdmi->ddc); + ret = drmm_connector_init_with_ddc(dev, connector, + &vc4_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + vc4_hdmi->ddc); + if (ret) + return ret; + drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs);
/* @@ -2933,7 +2929,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
ret = vc4_hdmi_hotplug_init(vc4_hdmi); if (ret) - goto err_destroy_conn; + goto err_put_runtime_pm;
ret = vc4_hdmi_cec_init(vc4_hdmi); if (ret) @@ -2955,8 +2951,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) 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_put_runtime_pm: pm_runtime_put_sync(dev); pm_runtime_disable(dev); @@ -2998,7 +2992,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, vc4_hdmi_audio_exit(vc4_hdmi); vc4_hdmi_cec_exit(vc4_hdmi); vc4_hdmi_hotplug_exit(vc4_hdmi); - vc4_hdmi_connector_destroy(&vc4_hdmi->connector);
pm_runtime_disable(dev);
The current code to unregister our ALSA device needs to be undone manually when we remove the HDMI driver.
Since ALSA doesn't seem to support any mechanism to defer freeing something until the last user of the ALSA device is gone, we can either use a device-managed or a DRM-managed action.
The consistent way would be to use a DRM-managed one, just like pretty much any framework-facing structure should be doing. However, ALSA does a lot of allocation and registration using device-managed calls. Thus, if we're going that way, by the time the DRM-managed action would run all of those allocation would have been freed and we would end up with a use-after-free.
Thus, let's do a device-managed action. It's been tested with KASAN enabled and doesn't seem to trigger any issue, so it's as good as anything.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 43 ++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 63d9a91f5038..8f71f5a5e4ce 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2021,6 +2021,14 @@ static struct hdmi_codec_pdata vc4_hdmi_codec_pdata = { .i2s = 1, };
+static void vc4_hdmi_audio_codec_release(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + platform_device_unregister(vc4_hdmi->audio.codec_pdev); + vc4_hdmi->audio.codec_pdev = NULL; +} + static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) { const struct vc4_hdmi_register *mai_data = @@ -2062,6 +2070,30 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; vc4_hdmi->audio.dma_data.maxburst = 2;
+ /* + * NOTE: Strictly speaking, we should probably use a DRM-managed + * registration there to avoid removing all the audio components + * by the time the driver doesn't have any user anymore. + * + * However, the ASoC core uses a number of devm_kzalloc calls + * when registering, even when using non-device-managed + * functions (such as in snd_soc_register_component()). + * + * If we call snd_soc_unregister_component() in a DRM-managed + * action, the device-managed actions have already been executed + * and thus we would access memory that has been freed. + * + * Using device-managed hooks here probably leaves us open to a + * bunch of issues if userspace still has a handle on the ALSA + * device when the device is removed. However, this is mitigated + * by the use of drm_dev_enter()/drm_dev_exit() in the audio + * path to prevent the access to the device resources if it + * isn't there anymore. + * + * Then, the vc4_hdmi structure is DRM-managed and thus only + * freed whenever the last user has closed the DRM device file. + * It should thus outlive ALSA in most situations. + */ ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0); if (ret) { dev_err(dev, "Could not register PCM component: %d\n", ret); @@ -2085,6 +2117,10 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) } vc4_hdmi->audio.codec_pdev = codec_pdev;
+ ret = devm_add_action_or_reset(dev, vc4_hdmi_audio_codec_release, vc4_hdmi); + if (ret) + return ret; + dai_link->cpus = &vc4_hdmi->audio.cpu; dai_link->codecs = &vc4_hdmi->audio.codec; dai_link->platforms = &vc4_hdmi->audio.platform; @@ -2123,12 +2159,6 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
}
-static void vc4_hdmi_audio_exit(struct vc4_hdmi *vc4_hdmi) -{ - platform_device_unregister(vc4_hdmi->audio.codec_pdev); - vc4_hdmi->audio.codec_pdev = NULL; -} - static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv) { struct vc4_hdmi *vc4_hdmi = priv; @@ -2989,7 +3019,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, kfree(vc4_hdmi->hdmi_regset.regs); kfree(vc4_hdmi->hd_regset.regs);
- vc4_hdmi_audio_exit(vc4_hdmi); vc4_hdmi_cec_exit(vc4_hdmi); vc4_hdmi_hotplug_exit(vc4_hdmi);
The current code to unregister our CEC device needs to be undone manually when we remove the HDMI driver.
Since the CEC framework will allocate its main structure, and will defer its deallocation to when the last user will have closed it, we don't really need to take any particular measure to prevent any use-after-free and can thus use any managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 95 ++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 45 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 8f71f5a5e4ce..402bfde3b5fe 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2541,6 +2541,14 @@ static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = { .adap_transmit = vc4_hdmi_cec_adap_transmit, };
+static void vc4_hdmi_cec_release(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + cec_unregister_adapter(vc4_hdmi->cec_adap); + vc4_hdmi->cec_adap = NULL; +} + static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) { struct cec_connector_info conn_info; @@ -2576,75 +2584,75 @@ 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 = 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); + ret = devm_request_threaded_irq(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); if (ret) goto err_delete_cec_adap;
- 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); + ret = devm_request_threaded_irq(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); if (ret) - goto err_remove_cec_rx_handler; + goto err_delete_cec_adap; } else { spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- ret = request_threaded_irq(platform_get_irq(pdev, 0), - vc4_cec_irq_handler, - vc4_cec_irq_handler_thread, 0, - "vc4 hdmi cec", vc4_hdmi); + ret = devm_request_threaded_irq(dev, 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_remove_handlers; + goto err_delete_cec_adap; + + /* + * NOTE: Strictly speaking, we should probably use a DRM-managed + * registration there to avoid removing the CEC adapter by the + * time the DRM driver doesn't have any user anymore. + * + * However, the CEC framework already cleans up the CEC adapter + * only when the last user has closed its file descriptor, so we + * don't need to handle it in DRM. + * + * By the time the device-managed hook is executed, we will give + * up our reference to the CEC adapter and therefore don't + * really care when it's actually freed. + * + * There's still a problematic sequence: if we unregister our + * CEC adapter, but the userspace keeps a handle on the CEC + * adapter but not the DRM device for some reason. In such a + * case, our vc4_hdmi structure will be freed, but the + * cec_adapter structure will have a dangling pointer to what + * used to be our HDMI controller. If we get a CEC call at that + * moment, we could end up with a use-after-free. Fortunately, + * the CEC framework already handles this too, by calling + * cec_is_registered() in cec_ioctl() and cec_poll(). + */ + ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi); + if (ret) + return ret;
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);
return ret; } - -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 static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) { return 0; } - -static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {}; - #endif
static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, @@ -2967,7 +2975,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
ret = vc4_hdmi_audio_init(vc4_hdmi); if (ret) - goto err_free_cec; + goto err_free_hotplug;
vc4_debugfs_add_file(drm, variant->debugfs_name, vc4_hdmi_debugfs_regs, @@ -2977,8 +2985,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
return 0;
-err_free_cec: - vc4_hdmi_cec_exit(vc4_hdmi); err_free_hotplug: vc4_hdmi_hotplug_exit(vc4_hdmi); err_put_runtime_pm: @@ -3019,7 +3025,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, kfree(vc4_hdmi->hdmi_regset.regs); kfree(vc4_hdmi->hd_regset.regs);
- vc4_hdmi_cec_exit(vc4_hdmi); vc4_hdmi_hotplug_exit(vc4_hdmi);
pm_runtime_disable(dev);
The reference to the DDC controller device needs to be put back when we're done with it. Let's use a device-managed action to simplify the driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 402bfde3b5fe..bb9bd0c701be 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2857,6 +2857,13 @@ static int vc4_hdmi_runtime_resume(struct device *dev) return 0; }
+static void vc4_hdmi_put_ddc_device(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + put_device(&vc4_hdmi->ddc->dev); +} + 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); @@ -2912,13 +2919,16 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -EPROBE_DEFER; }
+ ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc_device, vc4_hdmi); + if (ret) + return ret; + /* Only use the GPIO HPD pin if present in the DT, otherwise * we'll use the HDMI core's register. */ vc4_hdmi->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); if (IS_ERR(vc4_hdmi->hpd_gpio)) { - ret = PTR_ERR(vc4_hdmi->hpd_gpio); - goto err_put_ddc; + return PTR_ERR(vc4_hdmi->hpd_gpio); }
vc4_hdmi->disable_wifi_frequencies = @@ -2938,7 +2948,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) */ ret = vc4_hdmi_runtime_resume(dev); if (ret) - goto err_put_ddc; + return ret;
pm_runtime_get_noresume(dev); pm_runtime_set_active(dev); @@ -2990,8 +3000,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) err_put_runtime_pm: pm_runtime_put_sync(dev); pm_runtime_disable(dev); -err_put_ddc: - put_device(&vc4_hdmi->ddc->dev);
return ret; } @@ -3028,8 +3036,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, vc4_hdmi_hotplug_exit(vc4_hdmi);
pm_runtime_disable(dev); - - put_device(&vc4_hdmi->ddc->dev); }
static const struct component_ops vc4_hdmi_ops = {
The current code to build the registers set later exposed in debugfs for the HDMI controller relies on traditional allocations, that are later free'd as part of the driver unbind hook.
Since krealloc doesn't have a DRM-managed equivalent, let's add an action to free the buffer later on.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index bb9bd0c701be..ecc898684c4b 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2655,14 +2655,23 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) } #endif
+static void vc4_hdmi_free_regset(struct drm_device *drm, void *ptr) +{ + struct debugfs_reg32 *regs = ptr; + + kfree(regs); +} + static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, struct debugfs_regset32 *regset, enum vc4_hdmi_regs reg) { + struct drm_device *drm = vc4_hdmi->connector.dev; const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; struct debugfs_reg32 *regs, *new_regs; unsigned int count = 0; unsigned int i; + int ret;
regs = kcalloc(variant->num_registers, sizeof(*regs), GFP_KERNEL); @@ -2688,6 +2697,10 @@ static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, regset->regs = new_regs; regset->nregs = count;
+ ret = drmm_add_action_or_reset(drm, vc4_hdmi_free_regset, new_regs); + if (ret) + return ret; + return 0; }
@@ -3030,9 +3043,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); vc4_hdmi = dev_get_drvdata(dev);
- kfree(vc4_hdmi->hdmi_regset.regs); - kfree(vc4_hdmi->hd_regset.regs); - vc4_hdmi_hotplug_exit(vc4_hdmi);
pm_runtime_disable(dev);
Commit 776efe800fed ("drm/vc4: hdmi: Drop devm interrupt handler for hotplug interrupts") dropped the device-managed interrupt registration because it was creating bugs and races whenever an interrupt was coming in while the device was removed.
However, our latest patches to the HDMI controller driver fix this as well, so we can use device-managed interrupt handlers again.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 41 +++++++++------------------------- 1 file changed, 11 insertions(+), 30 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index ecc898684c4b..ca0bc8be3e6a 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2181,21 +2181,19 @@ static int vc4_hdmi_hotplug_init(struct vc4_hdmi *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); + ret = devm_request_threaded_irq(&pdev->dev, hpd_con, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd connected", vc4_hdmi); if (ret) return 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); + ret = devm_request_threaded_irq(&pdev->dev, hpd_rm, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd disconnected", vc4_hdmi); + if (ret) return ret; - }
connector->polled = DRM_CONNECTOR_POLL_HPD; } @@ -2203,16 +2201,6 @@ 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) { @@ -2994,11 +2982,11 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
ret = vc4_hdmi_cec_init(vc4_hdmi); if (ret) - goto err_free_hotplug; + goto err_put_runtime_pm;
ret = vc4_hdmi_audio_init(vc4_hdmi); if (ret) - goto err_free_hotplug; + goto err_put_runtime_pm;
vc4_debugfs_add_file(drm, variant->debugfs_name, vc4_hdmi_debugfs_regs, @@ -3008,8 +2996,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
return 0;
-err_free_hotplug: - vc4_hdmi_hotplug_exit(vc4_hdmi); err_put_runtime_pm: pm_runtime_put_sync(dev); pm_runtime_disable(dev); @@ -3020,8 +3006,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) static void vc4_hdmi_unbind(struct device *dev, struct device *master, void *data) { - struct vc4_hdmi *vc4_hdmi; - /* * ASoC makes it a bit hard to retrieve a pointer to the * vc4_hdmi structure. Registering the card will overwrite our @@ -3041,9 +3025,6 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master, */ BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); - vc4_hdmi = dev_get_drvdata(dev); - - vc4_hdmi_hotplug_exit(vc4_hdmi);
pm_runtime_disable(dev); }
The HDMI driver unbind hook doesn't have any ALSA-related code anymore, so let's move the ALSA sanity checks and comments we have to some other part of the driver dedicated to ALSA.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index ca0bc8be3e6a..814517c1fdaa 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -2041,6 +2041,26 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) int index; int ret;
+ /* + * ASoC makes it a bit hard to retrieve a pointer to the + * vc4_hdmi structure. Registering the card will overwrite our + * device drvdata with a pointer to the snd_soc_card structure, + * which can then be used to retrieve whatever drvdata we want + * to associate. + * + * However, that doesn't fly in the case where we wouldn't + * register an ASoC card (because of an old DT that is missing + * the dmas properties for example), then the card isn't + * registered and the device drvdata wouldn't be set. + * + * We can deal with both cases by making sure a snd_soc_card + * pointer and a vc4_hdmi structure are pointing to the same + * memory address, so we can treat them indistinctly without any + * issue. + */ + BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); + BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); + if (!of_find_property(dev->of_node, "dmas", NULL)) { dev_warn(dev, "'dmas' DT property is missing, no HDMI audio\n"); @@ -3006,26 +3026,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) static void vc4_hdmi_unbind(struct device *dev, struct device *master, void *data) { - /* - * ASoC makes it a bit hard to retrieve a pointer to the - * vc4_hdmi structure. Registering the card will overwrite our - * device drvdata with a pointer to the snd_soc_card structure, - * which can then be used to retrieve whatever drvdata we want - * to associate. - * - * However, that doesn't fly in the case where we wouldn't - * register an ASoC card (because of an old DT that is missing - * the dmas properties for example), then the card isn't - * registered and the device drvdata wouldn't be set. - * - * We can deal with both cases by making sure a snd_soc_card - * pointer and a vc4_hdmi structure are pointing to the same - * memory address, so we can treat them indistinctly without any - * issue. - */ - BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); - BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); - pm_runtime_disable(dev); }
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HDMI driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 286 +++++++++++++++++++++++++++++++-- 1 file changed, 269 insertions(+), 17 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 814517c1fdaa..b4fd581861ea 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -34,6 +34,7 @@ #include <drm/display/drm_hdmi_helper.h> #include <drm/display/drm_scdc_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h> #include <linux/clk.h> @@ -140,17 +141,33 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; struct vc4_hdmi *vc4_hdmi = node->info_ent->data; + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_printer p = drm_seq_file_printer(m); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV;
drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); drm_print_regset32(&p, &vc4_hdmi->hd_regset);
+ drm_dev_exit(idx); + return 0; }
static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + /* + * We can be called by our bind callback, when the + * connector->dev pointer might not be initialised yet. + */ + if (drm && !drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -167,11 +184,23 @@ static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + if (drm) + drm_dev_exit(idx); }
static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + /* + * We can be called by our bind callback, when the + * connector->dev pointer might not be initialised yet. + */ + if (drm && !drm_dev_enter(drm, &idx)) + return;
reset_control_reset(vc4_hdmi->reset);
@@ -183,15 +212,25 @@ static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + if (drm) + drm_dev_exit(idx); }
#ifdef CONFIG_DRM_VC4_HDMI_CEC static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) { - unsigned long cec_rate = clk_get_rate(vc4_hdmi->cec_clock); + struct drm_device *drm = vc4_hdmi->connector.dev; + unsigned long cec_rate; unsigned long flags; u16 clk_cnt; u32 value; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + + cec_rate = clk_get_rate(vc4_hdmi->cec_clock);
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -207,6 +246,8 @@ static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } #else static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {} @@ -427,25 +468,34 @@ static int vc4_hdmi_stop_packet(struct drm_encoder *encoder, bool poll) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = type - 0x80; unsigned long flags; + int ret = 0; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- if (!poll) - return 0; + if (poll) { + ret = wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & + BIT(packet_id)), 100); + }
- return wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & - BIT(packet_id)), 100); + drm_dev_exit(idx); + return ret; }
static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, union hdmi_infoframe *frame) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = frame->any.type - 0x80; const struct vc4_hdmi_register *ram_packet_start = &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START]; @@ -456,6 +506,10 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, unsigned long flags; ssize_t len, i; int ret; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE), @@ -463,12 +517,12 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); if (len < 0) - return; + goto out;
ret = vc4_hdmi_stop_packet(encoder, frame->any.type, true); if (ret) { DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret); - return; + goto out; }
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -497,6 +551,9 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, BIT(packet_id)), 100); if (ret) DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret); + +out: + drm_dev_exit(idx); }
static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, @@ -644,8 +701,10 @@ static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder, static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; unsigned long flags; + int idx;
lockdep_assert_held(&vc4_hdmi->mutex);
@@ -657,6 +716,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) vc4_hdmi->output_format)) return;
+ if (!drm_dev_enter(drm, &idx)) + return; + drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true); drm_scdc_set_scrambling(vc4_hdmi->ddc, true);
@@ -665,6 +727,8 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) VC5_HDMI_SCRAMBLER_CTL_ENABLE); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + vc4_hdmi->scdc_enabled = true;
queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work, @@ -674,7 +738,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx;
lockdep_assert_held(&vc4_hdmi->mutex);
@@ -686,6 +752,9 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) if (delayed_work_pending(&vc4_hdmi->scrambling_work)) cancel_delayed_work_sync(&vc4_hdmi->scrambling_work);
+ if (!drm_dev_enter(drm, &idx)) + return; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) & ~VC5_HDMI_SCRAMBLER_CTL_ENABLE); @@ -693,6 +762,8 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder)
drm_scdc_set_scrambling(vc4_hdmi->ddc, false); drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, false); + + drm_dev_exit(idx); }
static void vc4_hdmi_scrambling_wq(struct work_struct *work) @@ -715,10 +786,15 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0); @@ -736,6 +812,9 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
vc4_hdmi_disable_scrambling(encoder);
+ drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -743,11 +822,16 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; int ret; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX); @@ -763,6 +847,9 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, if (ret < 0) DRM_ERROR("Failed to release power domain: %d\n", ret);
+ drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -779,8 +866,13 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 csc_ctl; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -815,6 +907,8 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); }
/* @@ -899,6 +993,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); unsigned long flags; @@ -907,6 +1002,10 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, u32 csc_chan_ctl = 0; u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, VC5_MT_CP_CSC_CTL_MODE); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -949,12 +1048,15 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); }
static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; @@ -973,6 +1075,10 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, interlaced, VC4_HDMI_VERTB_VBP)); unsigned long flags; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1000,12 +1106,15 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_VERTB1, vertb);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); }
static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; const struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; @@ -1029,6 +1138,10 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, unsigned char gcp; bool gcp_en; u32 reg; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1100,13 +1213,20 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CLOCK_STOP, 0);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); }
static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 drift; int ret; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1135,12 +1255,15 @@ static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); WARN_ONCE(ret, "Timeout waiting for " "VC4_HDMI_FIFO_CTL_RECENTER_DONE"); + + drm_dev_exit(idx); }
static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); @@ -1151,9 +1274,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, unsigned long bvb_rate, hsm_rate; unsigned long flags; int ret; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + goto out; + /* * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must * be faster than pixel clock, infinitesimally faster, tested in @@ -1174,13 +1301,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate); if (ret) { DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); - goto out; + goto err_dev_exit; }
ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to retain power domain: %d\n", ret); - goto out; + goto err_dev_exit; }
ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate); @@ -1232,6 +1359,8 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, if (vc4_hdmi->variant->set_timings) vc4_hdmi->variant->set_timings(vc4_hdmi, conn_state, mode);
+ drm_dev_exit(idx); + mutex_unlock(&vc4_hdmi->mutex);
return; @@ -1240,6 +1369,8 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, clk_disable_unprepare(vc4_hdmi->pixel_clock); err_put_runtime_pm: pm_runtime_put(&vc4_hdmi->pdev->dev); +err_dev_exit: + drm_dev_exit(idx); out: mutex_unlock(&vc4_hdmi->mutex); return; @@ -1249,14 +1380,19 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); unsigned long flags; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + return; + if (vc4_hdmi->variant->csc_setup) vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode);
@@ -1264,6 +1400,8 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + mutex_unlock(&vc4_hdmi->mutex); }
@@ -1271,15 +1409,20 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_display_info *display = &vc4_hdmi->connector.display_info; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; unsigned long flags; int ret; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + return; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_VID_CTL, @@ -1340,6 +1483,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, vc4_hdmi_recenter_fifo(vc4_hdmi); vc4_hdmi_enable_scrambling(encoder);
+ drm_dev_exit(idx); mutex_unlock(&vc4_hdmi->mutex); }
@@ -1675,13 +1819,20 @@ static u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 hotplug; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return false;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); hotplug = HDMI_READ(HDMI_HOTPLUG); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + return !!(hotplug & VC4_HDMI_HOTPLUG_CONNECTED); }
@@ -1689,10 +1840,16 @@ static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate) { - u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); + struct drm_device *drm = vc4_hdmi->connector.dev; + u32 hsm_clock; unsigned long flags; unsigned long n, m; + int idx;
+ if (!drm_dev_enter(drm, &idx)) + return; + + hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); rational_best_approximation(hsm_clock, samplerate, VC4_HD_MAI_SMP_N_MASK >> VC4_HD_MAI_SMP_N_SHIFT, @@ -1705,6 +1862,8 @@ static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); }
static void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate) @@ -1764,13 +1923,21 @@ static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi) static int vc4_hdmi_audio_startup(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int ret = 0; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) { + ret = -ENODEV; + goto out; + } + if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) { - mutex_unlock(&vc4_hdmi->mutex); - return -ENODEV; + ret = -ENODEV; + goto out_dev_exit; }
vc4_hdmi->audio.streaming = true; @@ -1787,9 +1954,12 @@ static int vc4_hdmi_audio_startup(struct device *dev, void *data) if (vc4_hdmi->variant->phy_rng_enable) vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);
+out_dev_exit: + drm_dev_exit(idx); +out: mutex_unlock(&vc4_hdmi->mutex);
- return 0; + return ret; }
static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) @@ -1818,10 +1988,15 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx;
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_MAI_CTL, @@ -1837,6 +2012,9 @@ static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) vc4_hdmi->audio.streaming = false; vc4_hdmi_audio_reset(vc4_hdmi);
+ drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -1884,6 +2062,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, struct hdmi_codec_params *params) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_encoder *encoder = &vc4_hdmi->encoder.base; unsigned int sample_rate = params->sample_rate; unsigned int channels = params->channels; @@ -1892,15 +2071,22 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, u32 channel_map; u32 mai_audio_format; u32 mai_sample_rate; + int ret = 0; + int idx;
dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__, sample_rate, params->sample_width, channels);
mutex_lock(&vc4_hdmi->mutex);
+ if (!drm_dev_enter(drm, &idx)) { + ret = -ENODEV; + goto out; + } + if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) { - mutex_unlock(&vc4_hdmi->mutex); - return -EINVAL; + ret = -EINVAL; + goto out_dev_exit; }
vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate); @@ -1957,9 +2143,12 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, memcpy(&vc4_hdmi->audio.infoframe, ¶ms->cea, sizeof(params->cea)); vc4_hdmi_set_audio_infoframe(encoder);
+out_dev_exit: + drm_dev_exit(idx); +out: mutex_unlock(&vc4_hdmi->mutex);
- return 0; + return ret; }
static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = { @@ -2294,6 +2483,17 @@ static irqreturn_t vc4_cec_irq_handler_tx_bare_locked(struct vc4_hdmi *vc4_hdmi) { u32 cntrl1;
+ /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + lockdep_assert_held(&vc4_hdmi->hw_lock);
cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); @@ -2322,6 +2522,17 @@ static irqreturn_t vc4_cec_irq_handler_rx_bare_locked(struct vc4_hdmi *vc4_hdmi)
lockdep_assert_held(&vc4_hdmi->hw_lock);
+ /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + vc4_hdmi->cec_rx_msg.len = 0; cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); vc4_cec_read_msg(vc4_hdmi, cntrl1); @@ -2353,6 +2564,17 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) irqreturn_t ret; u32 cntrl5;
+ /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + if (!(stat & VC4_HDMI_CPU_CEC)) return IRQ_NONE;
@@ -2373,11 +2595,13 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) static int vc4_hdmi_cec_enable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; /* clock period in microseconds */ const u32 usecs = 1000000 / CEC_CLOCK_FREQ; unsigned long flags; u32 val; int ret; + int idx;
/* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2390,9 +2614,14 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap) * keep it in mind if we were to change that assumption. */
+ if (!drm_dev_enter(drm, &idx)) + return -ENODEV; + ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); - if (ret) + if (ret) { + drm_dev_exit(idx); return ret; + }
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -2428,13 +2657,20 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + return 0; }
static int vc4_hdmi_cec_disable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV;
/* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2459,6 +2695,8 @@ static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
pm_runtime_put(&vc4_hdmi->pdev->dev);
+ drm_dev_exit(idx); + return 0; }
@@ -2473,7 +2711,9 @@ static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx;
/* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2486,12 +2726,17 @@ static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) * keep it in mind if we were to change that assumption. */
+ if (!drm_dev_enter(drm, &idx)) + return -ENODEV; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_CEC_CNTRL_1, (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + return 0; }
@@ -2503,6 +2748,7 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, unsigned long flags; u32 val; unsigned int i; + int idx;
/* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2515,8 +2761,12 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, * keep it in mind if we were to change that assumption. */
+ if (!drm_dev_enter(dev, &idx)) + return -ENODEV; + if (msg->len > 16) { drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len); + drm_dev_exit(idx); return -ENOMEM; }
@@ -2540,6 +2790,8 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+ drm_dev_exit(idx); + return 0; }
Am 10.06.22 um 11:29 schrieb Maxime Ripard:
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the HDMI driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/vc4/vc4_hdmi.c | 286 +++++++++++++++++++++++++++++++-- 1 file changed, 269 insertions(+), 17 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 814517c1fdaa..b4fd581861ea 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -34,6 +34,7 @@ #include <drm/display/drm_hdmi_helper.h> #include <drm/display/drm_scdc_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h> #include <linux/clk.h> @@ -140,17 +141,33 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; struct vc4_hdmi *vc4_hdmi = node->info_ent->data;
struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_printer p = drm_seq_file_printer(m);
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); drm_print_regset32(&p, &vc4_hdmi->hd_regset);
drm_dev_exit(idx);
return 0; }
static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) {
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
/*
* We can be called by our bind callback, when the
* connector->dev pointer might not be initialised yet.
*/
if (drm && !drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -167,11 +184,23 @@ static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
if (drm)
drm_dev_exit(idx);
}
static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) {
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
/*
* We can be called by our bind callback, when the
* connector->dev pointer might not be initialised yet.
*/
if (drm && !drm_dev_enter(drm, &idx))
return;
reset_control_reset(vc4_hdmi->reset);
@@ -183,15 +212,25 @@ static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
if (drm)
drm_dev_exit(idx);
}
#ifdef CONFIG_DRM_VC4_HDMI_CEC static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {
- unsigned long cec_rate = clk_get_rate(vc4_hdmi->cec_clock);
struct drm_device *drm = vc4_hdmi->connector.dev;
unsigned long cec_rate; unsigned long flags; u16 clk_cnt; u32 value;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
cec_rate = clk_get_rate(vc4_hdmi->cec_clock);
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -207,6 +246,8 @@ static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_CEC_CNTRL_1, value);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- drm_dev_exit(idx); } #else static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {}
@@ -427,25 +468,34 @@ static int vc4_hdmi_stop_packet(struct drm_encoder *encoder, bool poll) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = type - 0x80; unsigned long flags;
int ret = 0;
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- if (!poll)
return 0;
- if (poll) {
ret = wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) &
BIT(packet_id)), 100);
- }
- return wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) &
BIT(packet_id)), 100);
drm_dev_exit(idx);
return ret; }
static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, union hdmi_infoframe *frame) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = frame->any.type - 0x80; const struct vc4_hdmi_register *ram_packet_start = &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START];
@@ -456,6 +506,10 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, unsigned long flags; ssize_t len, i; int ret;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE),
@@ -463,12 +517,12 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder,
len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); if (len < 0)
return;
goto out;
ret = vc4_hdmi_stop_packet(encoder, frame->any.type, true); if (ret) { DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret);
return;
goto out;
}
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -497,6 +551,9 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, BIT(packet_id)), 100); if (ret) DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret);
+out:
drm_dev_exit(idx); }
static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame,
@@ -644,8 +701,10 @@ static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder, static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; unsigned long flags;
int idx;
lockdep_assert_held(&vc4_hdmi->mutex);
@@ -657,6 +716,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) vc4_hdmi->output_format)) return;
- if (!drm_dev_enter(drm, &idx))
return;
- drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true); drm_scdc_set_scrambling(vc4_hdmi->ddc, true);
@@ -665,6 +727,8 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) VC5_HDMI_SCRAMBLER_CTL_ENABLE); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx);
vc4_hdmi->scdc_enabled = true;
queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work,
@@ -674,7 +738,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
lockdep_assert_held(&vc4_hdmi->mutex);
@@ -686,6 +752,9 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) if (delayed_work_pending(&vc4_hdmi->scrambling_work)) cancel_delayed_work_sync(&vc4_hdmi->scrambling_work);
- if (!drm_dev_enter(drm, &idx))
return;
- spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) & ~VC5_HDMI_SCRAMBLER_CTL_ENABLE);
@@ -693,6 +762,8 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder)
drm_scdc_set_scrambling(vc4_hdmi->ddc, false); drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, false);
drm_dev_exit(idx); }
static void vc4_hdmi_scrambling_wq(struct work_struct *work)
@@ -715,10 +786,15 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
goto out;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0);
@@ -736,6 +812,9 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
vc4_hdmi_disable_scrambling(encoder);
- drm_dev_exit(idx);
+out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -743,11 +822,16 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; int ret;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
goto out;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX);
@@ -763,6 +847,9 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, if (ret < 0) DRM_ERROR("Failed to release power domain: %d\n", ret);
- drm_dev_exit(idx);
+out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -779,8 +866,13 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) {
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 csc_ctl;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -815,6 +907,8 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx); }
/*
@@ -899,6 +993,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) {
- struct drm_device *drm = vc4_hdmi->connector.dev; struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); unsigned long flags;
@@ -907,6 +1002,10 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, u32 csc_chan_ctl = 0; u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, VC5_MT_CP_CSC_CTL_MODE);
int idx;
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -949,12 +1048,15 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx); }
static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) {
struct drm_device *drm = vc4_hdmi->connector.dev; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE;
@@ -973,6 +1075,10 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, interlaced, VC4_HDMI_VERTB_VBP)); unsigned long flags;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1000,12 +1106,15 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_VERTB1, vertb);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx); }
static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) {
struct drm_device *drm = vc4_hdmi->connector.dev; const struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC;
@@ -1029,6 +1138,10 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, unsigned char gcp; bool gcp_en; u32 reg;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1100,13 +1213,20 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CLOCK_STOP, 0);
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx); }
static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) {
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 drift; int ret;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -1135,12 +1255,15 @@ static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); WARN_ONCE(ret, "Timeout waiting for " "VC4_HDMI_FIFO_CTL_RECENTER_DONE");
drm_dev_exit(idx); }
static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector);
@@ -1151,9 +1274,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, unsigned long bvb_rate, hsm_rate; unsigned long flags; int ret;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
goto out;
/*
- As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must
- be faster than pixel clock, infinitesimally faster, tested in
@@ -1174,13 +1301,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate); if (ret) { DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
goto out;
goto err_dev_exit;
}
ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to retain power domain: %d\n", ret);
goto out;
goto err_dev_exit;
}
ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate);
@@ -1232,6 +1359,8 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, if (vc4_hdmi->variant->set_timings) vc4_hdmi->variant->set_timings(vc4_hdmi, conn_state, mode);
drm_dev_exit(idx);
mutex_unlock(&vc4_hdmi->mutex);
return;
@@ -1240,6 +1369,8 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, clk_disable_unprepare(vc4_hdmi->pixel_clock); err_put_runtime_pm: pm_runtime_put(&vc4_hdmi->pdev->dev); +err_dev_exit:
- drm_dev_exit(idx); out: mutex_unlock(&vc4_hdmi->mutex); return;
@@ -1249,14 +1380,19 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); unsigned long flags;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
return;
if (vc4_hdmi->variant->csc_setup) vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode);
@@ -1264,6 +1400,8 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- drm_dev_exit(idx);
- mutex_unlock(&vc4_hdmi->mutex); }
@@ -1271,15 +1409,20 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_display_info *display = &vc4_hdmi->connector.display_info; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; unsigned long flags; int ret;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
return;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_VID_CTL,
@@ -1340,6 +1483,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, vc4_hdmi_recenter_fifo(vc4_hdmi); vc4_hdmi_enable_scrambling(encoder);
- drm_dev_exit(idx); mutex_unlock(&vc4_hdmi->mutex); }
@@ -1675,13 +1819,20 @@ static u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask)
static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) {
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 hotplug;
int idx;
if (!drm_dev_enter(drm, &idx))
return false;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); hotplug = HDMI_READ(HDMI_HOTPLUG); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx);
return !!(hotplug & VC4_HDMI_HOTPLUG_CONNECTED); }
@@ -1689,10 +1840,16 @@ static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate) {
- u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock);
struct drm_device *drm = vc4_hdmi->connector.dev;
u32 hsm_clock; unsigned long flags; unsigned long n, m;
int idx;
if (!drm_dev_enter(drm, &idx))
return;
hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); rational_best_approximation(hsm_clock, samplerate, VC4_HD_MAI_SMP_N_MASK >> VC4_HD_MAI_SMP_N_SHIFT,
@@ -1705,6 +1862,8 @@ static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx); }
static void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate)
@@ -1764,13 +1923,21 @@ static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi) static int vc4_hdmi_audio_startup(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int ret = 0;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx)) {
ret = -ENODEV;
goto out;
}
if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
mutex_unlock(&vc4_hdmi->mutex);
return -ENODEV;
ret = -ENODEV;
goto out_dev_exit;
}
vc4_hdmi->audio.streaming = true;
@@ -1787,9 +1954,12 @@ static int vc4_hdmi_audio_startup(struct device *dev, void *data) if (vc4_hdmi->variant->phy_rng_enable) vc4_hdmi->variant->phy_rng_enable(vc4_hdmi);
+out_dev_exit:
- drm_dev_exit(idx);
+out: mutex_unlock(&vc4_hdmi->mutex);
- return 0;
return ret; }
static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi)
@@ -1818,10 +1988,15 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx))
goto out;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
HDMI_WRITE(HDMI_MAI_CTL,
@@ -1837,6 +2012,9 @@ static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) vc4_hdmi->audio.streaming = false; vc4_hdmi_audio_reset(vc4_hdmi);
- drm_dev_exit(idx);
+out: mutex_unlock(&vc4_hdmi->mutex); }
@@ -1884,6 +2062,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, struct hdmi_codec_params *params) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
- struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_encoder *encoder = &vc4_hdmi->encoder.base; unsigned int sample_rate = params->sample_rate; unsigned int channels = params->channels;
@@ -1892,15 +2071,22 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, u32 channel_map; u32 mai_audio_format; u32 mai_sample_rate;
int ret = 0;
int idx;
dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__, sample_rate, params->sample_width, channels);
mutex_lock(&vc4_hdmi->mutex);
if (!drm_dev_enter(drm, &idx)) {
ret = -ENODEV;
goto out;
}
if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) {
mutex_unlock(&vc4_hdmi->mutex);
return -EINVAL;
ret = -EINVAL;
goto out_dev_exit;
}
vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate);
@@ -1957,9 +2143,12 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, memcpy(&vc4_hdmi->audio.infoframe, ¶ms->cea, sizeof(params->cea)); vc4_hdmi_set_audio_infoframe(encoder);
+out_dev_exit:
- drm_dev_exit(idx);
+out: mutex_unlock(&vc4_hdmi->mutex);
- return 0;
return ret; }
static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = {
@@ -2294,6 +2483,17 @@ static irqreturn_t vc4_cec_irq_handler_tx_bare_locked(struct vc4_hdmi *vc4_hdmi) { u32 cntrl1;
/*
* We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
lockdep_assert_held(&vc4_hdmi->hw_lock);
cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1);
@@ -2322,6 +2522,17 @@ static irqreturn_t vc4_cec_irq_handler_rx_bare_locked(struct vc4_hdmi *vc4_hdmi)
lockdep_assert_held(&vc4_hdmi->hw_lock);
- /*
* We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
- vc4_hdmi->cec_rx_msg.len = 0; cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); vc4_cec_read_msg(vc4_hdmi, cntrl1);
@@ -2353,6 +2564,17 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) irqreturn_t ret; u32 cntrl5;
- /*
* We don't need to protect the register access using
* drm_dev_enter() there because the interrupt handler lifetime
* is tied to the device itself, and not to the DRM device.
*
* So when the device will be gone, one of the first thing we
* will be doing will be to unregister the interrupt handler,
* and then unregister the DRM device. drm_dev_enter() would
* thus always succeed if we are here.
*/
- if (!(stat & VC4_HDMI_CPU_CEC)) return IRQ_NONE;
@@ -2373,11 +2595,13 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) static int vc4_hdmi_cec_enable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct drm_device *drm = vc4_hdmi->connector.dev; /* clock period in microseconds */ const u32 usecs = 1000000 / CEC_CLOCK_FREQ; unsigned long flags; u32 val; int ret;
int idx;
/*
- NOTE: This function should really take vc4_hdmi->mutex, but doing so
@@ -2390,9 +2614,14 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap) * keep it in mind if we were to change that assumption. */
- if (!drm_dev_enter(drm, &idx))
return -ENODEV;
- ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
- if (ret)
if (ret) {
drm_dev_exit(idx);
return ret;
}
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
@@ -2428,13 +2657,20 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap)
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx);
return 0; }
static int vc4_hdmi_cec_disable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
/*
- NOTE: This function should really take vc4_hdmi->mutex, but doing so
@@ -2459,6 +2695,8 @@ static int vc4_hdmi_cec_disable(struct cec_adapter *adap)
pm_runtime_put(&vc4_hdmi->pdev->dev);
- drm_dev_exit(idx);
- return 0; }
@@ -2473,7 +2711,9 @@ static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap);
struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags;
int idx;
/*
- NOTE: This function should really take vc4_hdmi->mutex, but doing so
@@ -2486,12 +2726,17 @@ static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) * keep it in mind if we were to change that assumption. */
if (!drm_dev_enter(drm, &idx))
return -ENODEV;
spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_CEC_CNTRL_1, (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
drm_dev_exit(idx);
return 0; }
@@ -2503,6 +2748,7 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, unsigned long flags; u32 val; unsigned int i;
int idx;
/*
- NOTE: This function should really take vc4_hdmi->mutex, but doing so
@@ -2515,8 +2761,12 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, * keep it in mind if we were to change that assumption. */
- if (!drm_dev_enter(dev, &idx))
return -ENODEV;
- if (msg->len > 16) { drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len);
return -ENOMEM; }drm_dev_exit(idx);
@@ -2540,6 +2790,8 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
- drm_dev_exit(idx);
- return 0; }
devm_pm_runtime_enable() simplifies the driver a bit since it will call pm_runtime_disable() automatically through a device-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_hdmi.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index b4fd581861ea..b31487547070 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3225,7 +3225,12 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
pm_runtime_get_noresume(dev); pm_runtime_set_active(dev); - pm_runtime_enable(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) { + vc4_hdmi_runtime_suspend(dev); + return ret; + }
if (vc4_hdmi->variant->reset) vc4_hdmi->variant->reset(vc4_hdmi); @@ -3270,20 +3275,12 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
err_put_runtime_pm: pm_runtime_put_sync(dev); - pm_runtime_disable(dev);
return ret; }
-static void vc4_hdmi_unbind(struct device *dev, struct device *master, - void *data) -{ - pm_runtime_disable(dev); -} - static const struct component_ops vc4_hdmi_ops = { .bind = vc4_hdmi_bind, - .unbind = vc4_hdmi_unbind, };
static int vc4_hdmi_dev_probe(struct platform_device *pdev)
There's no user for that pointer so let's just get rid of it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.h | 1 - drivers/gpu/drm/vc4/vc4_txp.c | 6 ------ 2 files changed, 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 846f3cda179a..5f2d7640a09d 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -80,7 +80,6 @@ struct vc4_dev { struct vc4_hvs *hvs; struct vc4_v3d *v3d; struct vc4_vec *vec; - struct vc4_txp *txp;
struct vc4_hang_state *hang_state;
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index f306e05ac5b2..87c896f482fb 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -468,7 +468,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_crtc *vc4_crtc; struct vc4_txp *txp; struct drm_crtc *crtc; @@ -521,7 +520,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) return ret;
dev_set_drvdata(dev, txp); - vc4->txp = txp;
vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset);
@@ -531,13 +529,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) static void vc4_txp_unbind(struct device *dev, struct device *master, void *data) { - struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_txp *txp = dev_get_drvdata(dev);
vc4_txp_connector_destroy(&txp->connector.base); - - vc4->txp = NULL; }
static const struct component_ops vc4_txp_ops = {
Am 10.06.22 um 11:29 schrieb Maxime Ripard:
There's no user for that pointer so let's just get rid of it.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/vc4/vc4_drv.h | 1 - drivers/gpu/drm/vc4/vc4_txp.c | 6 ------ 2 files changed, 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 846f3cda179a..5f2d7640a09d 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -80,7 +80,6 @@ struct vc4_dev { struct vc4_hvs *hvs; struct vc4_v3d *v3d; struct vc4_vec *vec;
struct vc4_txp *txp;
struct vc4_hang_state *hang_state;
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index f306e05ac5b2..87c896f482fb 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -468,7 +468,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master);
- struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_crtc *vc4_crtc; struct vc4_txp *txp; struct drm_crtc *crtc;
@@ -521,7 +520,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) return ret;
dev_set_drvdata(dev, txp);
vc4->txp = txp;
vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset);
@@ -531,13 +529,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) static void vc4_txp_unbind(struct device *dev, struct device *master, void *data) {
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_txp *txp = dev_get_drvdata(dev);
vc4_txp_connector_destroy(&txp->connector.base);
vc4->txp = NULL; }
static const struct component_ops vc4_txp_ops = {
There's already a regset in the vc4_crtc structure so there's no need to duplicate it in vc4_txp.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_txp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 87c896f482fb..51ac01838093 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -154,7 +154,6 @@ struct vc4_txp { struct drm_writeback_connector connector;
void __iomem *regs; - struct debugfs_regset32 regset; };
static inline struct vc4_txp *encoder_to_vc4_txp(struct drm_encoder *encoder) @@ -493,9 +492,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) txp->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(txp->regs)) return PTR_ERR(txp->regs); - txp->regset.base = txp->regs; - txp->regset.regs = txp_regs; - txp->regset.nregs = ARRAY_SIZE(txp_regs); + vc4_crtc->regset.base = txp->regs; + vc4_crtc->regset.regs = txp_regs; + vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs);
drm_connector_helper_add(&txp->connector.base, &vc4_txp_connector_helper_funcs); @@ -521,7 +520,7 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, txp);
- vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset); + vc4_debugfs_add_regset32(drm, "txp_regs", &vc4_crtc->regset);
return 0; }
Am 10.06.22 um 11:29 schrieb Maxime Ripard:
There's already a regset in the vc4_crtc structure so there's no need to duplicate it in vc4_txp.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/vc4/vc4_txp.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 87c896f482fb..51ac01838093 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -154,7 +154,6 @@ struct vc4_txp { struct drm_writeback_connector connector;
void __iomem *regs;
struct debugfs_regset32 regset; };
static inline struct vc4_txp *encoder_to_vc4_txp(struct drm_encoder *encoder)
@@ -493,9 +492,9 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) txp->regs = vc4_ioremap_regs(pdev, 0); if (IS_ERR(txp->regs)) return PTR_ERR(txp->regs);
- txp->regset.base = txp->regs;
- txp->regset.regs = txp_regs;
- txp->regset.nregs = ARRAY_SIZE(txp_regs);
vc4_crtc->regset.base = txp->regs;
vc4_crtc->regset.regs = txp_regs;
vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs);
drm_connector_helper_add(&txp->connector.base, &vc4_txp_connector_helper_funcs);
@@ -521,7 +520,7 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, txp);
- vc4_debugfs_add_regset32(drm, "txp_regs", &txp->regset);
vc4_debugfs_add_regset32(drm, "txp_regs", &vc4_crtc->regset);
return 0; }
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_txp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 51ac01838093..6a16b2798724 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -477,7 +477,7 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) if (irq < 0) return irq;
- txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL); + txp = drmm_kzalloc(drm, sizeof(*txp), GFP_KERNEL); if (!txp) return -ENOMEM; vc4_crtc = &txp->base;
Am 10.06.22 um 11:29 schrieb Maxime Ripard:
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech
Acked-by: Thomas Zimmermann tzimmermann@suse.de
drivers/gpu/drm/vc4/vc4_txp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 51ac01838093..6a16b2798724 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -477,7 +477,7 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) if (irq < 0) return irq;
- txp = devm_kzalloc(dev, sizeof(*txp), GFP_KERNEL);
- txp = drmm_kzalloc(drm, sizeof(*txp), GFP_KERNEL); if (!txp) return -ENOMEM; vc4_crtc = &txp->base;
The current code will call drm_connector_unregister() and drm_connector_cleanup() when the device is unbound. However, by then, there might still be some references held to that connector, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_txp.c | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 6a16b2798724..3f214b702c47 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -335,16 +335,9 @@ vc4_txp_connector_detect(struct drm_connector *connector, bool force) return connector_status_connected; }
-static void vc4_txp_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - static const struct drm_connector_funcs vc4_txp_connector_funcs = { .detect = vc4_txp_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = vc4_txp_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -498,10 +491,10 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
drm_connector_helper_add(&txp->connector.base, &vc4_txp_connector_helper_funcs); - ret = drm_writeback_connector_init(drm, &txp->connector, - &vc4_txp_connector_funcs, - &vc4_txp_encoder_helper_funcs, - drm_fmts, ARRAY_SIZE(drm_fmts)); + ret = drmm_writeback_connector_init(drm, &txp->connector, + &vc4_txp_connector_funcs, + &vc4_txp_encoder_helper_funcs, + drm_fmts, ARRAY_SIZE(drm_fmts)); if (ret) return ret;
@@ -525,17 +518,8 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data) return 0; }
-static void vc4_txp_unbind(struct device *dev, struct device *master, - void *data) -{ - struct vc4_txp *txp = dev_get_drvdata(dev); - - vc4_txp_connector_destroy(&txp->connector.base); -} - static const struct component_ops vc4_txp_ops = { .bind = vc4_txp_bind, - .unbind = vc4_txp_unbind, };
static int vc4_txp_probe(struct platform_device *pdev)
Our current code now mixes some resources whose lifetime are tied to the device (clocks, IO mappings, etc.) and some that are tied to the DRM device (encoder, bridge).
The device one will be freed at unbind time, but the DRM one will only be freed when the last user of the DRM device closes its file handle.
So we end up with a time window during which we can call the encoder hooks, but we don't have access to the underlying resources and device.
Let's protect all those sections with drm_dev_enter() and drm_dev_exit() so that we bail out if we are during that window.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_txp.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index 3f214b702c47..fee00b7003ab 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -15,6 +15,7 @@
#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_fb_cma_helper.h> #include <drm/drm_fourcc.h> @@ -274,6 +275,7 @@ static int vc4_txp_connector_atomic_check(struct drm_connector *conn, static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, struct drm_atomic_state *state) { + struct drm_device *drm = conn->dev; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, conn); struct vc4_txp *txp = connector_to_vc4_txp(conn); @@ -281,6 +283,7 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, struct drm_display_mode *mode; struct drm_framebuffer *fb; u32 ctrl; + int idx; int i;
if (WARN_ON(!conn_state->writeback_job)) @@ -310,6 +313,9 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, */ ctrl |= TXP_ALPHA_INVERT;
+ if (!drm_dev_enter(drm, &idx)) + return; + gem = drm_fb_cma_get_gem_obj(fb, 0); TXP_WRITE(TXP_DST_PTR, gem->paddr + fb->offsets[0]); TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]); @@ -320,6 +326,8 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn, TXP_WRITE(TXP_DST_CTRL, ctrl);
drm_writeback_queue_job(&txp->connector, conn_state); + + drm_dev_exit(idx); }
static const struct drm_connector_helper_funcs vc4_txp_connector_helper_funcs = { @@ -345,7 +353,12 @@ static const struct drm_connector_funcs vc4_txp_connector_funcs = {
static void vc4_txp_encoder_disable(struct drm_encoder *encoder) { + struct drm_device *drm = encoder->dev; struct vc4_txp *txp = encoder_to_vc4_txp(encoder); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return;
if (TXP_READ(TXP_DST_CTRL) & TXP_BUSY) { unsigned long timeout = jiffies + msecs_to_jiffies(1000); @@ -360,6 +373,8 @@ static void vc4_txp_encoder_disable(struct drm_encoder *encoder) }
TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN); + + drm_dev_exit(idx); }
static const struct drm_encoder_helper_funcs vc4_txp_encoder_helper_funcs = { @@ -443,6 +458,16 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data) struct vc4_txp *txp = data; struct vc4_crtc *vc4_crtc = &txp->base;
+ /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ TXP_WRITE(TXP_DST_CTRL, TXP_READ(TXP_DST_CTRL) & ~TXP_EI); vc4_crtc_handle_vblank(vc4_crtc); drm_writeback_signal_completion(&txp->connector, 0);
There's no user for that pointer so let's just get rid of it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_drv.h | 1 - drivers/gpu/drm/vc4/vc4_vec.c | 7 ------- 2 files changed, 8 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 5f2d7640a09d..12ab6df30629 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -79,7 +79,6 @@ struct vc4_dev {
struct vc4_hvs *hvs; struct vc4_v3d *v3d; - struct vc4_vec *vec;
struct vc4_hang_state *hang_state;
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index 11fc3d6f66b1..99fe40c8cf81 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -532,7 +532,6 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_vec *vec; struct vc4_vec_encoder *vc4_vec_encoder; int ret; @@ -585,8 +584,6 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, vec);
- vc4->vec = vec; - vc4_debugfs_add_regset32(drm, "vec_regs", &vec->regset);
return 0; @@ -601,15 +598,11 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) static void vc4_vec_unbind(struct device *dev, struct device *master, void *data) { - struct drm_device *drm = dev_get_drvdata(master); - struct vc4_dev *vc4 = to_vc4_dev(drm); struct vc4_vec *vec = dev_get_drvdata(dev);
vc4_vec_connector_destroy(vec->connector); drm_encoder_cleanup(vec->encoder); pm_runtime_disable(dev); - - vc4->vec = NULL; }
static const struct component_ops vc4_vec_ops = {
The VC4 VEC driver private structure contains only a pointer to the encoder and connector it implements. This makes the overall structure somewhat inconsistent with the rest of the driver, and complicates its initialisation without any apparent gain.
Let's embed the drm_encoder structure (through the vc4_encoder one) and drm_connector into struct vc4_vec to fix both issues.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 83 +++++++++-------------------------- 1 file changed, 21 insertions(+), 62 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index 99fe40c8cf81..2c96d5adcf49 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -160,12 +160,12 @@ struct vc4_vec_variant {
/* General VEC hardware state. */ struct vc4_vec { + struct vc4_encoder encoder; + struct drm_connector connector; + struct platform_device *pdev; const struct vc4_vec_variant *variant;
- struct drm_encoder *encoder; - struct drm_connector *connector; - void __iomem *regs;
struct clk *clock; @@ -178,30 +178,12 @@ struct vc4_vec { #define VEC_READ(offset) readl(vec->regs + (offset)) #define VEC_WRITE(offset, val) writel(val, vec->regs + (offset))
-/* VC4 VEC encoder KMS struct */ -struct vc4_vec_encoder { - struct vc4_encoder base; - struct vc4_vec *vec; -}; - -static inline struct vc4_vec_encoder * -to_vc4_vec_encoder(struct drm_encoder *encoder) +static inline struct vc4_vec * +encoder_to_vc4_vec(struct drm_encoder *encoder) { - return container_of(encoder, struct vc4_vec_encoder, base.base); + return container_of(encoder, struct vc4_vec, encoder.base); }
-/* VC4 VEC connector KMS struct */ -struct vc4_vec_connector { - struct drm_connector base; - struct vc4_vec *vec; - - /* Since the connector is attached to just the one encoder, - * this is the reference to it so we can do the best_encoder() - * hook. - */ - struct drm_encoder *encoder; -}; - enum vc4_vec_tv_mode_id { VC4_VEC_TV_MODE_NTSC, VC4_VEC_TV_MODE_NTSC_J, @@ -343,22 +325,12 @@ static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = .get_modes = vc4_vec_connector_get_modes, };
-static struct drm_connector *vc4_vec_connector_init(struct drm_device *dev, - struct vc4_vec *vec) +static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec) { - struct drm_connector *connector = NULL; - struct vc4_vec_connector *vec_connector; + struct drm_connector *connector = &vec->connector;
- vec_connector = devm_kzalloc(dev->dev, sizeof(*vec_connector), - GFP_KERNEL); - if (!vec_connector) - return ERR_PTR(-ENOMEM); - - connector = &vec_connector->base; connector->interlace_allowed = true;
- vec_connector->encoder = vec->encoder; - vec_connector->vec = vec;
drm_connector_init(dev, connector, &vc4_vec_connector_funcs, DRM_MODE_CONNECTOR_Composite); @@ -369,15 +341,14 @@ static struct drm_connector *vc4_vec_connector_init(struct drm_device *dev, VC4_VEC_TV_MODE_NTSC); vec->tv_mode = &vc4_vec_tv_modes[VC4_VEC_TV_MODE_NTSC];
- drm_connector_attach_encoder(connector, vec->encoder); + drm_connector_attach_encoder(connector, &vec->encoder.base);
- return connector; + return 0; }
static void vc4_vec_encoder_disable(struct drm_encoder *encoder) { - struct vc4_vec_encoder *vc4_vec_encoder = to_vc4_vec_encoder(encoder); - struct vc4_vec *vec = vc4_vec_encoder->vec; + struct vc4_vec *vec = encoder_to_vc4_vec(encoder); int ret;
VEC_WRITE(VEC_CFG, 0); @@ -398,8 +369,7 @@ static void vc4_vec_encoder_disable(struct drm_encoder *encoder)
static void vc4_vec_encoder_enable(struct drm_encoder *encoder) { - struct vc4_vec_encoder *vc4_vec_encoder = to_vc4_vec_encoder(encoder); - struct vc4_vec *vec = vc4_vec_encoder->vec; + struct vc4_vec *vec = encoder_to_vc4_vec(encoder); int ret;
ret = pm_runtime_get_sync(&vec->pdev->dev); @@ -474,8 +444,7 @@ static void vc4_vec_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { - struct vc4_vec_encoder *vc4_vec_encoder = to_vc4_vec_encoder(encoder); - struct vc4_vec *vec = vc4_vec_encoder->vec; + struct vc4_vec *vec = encoder_to_vc4_vec(encoder);
vec->tv_mode = &vc4_vec_tv_modes[conn_state->tv.mode]; } @@ -533,7 +502,6 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_vec *vec; - struct vc4_vec_encoder *vc4_vec_encoder; int ret;
ret = drm_mode_create_tv_properties(drm, ARRAY_SIZE(tv_mode_names), @@ -545,14 +513,7 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) if (!vec) return -ENOMEM;
- vc4_vec_encoder = devm_kzalloc(dev, sizeof(*vc4_vec_encoder), - GFP_KERNEL); - if (!vc4_vec_encoder) - return -ENOMEM; - vc4_vec_encoder->base.type = VC4_ENCODER_TYPE_VEC; - vc4_vec_encoder->vec = vec; - vec->encoder = &vc4_vec_encoder->base.base; - + vec->encoder.type = VC4_ENCODER_TYPE_VEC; vec->pdev = pdev; vec->variant = (const struct vc4_vec_variant *) of_device_get_match_data(dev); @@ -573,14 +534,12 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
pm_runtime_enable(dev);
- drm_simple_encoder_init(drm, vec->encoder, DRM_MODE_ENCODER_TVDAC); - drm_encoder_helper_add(vec->encoder, &vc4_vec_encoder_helper_funcs); + drm_simple_encoder_init(drm, &vec->encoder.base, DRM_MODE_ENCODER_TVDAC); + drm_encoder_helper_add(&vec->encoder.base, &vc4_vec_encoder_helper_funcs);
- vec->connector = vc4_vec_connector_init(drm, vec); - if (IS_ERR(vec->connector)) { - ret = PTR_ERR(vec->connector); + ret = vc4_vec_connector_init(drm, vec); + if (ret) goto err_destroy_encoder; - }
dev_set_drvdata(dev, vec);
@@ -589,7 +548,7 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) return 0;
err_destroy_encoder: - drm_encoder_cleanup(vec->encoder); + drm_encoder_cleanup(&vec->encoder.base); pm_runtime_disable(dev);
return ret; @@ -600,8 +559,8 @@ static void vc4_vec_unbind(struct device *dev, struct device *master, { struct vc4_vec *vec = dev_get_drvdata(dev);
- vc4_vec_connector_destroy(vec->connector); - drm_encoder_cleanup(vec->encoder); + vc4_vec_connector_destroy(&vec->connector); + drm_encoder_cleanup(&vec->encoder.base); pm_runtime_disable(dev); }
Our internal structure that stores the DRM entities structure is allocated through a device-managed kzalloc.
This means that this will eventually be freed whenever the device is removed. In our case, the most like source of removal is that the main device is going to be unbound, and component_unbind_all() is being run.
However, it occurs while the DRM device is still registered, which will create dangling pointers, eventually resulting in use-after-free.
Switch to a DRM-managed allocation to keep our structure until the DRM driver doesn't need it anymore.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index 2c96d5adcf49..a051b25337c0 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -509,7 +509,7 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- vec = devm_kzalloc(dev, sizeof(*vec), GFP_KERNEL); + vec = drmm_kzalloc(drm, sizeof(*vec), GFP_KERNEL); if (!vec) return -ENOMEM;
The current code will call drm_encoder_cleanup() when the device is unbound. However, by then, there might still be some references held to that encoder, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index a051b25337c0..3ccbe34b22ea 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -534,12 +534,15 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
pm_runtime_enable(dev);
- drm_simple_encoder_init(drm, &vec->encoder.base, DRM_MODE_ENCODER_TVDAC); + ret = drmm_simple_encoder_init(drm, &vec->encoder.base, DRM_MODE_ENCODER_TVDAC); + if (ret) + goto err_put_runtime_pm; + drm_encoder_helper_add(&vec->encoder.base, &vc4_vec_encoder_helper_funcs);
ret = vc4_vec_connector_init(drm, vec); if (ret) - goto err_destroy_encoder; + goto err_put_runtime_pm;
dev_set_drvdata(dev, vec);
@@ -547,8 +550,7 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
return 0;
-err_destroy_encoder: - drm_encoder_cleanup(&vec->encoder.base); +err_put_runtime_pm: pm_runtime_disable(dev);
return ret; @@ -560,7 +562,6 @@ static void vc4_vec_unbind(struct device *dev, struct device *master, struct vc4_vec *vec = dev_get_drvdata(dev);
vc4_vec_connector_destroy(&vec->connector); - drm_encoder_cleanup(&vec->encoder.base); pm_runtime_disable(dev); }
The current code will call drm_connector_unregister() and drm_connector_cleanup() when the device is unbound. However, by then, there might still be some references held to that connector, including by the userspace that might still have the DRM device open.
Let's switch to a DRM-managed initialization to clean up after ourselves only once the DRM device has been last closed.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index 3ccbe34b22ea..c63701503141 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -289,12 +289,6 @@ vc4_vec_connector_detect(struct drm_connector *connector, bool force) return connector_status_unknown; }
-static void vc4_vec_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - static int vc4_vec_connector_get_modes(struct drm_connector *connector) { struct drm_connector_state *state = connector->state; @@ -315,7 +309,6 @@ static int vc4_vec_connector_get_modes(struct drm_connector *connector) static const struct drm_connector_funcs vc4_vec_connector_funcs = { .detect = vc4_vec_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = vc4_vec_connector_destroy, .reset = drm_atomic_helper_connector_reset, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -328,12 +321,15 @@ static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec) { struct drm_connector *connector = &vec->connector; + int ret;
connector->interlace_allowed = true;
+ ret = drmm_connector_init(dev, connector, &vc4_vec_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + if (ret) + return ret;
- drm_connector_init(dev, connector, &vc4_vec_connector_funcs, - DRM_MODE_CONNECTOR_Composite); drm_connector_helper_add(connector, &vc4_vec_connector_helper_funcs);
drm_object_attach_property(&connector->base, @@ -559,9 +555,6 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) static void vc4_vec_unbind(struct device *dev, struct device *master, void *data) { - struct vc4_vec *vec = dev_get_drvdata(dev); - - vc4_vec_connector_destroy(&vec->connector); pm_runtime_disable(dev); }
Whenever the device and driver are unbound, the main device and all the subdevices will be removed by calling their unbind() method.
However, the DRM device itself will only be freed when the last user will have closed it.
It means that there is a time window where the device and its resources aren't there anymore, but the userspace can still call into our driver.
Fortunately, the DRM framework provides the drm_dev_enter() and drm_dev_exit() functions to make sure our underlying device is still there for the section protected by those calls. Let's add them to the VEC driver.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 67 +++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index c63701503141..2a72644d99c5 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -14,6 +14,7 @@ */
#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_edid.h> #include <drm/drm_panel.h> #include <drm/drm_probe_helper.h> @@ -225,14 +226,30 @@ static const struct debugfs_reg32 vec_regs[] = {
static void vc4_vec_ntsc_mode_set(struct vc4_vec *vec) { + struct drm_device *drm = vec->connector.dev; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_NTSC_STD | VEC_CONFIG0_PDEN); VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS); + + drm_dev_exit(idx); }
static void vc4_vec_ntsc_j_mode_set(struct vc4_vec *vec) { + struct drm_device *drm = vec->connector.dev; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_NTSC_STD); VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS); + + drm_dev_exit(idx); }
static const struct drm_display_mode ntsc_mode = { @@ -244,17 +261,33 @@ static const struct drm_display_mode ntsc_mode = {
static void vc4_vec_pal_mode_set(struct vc4_vec *vec) { + struct drm_device *drm = vec->connector.dev; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_PAL_BDGHI_STD); VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS); + + drm_dev_exit(idx); }
static void vc4_vec_pal_m_mode_set(struct vc4_vec *vec) { + struct drm_device *drm = vec->connector.dev; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; + VEC_WRITE(VEC_CONFIG0, VEC_CONFIG0_PAL_BDGHI_STD); VEC_WRITE(VEC_CONFIG1, VEC_CONFIG1_C_CVBS_CVBS | VEC_CONFIG1_CUSTOM_FREQ); VEC_WRITE(VEC_FREQ3_2, 0x223b); VEC_WRITE(VEC_FREQ1_0, 0x61d1); + + drm_dev_exit(idx); }
static const struct drm_display_mode pal_mode = { @@ -344,8 +377,12 @@ static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
static void vc4_vec_encoder_disable(struct drm_encoder *encoder) { + struct drm_device *drm = encoder->dev; struct vc4_vec *vec = encoder_to_vc4_vec(encoder); - int ret; + int idx, ret; + + if (!drm_dev_enter(drm, &idx)) + return;
VEC_WRITE(VEC_CFG, 0); VEC_WRITE(VEC_DAC_MISC, @@ -359,19 +396,29 @@ static void vc4_vec_encoder_disable(struct drm_encoder *encoder) ret = pm_runtime_put(&vec->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to release power domain: %d\n", ret); - return; + goto err_dev_exit; } + + drm_dev_exit(idx); + return; + +err_dev_exit: + drm_dev_exit(idx); }
static void vc4_vec_encoder_enable(struct drm_encoder *encoder) { + struct drm_device *drm = encoder->dev; struct vc4_vec *vec = encoder_to_vc4_vec(encoder); - int ret; + int idx, ret; + + if (!drm_dev_enter(drm, &idx)) + return;
ret = pm_runtime_get_sync(&vec->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to retain power domain: %d\n", ret); - return; + goto err_dev_exit; }
/* @@ -384,13 +431,13 @@ static void vc4_vec_encoder_enable(struct drm_encoder *encoder) ret = clk_set_rate(vec->clock, 108000000); if (ret) { DRM_ERROR("Failed to set clock rate: %d\n", ret); - return; + goto err_put_runtime_pm; }
ret = clk_prepare_enable(vec->clock); if (ret) { DRM_ERROR("Failed to turn on core clock: %d\n", ret); - return; + goto err_put_runtime_pm; }
/* Reset the different blocks */ @@ -426,6 +473,14 @@ static void vc4_vec_encoder_enable(struct drm_encoder *encoder) VEC_WRITE(VEC_DAC_MISC, VEC_DAC_MISC_VID_ACT | VEC_DAC_MISC_DAC_RST_N); VEC_WRITE(VEC_CFG, VEC_CFG_VEC_EN); + + drm_dev_exit(idx); + return; + +err_put_runtime_pm: + pm_runtime_put(&vec->pdev->dev); +err_dev_exit: + drm_dev_exit(idx); }
devm_pm_runtime_enable() simplifies the driver a bit since it will call pm_runtime_disable() automatically through a device-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_vec.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index 2a72644d99c5..a75b82de3796 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -583,39 +583,29 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) return ret; }
- pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret;
ret = drmm_simple_encoder_init(drm, &vec->encoder.base, DRM_MODE_ENCODER_TVDAC); if (ret) - goto err_put_runtime_pm; + return ret;
drm_encoder_helper_add(&vec->encoder.base, &vc4_vec_encoder_helper_funcs);
ret = vc4_vec_connector_init(drm, vec); if (ret) - goto err_put_runtime_pm; + return ret;
dev_set_drvdata(dev, vec);
vc4_debugfs_add_regset32(drm, "vec_regs", &vec->regset);
return 0; - -err_put_runtime_pm: - pm_runtime_disable(dev); - - return ret; -} - -static void vc4_vec_unbind(struct device *dev, struct device *master, - void *data) -{ - pm_runtime_disable(dev); }
static const struct component_ops vc4_vec_ops = { .bind = vc4_vec_bind, - .unbind = vc4_vec_unbind, };
static int vc4_vec_dev_probe(struct platform_device *pdev)
Our current code now mixes some resources whose lifetime are tied to the device (clocks, IO mappings, etc.) and some that are tied to the DRM device (encoder, bridge).
The device one will be freed at unbind time, but the DRM one will only be freed when the last user of the DRM device closes its file handle.
So we end up with a time window during which we can call the encoder hooks, but we don't have access to the underlying resources and device.
Let's protect all those sections with drm_dev_enter() and drm_dev_exit() so that we bail out if we are during that window.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_debugfs.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/drivers/gpu/drm/vc4/vc4_debugfs.c b/drivers/gpu/drm/vc4/vc4_debugfs.c index ba2d8ea562af..d6350a8ca048 100644 --- a/drivers/gpu/drm/vc4/vc4_debugfs.c +++ b/drivers/gpu/drm/vc4/vc4_debugfs.c @@ -3,6 +3,8 @@ * Copyright © 2014 Broadcom */
+#include <drm/drm_drv.h> + #include <linux/seq_file.h> #include <linux/circ_buf.h> #include <linux/ctype.h> @@ -41,11 +43,18 @@ vc4_debugfs_init(struct drm_minor *minor) static int vc4_debugfs_regset32(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *drm = node->minor->dev; struct debugfs_regset32 *regset = node->info_ent->data; struct drm_printer p = drm_seq_file_printer(m); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV;
drm_print_regset32(&p, regset);
+ drm_dev_exit(idx); + return 0; }
vc4_debugfs_add_file() can fail, so let's propagate its error code instead of silencing it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_debugfs.c | 20 +++++++++++--------- drivers/gpu/drm/vc4/vc4_drv.h | 30 ++++++++++++++++-------------- 2 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_debugfs.c b/drivers/gpu/drm/vc4/vc4_debugfs.c index d6350a8ca048..b857fb9c94bc 100644 --- a/drivers/gpu/drm/vc4/vc4_debugfs.c +++ b/drivers/gpu/drm/vc4/vc4_debugfs.c @@ -67,10 +67,10 @@ static int vc4_debugfs_regset32(struct seq_file *m, void *unused) * track the request and delay it to be called on each minor during * vc4_debugfs_init(). */ -void vc4_debugfs_add_file(struct drm_device *dev, - const char *name, - int (*show)(struct seq_file*, void*), - void *data) +int vc4_debugfs_add_file(struct drm_device *dev, + const char *name, + int (*show)(struct seq_file*, void*), + void *data) { struct vc4_dev *vc4 = to_vc4_dev(dev);
@@ -78,18 +78,20 @@ void vc4_debugfs_add_file(struct drm_device *dev, devm_kzalloc(dev->dev, sizeof(*entry), GFP_KERNEL);
if (!entry) - return; + return -ENOMEM;
entry->info.name = name; entry->info.show = show; entry->info.data = data;
list_add(&entry->link, &vc4->debugfs_list); + + return 0; }
-void vc4_debugfs_add_regset32(struct drm_device *drm, - const char *name, - struct debugfs_regset32 *regset) +int vc4_debugfs_add_regset32(struct drm_device *drm, + const char *name, + struct debugfs_regset32 *regset) { - vc4_debugfs_add_file(drm, name, vc4_debugfs_regset32, regset); + return vc4_debugfs_add_file(drm, name, vc4_debugfs_regset32, regset); } diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 12ab6df30629..3d1482f414c8 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -859,25 +859,27 @@ void vc4_crtc_get_margins(struct drm_crtc_state *state, /* vc4_debugfs.c */ void vc4_debugfs_init(struct drm_minor *minor); #ifdef CONFIG_DEBUG_FS -void vc4_debugfs_add_file(struct drm_device *drm, - const char *filename, - int (*show)(struct seq_file*, void*), - void *data); -void vc4_debugfs_add_regset32(struct drm_device *drm, - const char *filename, - struct debugfs_regset32 *regset); +int vc4_debugfs_add_file(struct drm_device *drm, + const char *filename, + int (*show)(struct seq_file*, void*), + void *data); +int vc4_debugfs_add_regset32(struct drm_device *drm, + const char *filename, + struct debugfs_regset32 *regset); #else -static inline void vc4_debugfs_add_file(struct drm_device *drm, - const char *filename, - int (*show)(struct seq_file*, void*), - void *data) +static inline int vc4_debugfs_add_file(struct drm_device *drm, + const char *filename, + int (*show)(struct seq_file*, void*), + void *data) { + return 0; }
-static inline void vc4_debugfs_add_regset32(struct drm_device *drm, - const char *filename, - struct debugfs_regset32 *regset) +static inline int vc4_debugfs_add_regset32(struct drm_device *drm, + const char *filename, + struct debugfs_regset32 *regset) { + return 0; } #endif
The vc4 has a custom API to allow components to register a debugfs file before the DRM driver has been registered and the debugfs_init hook has been called.
However, the .late_register hook allows to have the debugfs file creation deferred after that time already.
Let's remove our custom code to only register later our debugfs entries as part of either debugfs_init or after it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_bo.c | 20 +++++++++++-- drivers/gpu/drm/vc4/vc4_crtc.c | 19 ++++++++++-- drivers/gpu/drm/vc4/vc4_debugfs.c | 50 ++++++++++--------------------- drivers/gpu/drm/vc4/vc4_dpi.c | 24 +++++++++++++-- drivers/gpu/drm/vc4/vc4_drv.h | 12 +++++--- drivers/gpu/drm/vc4/vc4_dsi.c | 23 ++++++++++++-- drivers/gpu/drm/vc4/vc4_hdmi.c | 45 ++++++++++++++++++++-------- drivers/gpu/drm/vc4/vc4_hdmi.h | 3 +- drivers/gpu/drm/vc4/vc4_hvs.c | 32 +++++++++++++++++--- drivers/gpu/drm/vc4/vc4_txp.c | 3 +- drivers/gpu/drm/vc4/vc4_v3d.c | 25 ++++++++++++++-- drivers/gpu/drm/vc4/vc4_vec.c | 25 ++++++++++++++-- 12 files changed, 206 insertions(+), 75 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 49c0f2ac868b..68fe9903947d 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -942,6 +942,23 @@ int vc4_get_tiling_ioctl(struct drm_device *dev, void *data, return 0; }
+int vc4_bo_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *drm = minor->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + int ret; + + if (!vc4->v3d) + return -ENODEV; + + ret = vc4_debugfs_add_file(minor, "bo_stats", + vc4_bo_stats_debugfs, NULL); + if (ret) + return ret; + + return 0; +} + static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused); int vc4_bo_cache_init(struct drm_device *dev) { @@ -963,9 +980,6 @@ int vc4_bo_cache_init(struct drm_device *dev) vc4->bo_labels[i].name = bo_type_names[i];
mutex_init(&vc4->bo_lock); - - vc4_debugfs_add_file(dev, "bo_stats", vc4_bo_stats_debugfs, NULL); - INIT_LIST_HEAD(&vc4->bo_cache.time_list);
INIT_WORK(&vc4->bo_cache.time_work, vc4_bo_cache_time_work); diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 24de4706b61a..4a9f68362137 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -946,6 +946,21 @@ void vc4_crtc_reset(struct drm_crtc *crtc) __drm_atomic_helper_crtc_reset(crtc, &vc4_crtc_state->base); }
+int vc4_crtc_late_register(struct drm_crtc *crtc) +{ + struct drm_device *drm = crtc->dev; + struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc); + const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc); + int ret; + + ret = vc4_debugfs_add_regset32(drm->primary, crtc_data->debugfs_name, + &vc4_crtc->regset); + if (ret) + return ret; + + return 0; +} + static const struct drm_crtc_funcs vc4_crtc_funcs = { .set_config = drm_atomic_helper_set_config, .page_flip = vc4_page_flip, @@ -958,6 +973,7 @@ static const struct drm_crtc_funcs vc4_crtc_funcs = { .enable_vblank = vc4_enable_vblank, .disable_vblank = vc4_disable_vblank, .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .late_register = vc4_crtc_late_register, };
static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = { @@ -1212,9 +1228,6 @@ static int vc4_crtc_bind(struct device *dev, struct device *master, void *data)
platform_set_drvdata(pdev, vc4_crtc);
- vc4_debugfs_add_regset32(drm, pv_data->base.debugfs_name, - &vc4_crtc->regset); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_debugfs.c b/drivers/gpu/drm/vc4/vc4_debugfs.c index b857fb9c94bc..b40d6a44b16b 100644 --- a/drivers/gpu/drm/vc4/vc4_debugfs.c +++ b/drivers/gpu/drm/vc4/vc4_debugfs.c @@ -14,11 +14,6 @@ #include "vc4_drv.h" #include "vc4_regs.h"
-struct vc4_debugfs_info_entry { - struct list_head link; - struct drm_info_list info; -}; - /* * Called at drm_dev_register() time on each of the minors registered * by the DRM device, to attach the debugfs files. @@ -27,16 +22,12 @@ void vc4_debugfs_init(struct drm_minor *minor) { struct vc4_dev *vc4 = to_vc4_dev(minor->dev); - struct vc4_debugfs_info_entry *entry;
- if (!of_device_is_compatible(vc4->hvs->pdev->dev.of_node, - "brcm,bcm2711-vc5")) - debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR, - minor->debugfs_root, &vc4->load_tracker_enabled); + WARN_ON(vc4_hvs_debugfs_init(minor));
- list_for_each_entry(entry, &vc4->debugfs_list, link) { - drm_debugfs_create_files(&entry->info, 1, - minor->debugfs_root, minor); + if (vc4->v3d) { + WARN_ON(vc4_bo_debugfs_init(minor)); + WARN_ON(vc4_v3d_debugfs_init(minor)); } }
@@ -58,40 +49,31 @@ static int vc4_debugfs_regset32(struct seq_file *m, void *unused) return 0; }
-/* - * Registers a debugfs file with a callback function for a vc4 component. - * - * This is like drm_debugfs_create_files(), but that can only be - * called a given DRM minor, while the various VC4 components want to - * register their debugfs files during the component bind process. We - * track the request and delay it to be called on each minor during - * vc4_debugfs_init(). - */ -int vc4_debugfs_add_file(struct drm_device *dev, +int vc4_debugfs_add_file(struct drm_minor *minor, const char *name, int (*show)(struct seq_file*, void*), void *data) { - struct vc4_dev *vc4 = to_vc4_dev(dev); + struct drm_device *dev = minor->dev; + struct dentry *root = minor->debugfs_root; + struct drm_info_list *file;
- struct vc4_debugfs_info_entry *entry = - devm_kzalloc(dev->dev, sizeof(*entry), GFP_KERNEL); - - if (!entry) + file = drmm_kzalloc(dev, sizeof(*file), GFP_KERNEL); + if (!file) return -ENOMEM;
- entry->info.name = name; - entry->info.show = show; - entry->info.data = data; + file->name = name; + file->show = show; + file->data = data;
- list_add(&entry->link, &vc4->debugfs_list); + drm_debugfs_create_files(file, 1, root, minor);
return 0; }
-int vc4_debugfs_add_regset32(struct drm_device *drm, +int vc4_debugfs_add_regset32(struct drm_minor *minor, const char *name, struct debugfs_regset32 *regset) { - return vc4_debugfs_add_file(drm, name, vc4_debugfs_regset32, regset); + return vc4_debugfs_add_file(minor, name, vc4_debugfs_regset32, regset); } diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index ea3d20651f43..0445357597f0 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -224,6 +224,23 @@ static const struct drm_encoder_helper_funcs vc4_dpi_encoder_helper_funcs = { .mode_valid = vc4_dpi_encoder_mode_valid, };
+static int vc4_dpi_late_register(struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct vc4_dpi *dpi = to_vc4_dpi(encoder); + int ret; + + ret = vc4_debugfs_add_regset32(drm->primary, "dpi_regs", &dpi->regset); + if (ret) + return ret; + + return 0; +} + +static const struct drm_encoder_funcs vc4_dpi_encoder_funcs = { + .late_register = vc4_dpi_late_register, +}; + static const struct of_device_id vc4_dpi_dt_match[] = { { .compatible = "brcm,bcm2835-dpi", .data = NULL }, {} @@ -311,7 +328,10 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- ret = drmm_simple_encoder_init(drm, &dpi->encoder.base, DRM_MODE_ENCODER_DPI); + ret = drmm_encoder_init(drm, &dpi->encoder.base, + &vc4_dpi_encoder_funcs, + DRM_MODE_ENCODER_DPI, + NULL); if (ret) return ret;
@@ -323,8 +343,6 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, dpi);
- vc4_debugfs_add_regset32(drm, "dpi_regs", &dpi->regset); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h index 3d1482f414c8..ccde56be3a9c 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.h +++ b/drivers/gpu/drm/vc4/vc4_drv.h @@ -835,6 +835,7 @@ int vc4_bo_inc_usecnt(struct vc4_bo *bo); void vc4_bo_dec_usecnt(struct vc4_bo *bo); void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo); void vc4_bo_remove_from_purgeable_pool(struct vc4_bo *bo); +int vc4_bo_debugfs_init(struct drm_minor *minor);
/* vc4_crtc.c */ extern struct platform_driver vc4_crtc_driver; @@ -852,6 +853,7 @@ void vc4_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state); void vc4_crtc_reset(struct drm_crtc *crtc); void vc4_crtc_handle_vblank(struct vc4_crtc *crtc); +int vc4_crtc_late_register(struct drm_crtc *crtc); void vc4_crtc_get_margins(struct drm_crtc_state *state, unsigned int *left, unsigned int *right, unsigned int *top, unsigned int *bottom); @@ -859,15 +861,15 @@ void vc4_crtc_get_margins(struct drm_crtc_state *state, /* vc4_debugfs.c */ void vc4_debugfs_init(struct drm_minor *minor); #ifdef CONFIG_DEBUG_FS -int vc4_debugfs_add_file(struct drm_device *drm, +int vc4_debugfs_add_file(struct drm_minor *minor, const char *filename, int (*show)(struct seq_file*, void*), void *data); -int vc4_debugfs_add_regset32(struct drm_device *drm, +int vc4_debugfs_add_regset32(struct drm_minor *minor, const char *filename, struct debugfs_regset32 *regset); #else -static inline int vc4_debugfs_add_file(struct drm_device *drm, +static inline int vc4_debugfs_add_file(struct drm_minor *minor, const char *filename, int (*show)(struct seq_file*, void*), void *data) @@ -875,7 +877,7 @@ static inline int vc4_debugfs_add_file(struct drm_device *drm, return 0; }
-static inline int vc4_debugfs_add_regset32(struct drm_device *drm, +static inline int vc4_debugfs_add_regset32(struct drm_minor *minor, const char *filename, struct debugfs_regset32 *regset) { @@ -944,6 +946,7 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) void vc4_hvs_dump_state(struct vc4_hvs *hvs); void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel); void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel); +int vc4_hvs_debugfs_init(struct drm_minor *minor);
/* vc4_kms.c */ int vc4_kms_load(struct drm_device *dev); @@ -966,6 +969,7 @@ int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used); void vc4_v3d_bin_bo_put(struct vc4_dev *vc4); int vc4_v3d_pm_get(struct vc4_dev *vc4); void vc4_v3d_pm_put(struct vc4_dev *vc4); +int vc4_v3d_debugfs_init(struct drm_minor *minor);
/* vc4_validate.c */ int diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 741db2dce8ab..77afbdfac897 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -1314,6 +1314,24 @@ static const struct drm_encoder_helper_funcs vc4_dsi_encoder_helper_funcs = { .mode_fixup = vc4_dsi_encoder_mode_fixup, };
+static int vc4_dsi_late_register(struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct vc4_dsi *dsi = to_vc4_dsi(encoder); + int ret; + + ret = vc4_debugfs_add_regset32(drm->primary, dsi->variant->debugfs_name, + &dsi->regset); + if (ret) + return ret; + + return 0; +} + +static const struct drm_encoder_funcs vc4_dsi_encoder_funcs = { + .late_register = vc4_dsi_late_register, +}; + static const struct vc4_dsi_variant bcm2711_dsi1_variant = { .port = 1, .debugfs_name = "dsi1_regs", @@ -1616,7 +1634,8 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- ret = drmm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DSI); + ret = drmm_encoder_init(drm, encoder, &vc4_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, NULL); if (ret) return ret;
@@ -1636,8 +1655,6 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) */ list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
- vc4_debugfs_add_regset32(drm, dsi->variant->debugfs_name, &dsi->regset); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index b31487547070..2092231001c8 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -1793,6 +1793,26 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { .enable = vc4_hdmi_encoder_enable, };
+static int vc4_hdmi_late_register(struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; + int ret; + + ret = vc4_debugfs_add_file(drm->primary, variant->debugfs_name, + vc4_hdmi_debugfs_regs, + vc4_hdmi); + if (ret) + return ret; + + return 0; +} + +static const struct drm_encoder_funcs vc4_hdmi_encoder_funcs = { + .late_register = vc4_hdmi_late_register, +}; + static u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) { int i; @@ -2922,11 +2942,11 @@ static void vc4_hdmi_free_regset(struct drm_device *drm, void *ptr) kfree(regs); }
-static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, +static int vc4_hdmi_build_regset(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi, struct debugfs_regset32 *regset, enum vc4_hdmi_regs reg) { - struct drm_device *drm = vc4_hdmi->connector.dev; const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; struct debugfs_reg32 *regs, *new_regs; unsigned int count = 0; @@ -2964,7 +2984,8 @@ static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, return 0; }
-static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +static int vc4_hdmi_init_resources(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi) { struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; @@ -2978,11 +2999,11 @@ static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) if (IS_ERR(vc4_hdmi->hd_regs)) return PTR_ERR(vc4_hdmi->hd_regs);
- ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); if (ret) return ret;
- ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); if (ret) return ret;
@@ -3005,7 +3026,8 @@ static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) return 0; }
-static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +static int vc5_hdmi_init_resources(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi) { struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; @@ -3175,7 +3197,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) if (variant->max_pixel_clock > HDMI_14_MAX_TMDS_CLK) vc4_hdmi->scdc_enabled = true;
- ret = variant->init_resources(vc4_hdmi); + ret = variant->init_resources(drm, vc4_hdmi); if (ret) return ret;
@@ -3243,7 +3265,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); }
- ret = drmm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + ret = drmm_encoder_init(drm, encoder, + &vc4_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); if (ret) goto err_put_runtime_pm;
@@ -3265,10 +3290,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) if (ret) goto err_put_runtime_pm;
- vc4_debugfs_add_file(drm, variant->debugfs_name, - vc4_hdmi_debugfs_regs, - vc4_hdmi); - pm_runtime_put_sync(dev);
return 0; diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 51b27dcdcd9b..f6be92f33383 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -58,7 +58,8 @@ struct vc4_hdmi_variant { /* Callback to get the resources (memory region, interrupts, * clocks, etc) for that variant. */ - int (*init_resources)(struct vc4_hdmi *vc4_hdmi); + int (*init_resources)(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi);
/* Callback to reset the HDMI block */ void (*reset)(struct vc4_hdmi *vc4_hdmi); diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index b0906bb96c32..f336ada6c84c 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -693,6 +693,34 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data) return irqret; }
+int vc4_hvs_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *drm = minor->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_hvs *hvs = vc4->hvs; + int ret; + + if (!vc4->hvs) + return -ENODEV; + + if (!hvs->hvs5) + debugfs_create_bool("hvs_load_tracker", S_IRUGO | S_IWUSR, + minor->debugfs_root, + &vc4->load_tracker_enabled); + + ret = vc4_debugfs_add_file(minor, "hvs_underrun", + vc4_hvs_debugfs_underrun, NULL); + if (ret) + return ret; + + ret = vc4_debugfs_add_regset32(minor, "hvs_regs", + &hvs->regset); + if (ret) + return ret; + + return 0; +} + static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -821,10 +849,6 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset); - vc4_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, - NULL); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c index fee00b7003ab..19d3cfac40f2 100644 --- a/drivers/gpu/drm/vc4/vc4_txp.c +++ b/drivers/gpu/drm/vc4/vc4_txp.c @@ -396,6 +396,7 @@ static const struct drm_crtc_funcs vc4_txp_crtc_funcs = { .atomic_destroy_state = vc4_crtc_destroy_state, .enable_vblank = vc4_txp_enable_vblank, .disable_vblank = vc4_txp_disable_vblank, + .late_register = vc4_crtc_late_register, };
static int vc4_txp_atomic_check(struct drm_crtc *crtc, @@ -538,8 +539,6 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, txp);
- vc4_debugfs_add_regset32(drm, "txp_regs", &vc4_crtc->regset); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 7bb3067f8425..8fd16ece5b5c 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -386,6 +386,28 @@ static int vc4_v3d_runtime_resume(struct device *dev) } #endif
+int vc4_v3d_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *drm = minor->dev; + struct vc4_dev *vc4 = to_vc4_dev(drm); + struct vc4_v3d *v3d = vc4->v3d; + int ret; + + if (!vc4->v3d) + return -ENODEV; + + ret = vc4_debugfs_add_file(minor, "v3d_ident", + vc4_v3d_debugfs_ident, NULL); + if (ret) + return ret; + + ret = vc4_debugfs_add_regset32(minor, "v3d_regs", &v3d->regset); + if (ret) + return ret; + + return 0; +} + static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); @@ -462,9 +484,6 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ pm_runtime_enable(dev);
- vc4_debugfs_add_file(drm, "v3d_ident", vc4_v3d_debugfs_ident, NULL); - vc4_debugfs_add_regset32(drm, "v3d_regs", &v3d->regset); - return 0; }
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c index a75b82de3796..4502a83ad16f 100644 --- a/drivers/gpu/drm/vc4/vc4_vec.c +++ b/drivers/gpu/drm/vc4/vc4_vec.c @@ -523,6 +523,24 @@ static const struct drm_encoder_helper_funcs vc4_vec_encoder_helper_funcs = { .atomic_mode_set = vc4_vec_encoder_atomic_mode_set, };
+static int vc4_vec_late_register(struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct vc4_vec *vec = encoder_to_vc4_vec(encoder); + int ret; + + ret = vc4_debugfs_add_regset32(drm->primary, "vec_regs", + &vec->regset); + if (ret) + return ret; + + return 0; +} + +static const struct drm_encoder_funcs vc4_vec_encoder_funcs = { + .late_register = vc4_vec_late_register, +}; + static const struct vc4_vec_variant bcm2835_vec_variant = { .dac_config = VEC_DAC_CONFIG_DAC_CTRL(0xc) | VEC_DAC_CONFIG_DRIVER_CTRL(0xc) | @@ -587,7 +605,10 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data) if (ret) return ret;
- ret = drmm_simple_encoder_init(drm, &vec->encoder.base, DRM_MODE_ENCODER_TVDAC); + ret = drmm_encoder_init(drm, &vec->encoder.base, + &vc4_vec_encoder_funcs, + DRM_MODE_ENCODER_TVDAC, + NULL); if (ret) return ret;
@@ -599,8 +620,6 @@ static int vc4_vec_bind(struct device *dev, struct device *master, void *data)
dev_set_drvdata(dev, vec);
- vc4_debugfs_add_regset32(drm, "vec_regs", &vec->regset); - return 0; }
mutex_init is supposed to be balanced by a call to mutex_destroy that we were never doing in the vc4 driver.
Since a DRM-managed mutex_init variant has been introduced, let's just switch to it.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_bo.c | 15 +++++++++++++-- drivers/gpu/drm/vc4/vc4_drv.c | 4 +++- drivers/gpu/drm/vc4/vc4_gem.c | 10 ++++++++-- drivers/gpu/drm/vc4/vc4_hdmi.c | 5 ++++- 4 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index 68fe9903947d..f52c2cd85650 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -386,6 +386,7 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size) { struct vc4_dev *vc4 = to_vc4_dev(dev); struct vc4_bo *bo; + int ret;
bo = kzalloc(sizeof(*bo), GFP_KERNEL); if (!bo) @@ -393,7 +394,11 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size)
bo->madv = VC4_MADV_WILLNEED; refcount_set(&bo->usecnt, 0); - mutex_init(&bo->madv_lock); + + ret = drmm_mutex_init(dev, &bo->madv_lock); + if (ret) + return ERR_PTR(ret); + mutex_lock(&vc4->bo_lock); bo->label = VC4_BO_TYPE_KERNEL; vc4->bo_labels[VC4_BO_TYPE_KERNEL].num_allocated++; @@ -963,6 +968,7 @@ static void vc4_bo_cache_destroy(struct drm_device *dev, void *unused); int vc4_bo_cache_init(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); + int ret; int i;
/* Create the initial set of BO labels that the kernel will @@ -979,7 +985,12 @@ int vc4_bo_cache_init(struct drm_device *dev) for (i = 0; i < VC4_BO_TYPE_COUNT; i++) vc4->bo_labels[i].name = bo_type_names[i];
- mutex_init(&vc4->bo_lock); + ret = drmm_mutex_init(dev, &vc4->bo_lock); + if (ret) { + kfree(vc4->bo_labels); + return ret; + } + INIT_LIST_HEAD(&vc4->bo_cache.time_list);
INIT_WORK(&vc4->bo_cache.time_work, vc4_bo_cache_time_work); diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 031f2cdd658d..df3b92d06bd0 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -243,7 +243,9 @@ static int vc4_drm_bind(struct device *dev) platform_set_drvdata(pdev, drm); INIT_LIST_HEAD(&vc4->debugfs_list);
- mutex_init(&vc4->bin_bo_lock); + ret = drmm_mutex_init(drm, &vc4->bin_bo_lock); + if (ret) + return ret;
ret = vc4_bo_cache_init(drm); if (ret) diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c index 9eaf304fc20d..45f96409a72e 100644 --- a/drivers/gpu/drm/vc4/vc4_gem.c +++ b/drivers/gpu/drm/vc4/vc4_gem.c @@ -1275,6 +1275,7 @@ static void vc4_gem_destroy(struct drm_device *dev, void *unused); int vc4_gem_init(struct drm_device *dev) { struct vc4_dev *vc4 = to_vc4_dev(dev); + int ret;
vc4->dma_fence_context = dma_fence_context_alloc(1);
@@ -1289,10 +1290,15 @@ int vc4_gem_init(struct drm_device *dev)
INIT_WORK(&vc4->job_done_work, vc4_job_done_work);
- mutex_init(&vc4->power_lock); + ret = drmm_mutex_init(dev, &vc4->power_lock); + if (ret) + return ret;
INIT_LIST_HEAD(&vc4->purgeable.list); - mutex_init(&vc4->purgeable.lock); + + ret = drmm_mutex_init(dev, &vc4->purgeable.lock); + if (ret) + return ret;
return drmm_add_action_or_reset(dev, vc4_gem_destroy, NULL); } diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 2092231001c8..6672542811c9 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -3173,7 +3173,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) if (!vc4_hdmi) return -ENOMEM;
- mutex_init(&vc4_hdmi->mutex); + ret = drmm_mutex_init(drm, &vc4_hdmi->mutex); + if (ret) + return ret; + spin_lock_init(&vc4_hdmi->hw_lock); INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
vc4_perfmon_open_file() will instantiate a mutex for that file instance, but we never call mutex_destroy () in vc4_perfmon_close_file().
Let's add that missing call.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_perfmon.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/gpu/drm/vc4/vc4_perfmon.c b/drivers/gpu/drm/vc4/vc4_perfmon.c index 18abc06335c1..531b0e7ba035 100644 --- a/drivers/gpu/drm/vc4/vc4_perfmon.c +++ b/drivers/gpu/drm/vc4/vc4_perfmon.c @@ -95,6 +95,7 @@ void vc4_perfmon_close_file(struct vc4_file *vc4file) idr_for_each(&vc4file->perfmon.idr, vc4_perfmon_idr_del, NULL); idr_destroy(&vc4file->perfmon.idr); mutex_unlock(&vc4file->perfmon.lock); + mutex_destroy(&vc4file->perfmon.lock); }
int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data,
The vc4_irq_disable(), among other things, will call disable_irq() to complete any in-flight interrupts.
This requires its counterpart, vc4_irq_enable(), to call enable_irq() which causes issues addressed in a later patch.
However, vc4_irq_disable() is called by two callees: vc4_irq_uninstall() and vc4_v3d_runtime_suspend().
vc4_irq_uninstall() also calls free_irq() which already disables the interrupt line. We thus don't require an explicit disable_irq() for that call site.
vc4_v3d_runtime_suspend() doesn't have any other code. However, the rest of vc4_irq_disable() masks the interrupts coming from the v3d, so explictly disabling the interrupt line is also redundant.
The only thing we really care about is thus to make sure we don't have any handler in-flight, as suggested by the comment. We can thus replace disable_irq() by synchronize_irq().
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_irq.c | 2 +- drivers/gpu/drm/vc4/vc4_v3d.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c index 4342fb43e8c1..1af0560ed16c 100644 --- a/drivers/gpu/drm/vc4/vc4_irq.c +++ b/drivers/gpu/drm/vc4/vc4_irq.c @@ -289,7 +289,7 @@ vc4_irq_disable(struct drm_device *dev) V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
/* Finish any interrupt handler still in flight. */ - disable_irq(vc4->irq); + synchronize_irq(vc4->irq);
cancel_work_sync(&vc4->overflow_mem_work); } diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index 8fd16ece5b5c..ad0dac62deb2 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -378,8 +378,6 @@ static int vc4_v3d_runtime_resume(struct device *dev)
vc4_v3d_init_hw(&vc4->base);
- /* We disabled the IRQ as part of vc4_irq_uninstall in suspend. */ - enable_irq(vc4->irq); vc4_irq_enable(&vc4->base);
return 0;
At bind time, vc4_v3d_bind() will read a register to retrieve the v3d version and make sure it's a version we're compatible with.
However, the v3d has an optional clock that is enabled only after the register read-out and a power domain that wasn't enabled at all in the bind implementation. This was working fine at boot because both were enabled, but resulted in the version check failing if we were unbinding and rebinding the driver because the unbinding would have turned them off.
The fix isn't as easy as calling pm_runtime_resume_and_get() prior to the register access to power up the power domain though.
Indeed, the runtime_resume implementation will enable the clock mentioned above, call vc4_v3d_init_hw() and then vc4_irq_enable().
Prior to the previous patch, vc4_irq_enable() needed to occur after our call to platform_get_irq() and vc4_irq_install(), since vc4_irq_enable() used to call enable_irq() and vc4_irq_install() will call request_irq().
vc4_irq_install() will also do some register access, so needs the power domain to be on. So we ended up in a situation where vc4_v3d_runtime_resume() needed vc4_irq_install() to have been called before, and vc4_irq_install() needed vc4_v3d_runtime_resume().
The previous patch removed the enable_irq() call in vc4_irq_enable() and thus removed the dependency of vc4_v3d_runtime_resume() on vc4_irq_install().
Thus, we can now rework our bind implementation to call pm_runtime_resume_and_get() before our register access to make sure the power domain is on. vc4_v3d_runtime_resume() also takes care of turning the clock on and calling vc4_v3d_init_hw() so we can remove them from bind.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_v3d.c | 37 +++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index ad0dac62deb2..a3fcabf5e6ab 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -448,41 +448,48 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) } }
+ ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + vc4->irq = ret; + + pm_runtime_enable(dev); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto err_disable_runtime_pm; + if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) { DRM_ERROR("V3D_IDENT0 read 0x%08x instead of 0x%08x\n", V3D_READ(V3D_IDENT0), V3D_EXPECTED_IDENT0); - return -EINVAL; + ret = -EINVAL; + goto err_put_runtime_pm; }
- ret = clk_prepare_enable(v3d->clk); - if (ret != 0) - return ret; - /* Reset the binner overflow address/size at setup, to be sure * we don't reuse an old one. */ V3D_WRITE(V3D_BPOA, 0); V3D_WRITE(V3D_BPOS, 0);
- vc4_v3d_init_hw(drm); - - ret = platform_get_irq(pdev, 0); - if (ret < 0) - return ret; - vc4->irq = ret; - ret = vc4_irq_install(drm, vc4->irq); if (ret) { DRM_ERROR("Failed to install IRQ handler\n"); - return ret; + goto err_put_runtime_pm; }
- pm_runtime_set_active(dev); pm_runtime_use_autosuspend(dev); pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */ - pm_runtime_enable(dev);
return 0; + +err_put_runtime_pm: + pm_runtime_put(dev); + +err_disable_runtime_pm: + pm_runtime_disable(dev); + + return ret; }
static void vc4_v3d_unbind(struct device *dev, struct device *master,
devm_pm_runtime_enable() simplifies the driver a bit since it will call pm_runtime_disable() automatically through a device-managed action.
Signed-off-by: Maxime Ripard maxime@cerno.tech --- drivers/gpu/drm/vc4/vc4_v3d.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c index a3fcabf5e6ab..2d63124e2ac0 100644 --- a/drivers/gpu/drm/vc4/vc4_v3d.c +++ b/drivers/gpu/drm/vc4/vc4_v3d.c @@ -453,11 +453,13 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) return ret; vc4->irq = ret;
- pm_runtime_enable(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret;
ret = pm_runtime_resume_and_get(dev); if (ret) - goto err_disable_runtime_pm; + return ret;
if (V3D_READ(V3D_IDENT0) != V3D_EXPECTED_IDENT0) { DRM_ERROR("V3D_IDENT0 read 0x%08x instead of 0x%08x\n", @@ -486,9 +488,6 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data) err_put_runtime_pm: pm_runtime_put(dev);
-err_disable_runtime_pm: - pm_runtime_disable(dev); - return ret; }
@@ -498,8 +497,6 @@ static void vc4_v3d_unbind(struct device *dev, struct device *master, struct drm_device *drm = dev_get_drvdata(master); struct vc4_dev *vc4 = to_vc4_dev(drm);
- pm_runtime_disable(dev); - vc4_irq_uninstall(drm);
/* Disable the binner's overflow memory address, so the next
dri-devel@lists.freedesktop.org