diff options
Diffstat (limited to 'drivers/gpu/drm/drm_connector.c')
-rw-r--r-- | drivers/gpu/drm/drm_connector.c | 228 |
1 files changed, 152 insertions, 76 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 7a7019ac9388..45464c8b797d 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -23,6 +23,7 @@ #include <drm/drmP.h> #include <drm/drm_connector.h> #include <drm/drm_edid.h> +#include <drm/drm_encoder.h> #include "drm_crtc_internal.h" #include "drm_internal.h" @@ -37,18 +38,17 @@ * Hence they are reference-counted using drm_connector_reference() and * drm_connector_unreference(). * - * KMS driver must create, initialize, register and attach at a struct - * &drm_connector for each such sink. The instance is created as other KMS - * objects and initialized by setting the following fields. - * - * The connector is then registered with a call to drm_connector_init() with a - * pointer to the connector functions and a connector type, and exposed through - * sysfs with a call to drm_connector_register(). + * KMS driver must create, initialize, register and attach at a &struct + * drm_connector for each such sink. The instance is created as other KMS + * objects and initialized by setting the following fields. The connector is + * initialized with a call to drm_connector_init() with a pointer to the + * &struct drm_connector_funcs and a connector type, and then exposed to + * userspace with a call to drm_connector_register(). * * Connectors must be attached to an encoder to be used. For devices that map * connectors to encoders 1:1, the connector should be attached at * initialization time with a call to drm_mode_connector_attach_encoder(). The - * driver must also set the struct &drm_connector encoder field to point to the + * driver must also set the &drm_connector.encoder field to point to the * attached encoder. * * For connectors which are not fixed (like built-in panels) the driver needs to @@ -189,13 +189,11 @@ int drm_connector_init(struct drm_device *dev, struct ida *connector_ida = &drm_connector_enum_list[connector_type].ida; - drm_modeset_lock_all(dev); - ret = drm_mode_object_get_reg(dev, &connector->base, DRM_MODE_OBJECT_CONNECTOR, false, drm_connector_free); if (ret) - goto out_unlock; + return ret; connector->base.properties = &connector->properties; connector->dev = dev; @@ -233,8 +231,10 @@ int drm_connector_init(struct drm_device *dev, /* We should add connectors at the end to avoid upsetting the connector * index too much. */ + spin_lock_irq(&config->connector_list_lock); list_add_tail(&connector->head, &config->connector_list); config->num_connector++; + spin_unlock_irq(&config->connector_list_lock); if (connector_type != DRM_MODE_CONNECTOR_VIRTUAL) drm_object_attach_property(&connector->base, @@ -259,9 +259,6 @@ out_put: if (ret) drm_mode_object_unregister(dev, &connector->base); -out_unlock: - drm_modeset_unlock_all(dev); - return ret; } EXPORT_SYMBOL(drm_connector_init); @@ -352,8 +349,10 @@ void drm_connector_cleanup(struct drm_connector *connector) drm_mode_object_unregister(dev, &connector->base); kfree(connector->name); connector->name = NULL; + spin_lock_irq(&dev->mode_config.connector_list_lock); list_del(&connector->head); dev->mode_config.num_connector--; + spin_unlock_irq(&dev->mode_config.connector_list_lock); WARN_ON(connector->state && !connector->funcs->atomic_destroy_state); if (connector->state && connector->funcs->atomic_destroy_state) @@ -444,30 +443,30 @@ EXPORT_SYMBOL(drm_connector_unregister); void drm_connector_unregister_all(struct drm_device *dev) { struct drm_connector *connector; + struct drm_connector_list_iter conn_iter; - /* FIXME: taking the mode config mutex ends up in a clash with sysfs */ - list_for_each_entry(connector, &dev->mode_config.connector_list, head) + drm_connector_list_iter_get(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) drm_connector_unregister(connector); + drm_connector_list_iter_put(&conn_iter); } int drm_connector_register_all(struct drm_device *dev) { struct drm_connector *connector; - int ret; + struct drm_connector_list_iter conn_iter; + int ret = 0; - /* FIXME: taking the mode config mutex ends up in a clash with - * fbcon/backlight registration */ - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + drm_connector_list_iter_get(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { ret = drm_connector_register(connector); if (ret) - goto err; + break; } + drm_connector_list_iter_put(&conn_iter); - return 0; - -err: - mutex_unlock(&dev->mode_config.mutex); - drm_connector_unregister_all(dev); + if (ret) + drm_connector_unregister_all(dev); return ret; } @@ -489,6 +488,87 @@ const char *drm_get_connector_status_name(enum drm_connector_status status) } EXPORT_SYMBOL(drm_get_connector_status_name); +#ifdef CONFIG_LOCKDEP +static struct lockdep_map connector_list_iter_dep_map = { + .name = "drm_connector_list_iter" +}; +#endif + +/** + * drm_connector_list_iter_get - initialize a connector_list iterator + * @dev: DRM device + * @iter: connector_list iterator + * + * Sets @iter up to walk the &drm_mode_config.connector_list of @dev. @iter + * must always be cleaned up again by calling drm_connector_list_iter_put(). + * Iteration itself happens using drm_connector_list_iter_next() or + * drm_for_each_connector_iter(). + */ +void drm_connector_list_iter_get(struct drm_device *dev, + struct drm_connector_list_iter *iter) +{ + iter->dev = dev; + iter->conn = NULL; + lock_acquire_shared_recursive(&connector_list_iter_dep_map, 0, 1, NULL, _RET_IP_); +} +EXPORT_SYMBOL(drm_connector_list_iter_get); + +/** + * drm_connector_list_iter_next - return next connector + * @iter: connectr_list iterator + * + * Returns the next connector for @iter, or NULL when the list walk has + * completed. + */ +struct drm_connector * +drm_connector_list_iter_next(struct drm_connector_list_iter *iter) +{ + struct drm_connector *old_conn = iter->conn; + struct drm_mode_config *config = &iter->dev->mode_config; + struct list_head *lhead; + unsigned long flags; + + spin_lock_irqsave(&config->connector_list_lock, flags); + lhead = old_conn ? &old_conn->head : &config->connector_list; + + do { + if (lhead->next == &config->connector_list) { + iter->conn = NULL; + break; + } + + lhead = lhead->next; + iter->conn = list_entry(lhead, struct drm_connector, head); + + /* loop until it's not a zombie connector */ + } while (!kref_get_unless_zero(&iter->conn->base.refcount)); + spin_unlock_irqrestore(&config->connector_list_lock, flags); + + if (old_conn) + drm_connector_unreference(old_conn); + + return iter->conn; +} +EXPORT_SYMBOL(drm_connector_list_iter_next); + +/** + * drm_connector_list_iter_put - tear down a connector_list iterator + * @iter: connector_list iterator + * + * Tears down @iter and releases any resources (like &drm_connector references) + * acquired while walking the list. This must always be called, both when the + * iteration completes fully or when it was aborted without walking the entire + * list. + */ +void drm_connector_list_iter_put(struct drm_connector_list_iter *iter) +{ + iter->dev = NULL; + if (iter->conn) + drm_connector_unreference(iter->conn); + lock_release(&connector_list_iter_dep_map, 0, _RET_IP_); +} +EXPORT_SYMBOL(drm_connector_list_iter_put); + static const struct drm_prop_enum_list drm_subpixel_enum_list[] = { { SubPixelUnknown, "Unknown" }, { SubPixelHorizontalRGB, "Horizontal RGB" }, @@ -618,8 +698,8 @@ DRM_ENUM_NAME_FN(drm_get_tv_subconnector_name, * drivers this is only provided for backwards compatibility with existing * drivers, it remaps to controlling the "ACTIVE" property on the CRTC the * connector is linked to. Drivers should never set this property directly, - * it is handled by the DRM core by calling the ->dpms() callback in - * &drm_connector_funcs. Atomic drivers should implement this hook using + * it is handled by the DRM core by calling the &drm_connector_funcs.dpms + * callback. Atomic drivers should implement this hook using * drm_atomic_helper_connector_dpms(). This is the only property standard * connector property that userspace can change. * PATH: @@ -1085,43 +1165,65 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, memset(&u_mode, 0, sizeof(struct drm_mode_modeinfo)); - mutex_lock(&dev->mode_config.mutex); - connector = drm_connector_lookup(dev, out_resp->connector_id); - if (!connector) { - ret = -ENOENT; - goto out_unlock; - } + if (!connector) + return -ENOENT; + + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + encoder = drm_connector_get_encoder(connector); + if (encoder) + out_resp->encoder_id = encoder->base.id; + else + out_resp->encoder_id = 0; + + ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic, + (uint32_t __user *)(unsigned long)(out_resp->props_ptr), + (uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr), + &out_resp->count_props); + drm_modeset_unlock(&dev->mode_config.connection_mutex); + if (ret) + goto out_unref; for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) if (connector->encoder_ids[i] != 0) encoders_count++; + if ((out_resp->count_encoders >= encoders_count) && encoders_count) { + copied = 0; + encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr); + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + if (connector->encoder_ids[i] != 0) { + if (put_user(connector->encoder_ids[i], + encoder_ptr + copied)) { + ret = -EFAULT; + goto out_unref; + } + copied++; + } + } + } + out_resp->count_encoders = encoders_count; + + out_resp->connector_id = connector->base.id; + out_resp->connector_type = connector->connector_type; + out_resp->connector_type_id = connector->connector_type_id; + + mutex_lock(&dev->mode_config.mutex); if (out_resp->count_modes == 0) { connector->funcs->fill_modes(connector, dev->mode_config.max_width, dev->mode_config.max_height); } - /* delayed so we get modes regardless of pre-fill_modes state */ - list_for_each_entry(mode, &connector->modes, head) - if (drm_mode_expose_to_userspace(mode, file_priv)) - mode_count++; - - out_resp->connector_id = connector->base.id; - out_resp->connector_type = connector->connector_type; - out_resp->connector_type_id = connector->connector_type_id; out_resp->mm_width = connector->display_info.width_mm; out_resp->mm_height = connector->display_info.height_mm; out_resp->subpixel = connector->display_info.subpixel_order; out_resp->connection = connector->status; - drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); - encoder = drm_connector_get_encoder(connector); - if (encoder) - out_resp->encoder_id = encoder->base.id; - else - out_resp->encoder_id = 0; + /* delayed so we get modes regardless of pre-fill_modes state */ + list_for_each_entry(mode, &connector->modes, head) + if (drm_mode_expose_to_userspace(mode, file_priv)) + mode_count++; /* * This ioctl is called twice, once to determine how much space is @@ -1144,36 +1246,10 @@ int drm_mode_getconnector(struct drm_device *dev, void *data, } } out_resp->count_modes = mode_count; - - ret = drm_mode_object_get_properties(&connector->base, file_priv->atomic, - (uint32_t __user *)(unsigned long)(out_resp->props_ptr), - (uint64_t __user *)(unsigned long)(out_resp->prop_values_ptr), - &out_resp->count_props); - if (ret) - goto out; - - if ((out_resp->count_encoders >= encoders_count) && encoders_count) { - copied = 0; - encoder_ptr = (uint32_t __user *)(unsigned long)(out_resp->encoders_ptr); - for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { - if (connector->encoder_ids[i] != 0) { - if (put_user(connector->encoder_ids[i], - encoder_ptr + copied)) { - ret = -EFAULT; - goto out; - } - copied++; - } - } - } - out_resp->count_encoders = encoders_count; - out: - drm_modeset_unlock(&dev->mode_config.connection_mutex); - - drm_connector_unreference(connector); -out_unlock: mutex_unlock(&dev->mode_config.mutex); +out_unref: + drm_connector_unreference(connector); return ret; } |