The dev->registered variable was initially added in such a way that it
is only set after all functions that could have failed have been called
and haven't returned an error. However, since then, the function
drm_modeset_register_all() is being checked for its return value, which
opens a possibility of failing the register after setting the registered
variable anyway. drm_dev_register() cleans up after itself in case of
failure, mimicking the functions called in drm_dev_unregister(), with
the exception of drm_client_sysrq_unregister() and
drm_panic_unregister() (_register() counterparts of which are not
checked for their return values), and the dev->registered flag.

This creates a situation in which after calling drm_dev_register()
nothing is registered (as the function entered an error path and cleaned
up), but the device is reported as being registered according to the
dev->registered variable.

This, for example, confuses the WARN_ON() in drm_mode_object_register(),
which is raised if a non-dynamic mode object is attempted to be
unregistered in between the drm_dev_register() and drm_dev_unregister()
calls. If drm_dev_register() fails - meaning the device *isn't*
registered - currently it still reports dev->registered = true, which
triggers the WARN_ON(), even though the usage is correct in that instance.

What is more, commit 6915190a50e8 ("drm/client: Support emergency restore
via sysrq for all clients") introduced a new WARN_ON and added
drm_client_sysrq_register/unregister() calls to the drm device
register/unregister paths. Client sysrqs are registered only when
drm_dev_register() is sure to complete without failures, but
drm_client_sysrq_unregister() gets called every time during
drm_dev_unregister() and prints warning messages whenever the caller tries
to clean up the mess with immediate unregistration.

Set dev->registered back to false in the error path to prevent this.

Co-developed-by: Krzysztof Karas <[email protected]>
Signed-off-by: Krzysztof Karas <[email protected]>
Signed-off-by: Krzysztof Niemiec <[email protected]>
---
Changelog:

RFC -> v1:
 * addedd title of commit 6915190a50e8 to commit message;
 * rebased;

v1 -> v2:
 * updated commit log slightly to mention possible bugs;
 * updated authorship - the bug was found and fixed independently by
   Krzysztof K. and Krzysztof N.; We decided to exchange the findings
   and our comments and send the patch as co-developed-by.

v2 -> v3:
 no functional changes

v3 -> v4:
 * rebased
 * removed the debugfs change (sashiko, Andi)
 * put the =false in different places than v2/v3 (Andi). First driver I
   sanity-checked that uses the .unload() callback calls
   drm_mode_config_cleanup(), which expects registered=false down the
   call stack, so =false needs to happen before the callback. Problem
   is, the err_minors error path also needs to have the variable unset.
   I'm not sure there's a way to do this cleanly given how the
   .(un)load callbacks operate so we might be stuck with this, and it
   smells, the way I did it smells less *to me* than what Andi suggested
   with jumping between labels in the error path, but I'm not very
   opinionated either
 * spliced the v1 and v2/v3 commit logs together a bit to account for
   the debugfs change removal

An instance of a bug caused by this was uncovered in i915 with fault
injection. During driver probe in the error path, after failing
drm_dev_register() there is a reasonable expectation of a full proper
teardown of whatever drm_dev_register() has done. This is, however, not
the case, as shown in the calls below:

        i915_driver_probe()

        i915_driver_register()
                calls drm_dev_register()

        drm_dev_register()

        ...

        connector .late_register() callback
                called from drm_dev_register(), and fails, leaving
                dev->registered as true, propagates the error back up

        ...

        drm_dev_register()
                enters error path, but doesn't fully unwind

        i915_driver_register()
                enters error path

        i915_driver_probe()
                enters error path

        intel_display_driver_remove_noirq()
                called in _probe()'s error path

        intel_mode_config_cleanup()

        drm_mode_config_cleanup()
                this triggers a WARN_ON(), dev->registered should be false

This is a bug on drm_dev_register()'s side.

 drivers/gpu/drm/drm_drv.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index a2872c69b8c6..812add80831a 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -1135,9 +1135,11 @@ int drm_dev_register(struct drm_device *dev, unsigned 
long flags)
        goto out_unlock;
 
 err_unload:
+       dev->registered = false;
        if (dev->driver->unload)
                dev->driver->unload(dev);
 err_minors:
+       dev->registered = false; /* fallback for driver->load() error path */
        /*
         * If a minor was registered before the failure, userspace could have
         * opened it and entered a drm_dev_enter() critical section. Ensure all
-- 
2.55.0

Reply via email to