[PATCH v2 09/11] drm/tinydrm: Support device unplug in core
Noralf Trønnes
noralf at tronnes.org
Fri Sep 8 15:07:28 UTC 2017
Support device unplugging to make tinydrm suitable for USB devices.
Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 84 +++++++++++++++++++++--------
drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 26 ++++-----
2 files changed, 72 insertions(+), 38 deletions(-)
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
index c8ae2e9..082b347 100644
--- a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c
@@ -32,6 +32,36 @@
* The driver allocates &tinydrm_device, initializes it using
* devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
* and registers the DRM device using devm_tinydrm_register().
+ *
+ * Device unplug
+ * -------------
+ *
+ * tinydrm supports device unplugging when there are still open DRM or fbdev
+ * file handles.
+ *
+ * There are 3 ways for driver-device unbinding to happen:
+ *
+ * - The driver module is unloaded causing the driver to be unregistered.
+ * This can't happen as long as there are open file handles because a
+ * reference is taken on the module.
+ *
+ * - The device is removed (USB, Device Tree overlay).
+ * This can happen at any time.
+ *
+ * - The driver sysfs _unbind_ file can be used to unbind the driver from the
+ * device. This can happen any time.
+ *
+ * The driver needs to protect device resources from access after the device is
+ * gone. This is done marking the region with drm_dev_enter() and
+ * drm_dev_exit(), typically in &drm_framebuffer_funcs.dirty,
+ * &drm_simple_display_pipe_funcs.enable and \.disable.
+ *
+ * Resources that don't face userspace and are only used with the
+ * device can be setup using devm\_ functions, but &tinydrm_device must be
+ * allocated using plain kzalloc() since it's lifetime can exceed that of the
+ * device.
+ *
+ * The structure should be freed in the &drm_driver->release callback.
*/
/**
@@ -149,9 +179,16 @@ static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
*/
void tinydrm_release(struct drm_device *drm)
{
+ struct tinydrm_device *tdev = drm_to_tinydrm(drm);
+
DRM_DEBUG_DRIVER("\n");
+ drm_fbdev_cma_fini(tdev->fbdev_cma);
+
+ drm_mode_config_cleanup(&tdev->drm);
drm_dev_fini(drm);
+
+ mutex_destroy(&tdev->dirty_lock);
}
EXPORT_SYMBOL(tinydrm_release);
@@ -175,16 +212,11 @@ static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
return 0;
}
-static void tinydrm_fini(struct tinydrm_device *tdev)
-{
- drm_mode_config_cleanup(&tdev->drm);
- mutex_destroy(&tdev->dirty_lock);
- drm_dev_unref(&tdev->drm);
-}
-
static void devm_tinydrm_release(void *data)
{
- tinydrm_fini(data);
+ struct tinydrm_device *tdev = data;
+
+ drm_dev_unref(&tdev->drm);
}
/**
@@ -194,9 +226,10 @@ static void devm_tinydrm_release(void *data)
* @fb_funcs: Framebuffer functions
* @driver: DRM driver
*
- * This function initializes @tdev, the underlying DRM device and it's
- * mode_config. Resources will be automatically freed on driver detach (devres)
- * using drm_mode_config_cleanup() and drm_dev_unref().
+ * This function initializes @tdev, the underlying DRM device and its
+ * mode_config. drm_dev_unref() is called on driver detach (devres) and when
+ * all refs are dropped, the &drm_driver->release callback is called which in
+ * turn calls tinydrm_release().
*
* Returns:
* Zero on success, negative error code on failure.
@@ -213,7 +246,7 @@ int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
ret = devm_add_action(parent, devm_tinydrm_release, tdev);
if (ret)
- tinydrm_fini(tdev);
+ devm_tinydrm_release(tdev);
return ret;
}
@@ -243,14 +276,9 @@ static int tinydrm_register(struct tinydrm_device *tdev)
static void tinydrm_unregister(struct tinydrm_device *tdev)
{
- struct drm_fbdev_cma *fbdev_cma = tdev->fbdev_cma;
-
- drm_atomic_helper_shutdown(&tdev->drm);
- /* don't restore fbdev in lastclose, keep pipeline disabled */
- tdev->fbdev_cma = NULL;
- drm_dev_unregister(&tdev->drm);
- if (fbdev_cma)
- drm_fbdev_cma_fini(fbdev_cma);
+ if (tdev->fbdev_cma)
+ drm_fb_helper_unregister_fbi(&tdev->fbdev_cma->fb_helper);
+ drm_dev_unplug(&tdev->drm);
}
static void devm_tinydrm_register_release(void *data)
@@ -279,10 +307,20 @@ int devm_tinydrm_register(struct tinydrm_device *tdev)
return ret;
ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
- if (ret)
- tinydrm_unregister(tdev);
+ if (ret) {
+ devm_tinydrm_register_release(tdev);
+ return ret;
+ }
- return ret;
+ /*
+ * Take a ref that will be put in devm_tinydrm_release().
+ * It's done like this so devres cleanup can happen if there's an error
+ * in the probe function between calling devm_tinydrm_init() and
+ * devm_tinydrm_register().
+ */
+ drm_dev_ref(&tdev->drm);
+
+ return 0;
}
EXPORT_SYMBOL(devm_tinydrm_register);
diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
index 1bcb43a..22ef9d9 100644
--- a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
+++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c
@@ -14,7 +14,7 @@
struct tinydrm_connector {
struct drm_connector base;
- const struct drm_display_mode *mode;
+ struct drm_display_mode mode;
};
static inline struct tinydrm_connector *
@@ -28,7 +28,7 @@ static int tinydrm_connector_get_modes(struct drm_connector *connector)
struct tinydrm_connector *tconn = to_tinydrm_connector(connector);
struct drm_display_mode *mode;
- mode = drm_mode_duplicate(connector->dev, tconn->mode);
+ mode = drm_mode_duplicate(connector->dev, &tconn->mode);
if (!mode) {
DRM_ERROR("Failed to duplicate mode\n");
return 0;
@@ -92,7 +92,7 @@ tinydrm_connector_create(struct drm_device *drm,
if (!tconn)
return ERR_PTR(-ENOMEM);
- tconn->mode = mode;
+ tconn->mode = *mode;
connector = &tconn->base;
drm_connector_helper_add(connector, &tinydrm_connector_hfuncs);
@@ -199,27 +199,23 @@ tinydrm_display_pipe_init(struct tinydrm_device *tdev,
unsigned int rotation)
{
struct drm_device *drm = &tdev->drm;
- struct drm_display_mode *mode_copy;
+ struct drm_display_mode mode_copy;
struct drm_connector *connector;
int ret;
- mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL);
- if (!mode_copy)
- return -ENOMEM;
-
- *mode_copy = *mode;
- ret = tinydrm_rotate_mode(mode_copy, rotation);
+ mode_copy = *mode;
+ ret = tinydrm_rotate_mode(&mode_copy, rotation);
if (ret) {
DRM_ERROR("Illegal rotation value %u\n", rotation);
return -EINVAL;
}
- drm->mode_config.min_width = mode_copy->hdisplay;
- drm->mode_config.max_width = mode_copy->hdisplay;
- drm->mode_config.min_height = mode_copy->vdisplay;
- drm->mode_config.max_height = mode_copy->vdisplay;
+ drm->mode_config.min_width = mode_copy.hdisplay;
+ drm->mode_config.max_width = mode_copy.hdisplay;
+ drm->mode_config.min_height = mode_copy.vdisplay;
+ drm->mode_config.max_height = mode_copy.vdisplay;
- connector = tinydrm_connector_create(drm, mode_copy, connector_type);
+ connector = tinydrm_connector_create(drm, &mode_copy, connector_type);
if (IS_ERR(connector))
return PTR_ERR(connector);
--
2.7.4
More information about the dri-devel
mailing list