diff options
author | Daniel Vetter <daniel.vetter@ffwll.ch> | 2016-12-18 08:35:45 -0500 |
---|---|---|
committer | Daniel Vetter <daniel.vetter@ffwll.ch> | 2017-01-30 04:17:17 -0500 |
commit | 4e5b54f127426c82dc2816340c26d951a5bb3429 (patch) | |
tree | cc3a8f7e335cf12880bc8d1a1e2066f6cfe0a68e | |
parent | 566cf877a1fcb6d6dc0126b076aad062054c2637 (diff) |
drm: prevent double-(un)registration for connectors
If we're unlucky then the registration from a hotplugged connector
might race with the final registration step on driver load. And since
MST topology discover is asynchronous that's even somewhat likely.
v2: Also update the kerneldoc for @registered!
v3: Review from Chris:
- Improve kerneldoc for late_register/early_unregister callbacks.
- Use mutex_destroy.
Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Chris Wilson <chris@chris-wilson.co.uk>
Reviewed-by: Sean Paul <seanpaul@chromium.org>
Reported-by: Chris Wilson <chris@chris-wilson.co.uk>
Signed-off-by: Daniel Vetter <daniel.vetter@intel.com>
Link: http://patchwork.freedesktop.org/patch/msgid/20161218133545.2106-1-daniel.vetter@ffwll.ch
(cherry picked from commit e73ab00e9a0f1731f34d0620a9c55f5c30c4ad4e)
-rw-r--r-- | drivers/gpu/drm/drm_connector.c | 20 | ||||
-rw-r--r-- | include/drm/drm_connector.h | 16 |
2 files changed, 30 insertions, 6 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 5a4526289392..8be60a4576b3 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c | |||
@@ -225,6 +225,7 @@ int drm_connector_init(struct drm_device *dev, | |||
225 | 225 | ||
226 | INIT_LIST_HEAD(&connector->probed_modes); | 226 | INIT_LIST_HEAD(&connector->probed_modes); |
227 | INIT_LIST_HEAD(&connector->modes); | 227 | INIT_LIST_HEAD(&connector->modes); |
228 | mutex_init(&connector->mutex); | ||
228 | connector->edid_blob_ptr = NULL; | 229 | connector->edid_blob_ptr = NULL; |
229 | connector->status = connector_status_unknown; | 230 | connector->status = connector_status_unknown; |
230 | 231 | ||
@@ -359,6 +360,8 @@ void drm_connector_cleanup(struct drm_connector *connector) | |||
359 | connector->funcs->atomic_destroy_state(connector, | 360 | connector->funcs->atomic_destroy_state(connector, |
360 | connector->state); | 361 | connector->state); |
361 | 362 | ||
363 | mutex_destroy(&connector->mutex); | ||
364 | |||
362 | memset(connector, 0, sizeof(*connector)); | 365 | memset(connector, 0, sizeof(*connector)); |
363 | } | 366 | } |
364 | EXPORT_SYMBOL(drm_connector_cleanup); | 367 | EXPORT_SYMBOL(drm_connector_cleanup); |
@@ -374,14 +377,15 @@ EXPORT_SYMBOL(drm_connector_cleanup); | |||
374 | */ | 377 | */ |
375 | int drm_connector_register(struct drm_connector *connector) | 378 | int drm_connector_register(struct drm_connector *connector) |
376 | { | 379 | { |
377 | int ret; | 380 | int ret = 0; |
378 | 381 | ||
382 | mutex_lock(&connector->mutex); | ||
379 | if (connector->registered) | 383 | if (connector->registered) |
380 | return 0; | 384 | goto unlock; |
381 | 385 | ||
382 | ret = drm_sysfs_connector_add(connector); | 386 | ret = drm_sysfs_connector_add(connector); |
383 | if (ret) | 387 | if (ret) |
384 | return ret; | 388 | goto unlock; |
385 | 389 | ||
386 | ret = drm_debugfs_connector_add(connector); | 390 | ret = drm_debugfs_connector_add(connector); |
387 | if (ret) { | 391 | if (ret) { |
@@ -397,12 +401,14 @@ int drm_connector_register(struct drm_connector *connector) | |||
397 | drm_mode_object_register(connector->dev, &connector->base); | 401 | drm_mode_object_register(connector->dev, &connector->base); |
398 | 402 | ||
399 | connector->registered = true; | 403 | connector->registered = true; |
400 | return 0; | 404 | goto unlock; |
401 | 405 | ||
402 | err_debugfs: | 406 | err_debugfs: |
403 | drm_debugfs_connector_remove(connector); | 407 | drm_debugfs_connector_remove(connector); |
404 | err_sysfs: | 408 | err_sysfs: |
405 | drm_sysfs_connector_remove(connector); | 409 | drm_sysfs_connector_remove(connector); |
410 | unlock: | ||
411 | mutex_unlock(&connector->mutex); | ||
406 | return ret; | 412 | return ret; |
407 | } | 413 | } |
408 | EXPORT_SYMBOL(drm_connector_register); | 414 | EXPORT_SYMBOL(drm_connector_register); |
@@ -415,8 +421,11 @@ EXPORT_SYMBOL(drm_connector_register); | |||
415 | */ | 421 | */ |
416 | void drm_connector_unregister(struct drm_connector *connector) | 422 | void drm_connector_unregister(struct drm_connector *connector) |
417 | { | 423 | { |
418 | if (!connector->registered) | 424 | mutex_lock(&connector->mutex); |
425 | if (!connector->registered) { | ||
426 | mutex_unlock(&connector->mutex); | ||
419 | return; | 427 | return; |
428 | } | ||
420 | 429 | ||
421 | if (connector->funcs->early_unregister) | 430 | if (connector->funcs->early_unregister) |
422 | connector->funcs->early_unregister(connector); | 431 | connector->funcs->early_unregister(connector); |
@@ -425,6 +434,7 @@ void drm_connector_unregister(struct drm_connector *connector) | |||
425 | drm_debugfs_connector_remove(connector); | 434 | drm_debugfs_connector_remove(connector); |
426 | 435 | ||
427 | connector->registered = false; | 436 | connector->registered = false; |
437 | mutex_unlock(&connector->mutex); | ||
428 | } | 438 | } |
429 | EXPORT_SYMBOL(drm_connector_unregister); | 439 | EXPORT_SYMBOL(drm_connector_unregister); |
430 | 440 | ||
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index a9b95246e26e..045a97cbeba2 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h | |||
@@ -381,6 +381,8 @@ struct drm_connector_funcs { | |||
381 | * core drm connector interfaces. Everything added from this callback | 381 | * core drm connector interfaces. Everything added from this callback |
382 | * should be unregistered in the early_unregister callback. | 382 | * should be unregistered in the early_unregister callback. |
383 | * | 383 | * |
384 | * This is called while holding drm_connector->mutex. | ||
385 | * | ||
384 | * Returns: | 386 | * Returns: |
385 | * | 387 | * |
386 | * 0 on success, or a negative error code on failure. | 388 | * 0 on success, or a negative error code on failure. |
@@ -395,6 +397,8 @@ struct drm_connector_funcs { | |||
395 | * late_register(). It is called from drm_connector_unregister(), | 397 | * late_register(). It is called from drm_connector_unregister(), |
396 | * early in the driver unload sequence to disable userspace access | 398 | * early in the driver unload sequence to disable userspace access |
397 | * before data structures are torndown. | 399 | * before data structures are torndown. |
400 | * | ||
401 | * This is called while holding drm_connector->mutex. | ||
398 | */ | 402 | */ |
399 | void (*early_unregister)(struct drm_connector *connector); | 403 | void (*early_unregister)(struct drm_connector *connector); |
400 | 404 | ||
@@ -559,7 +563,6 @@ struct drm_cmdline_mode { | |||
559 | * @interlace_allowed: can this connector handle interlaced modes? | 563 | * @interlace_allowed: can this connector handle interlaced modes? |
560 | * @doublescan_allowed: can this connector handle doublescan? | 564 | * @doublescan_allowed: can this connector handle doublescan? |
561 | * @stereo_allowed: can this connector handle stereo modes? | 565 | * @stereo_allowed: can this connector handle stereo modes? |
562 | * @registered: is this connector exposed (registered) with userspace? | ||
563 | * @modes: modes available on this connector (from fill_modes() + user) | 566 | * @modes: modes available on this connector (from fill_modes() + user) |
564 | * @status: one of the drm_connector_status enums (connected, not, or unknown) | 567 | * @status: one of the drm_connector_status enums (connected, not, or unknown) |
565 | * @probed_modes: list of modes derived directly from the display | 568 | * @probed_modes: list of modes derived directly from the display |
@@ -608,6 +611,13 @@ struct drm_connector { | |||
608 | char *name; | 611 | char *name; |
609 | 612 | ||
610 | /** | 613 | /** |
614 | * @mutex: Lock for general connector state, but currently only protects | ||
615 | * @registered. Most of the connector state is still protected by the | ||
616 | * mutex in &drm_mode_config. | ||
617 | */ | ||
618 | struct mutex mutex; | ||
619 | |||
620 | /** | ||
611 | * @index: Compacted connector index, which matches the position inside | 621 | * @index: Compacted connector index, which matches the position inside |
612 | * the mode_config.list for drivers not supporting hot-add/removing. Can | 622 | * the mode_config.list for drivers not supporting hot-add/removing. Can |
613 | * be used as an array index. It is invariant over the lifetime of the | 623 | * be used as an array index. It is invariant over the lifetime of the |
@@ -620,6 +630,10 @@ struct drm_connector { | |||
620 | bool interlace_allowed; | 630 | bool interlace_allowed; |
621 | bool doublescan_allowed; | 631 | bool doublescan_allowed; |
622 | bool stereo_allowed; | 632 | bool stereo_allowed; |
633 | /** | ||
634 | * @registered: Is this connector exposed (registered) with userspace? | ||
635 | * Protected by @mutex. | ||
636 | */ | ||
623 | bool registered; | 637 | bool registered; |
624 | struct list_head modes; /* list of modes on this connector */ | 638 | struct list_head modes; /* list of modes on this connector */ |
625 | 639 | ||