Support device unplugging to make tinydrm suitable for USB devices. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- 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 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel