In order to enable drivers to fill their initial state from the hardware state, we need to provide an alternative atomic_reset helper.
This helper relies on each state having its own atomic_state_readout() hooks. Each component will thus be able to fill the initial state based on what they can figure out from the hardware. It also allocates a dummy drm_atomic_state to glue the whole thing together so atomic_state_readout implementations can still figure out the state of other related entities. Link: https://lore.kernel.org/dri-devel/CAKMK7uHtqHy_oz4W7F+hmp9iqp7W5Ra8CxPvJ=9bwmvfu-o...@mail.gmail.com/ Signed-off-by: Maxime Ripard <mrip...@kernel.org> --- drivers/gpu/drm/drm_atomic_helper.c | 382 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/drm_mode_config.c | 1 + include/drm/drm_atomic_helper.h | 1 + include/drm/drm_bridge.h | 21 ++ include/drm/drm_connector.h | 26 +++ include/drm/drm_crtc.h | 19 ++ include/drm/drm_plane.h | 27 +++ 7 files changed, 477 insertions(+) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index d5ebe6ea0acbc5a08aef7fa41ecb9ed5d8fa8e80..f59512476ebf2b48e1c7034950bcaf99237f03c6 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -45,10 +45,392 @@ #include <drm/drm_vblank.h> #include <drm/drm_writeback.h> #include "drm_crtc_helper_internal.h" #include "drm_crtc_internal.h" +#include "drm_internal.h" + +static void drm_atomic_set_old_plane_state(struct drm_atomic_state *state, + struct drm_plane *plane, + struct drm_plane_state *plane_state) +{ + int idx = drm_plane_index(plane); + + state->planes[idx].old_state = plane_state; + state->planes[idx].ptr = plane; + + drm_dbg_atomic(plane->dev, "Added [PLANE:%d:%s] %p state to %p\n", + plane->base.id, plane->name, plane_state, state); +} + +static void drm_atomic_set_old_crtc_state(struct drm_atomic_state *state, + struct drm_crtc *crtc, + struct drm_crtc_state *crtc_state) +{ + int idx = drm_crtc_index(crtc); + + state->crtcs[idx].old_state = crtc_state; + state->crtcs[idx].ptr = crtc; + + drm_dbg_atomic(state->dev, "Added [CRTC:%d:%s] %p state to %p\n", + crtc->base.id, crtc->name, crtc_state, state); +} + +static void drm_atomic_set_old_connector_state(struct drm_atomic_state *state, + struct drm_connector *conn, + struct drm_connector_state *conn_state) +{ + int idx = drm_connector_index(conn); + + drm_connector_get(conn); + state->connectors[idx].old_state = conn_state; + state->connectors[idx].ptr = conn; + state->num_connector++; + + drm_dbg_atomic(conn->dev, + "Added [CONNECTOR:%d:%s] %p state to %p\n", + conn->base.id, conn->name, conn_state, state); +} + +static void drm_atomic_set_old_private_obj_state(struct drm_atomic_state *state, + struct drm_private_obj *obj, + struct drm_private_state *obj_state) +{ + int idx = state->num_private_objs; + + memset(&state->private_objs[idx], 0, sizeof(*state->private_objs)); + state->private_objs[idx].old_state = obj_state; + state->private_objs[idx].ptr = obj; + state->num_private_objs++; + + drm_dbg_atomic(state->dev, + "Added new private object %p state %p to %p\n", obj, + obj_state, state); +} + +static void drm_atomic_set_old_bridge_state(struct drm_atomic_state *state, + struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state) +{ + drm_atomic_set_old_private_obj_state(state, &bridge->base, &bridge_state->base); +} + +static struct drm_connector_state * +find_connector_state_for_encoder(struct drm_atomic_state *state, + struct drm_encoder *encoder) +{ + struct drm_connector_list_iter conn_iter; + struct drm_connector *connector; + + drm_connector_list_iter_begin(state->dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + struct drm_connector_state *conn_state = + drm_atomic_get_old_connector_state(state, connector); + + if (WARN_ON(!conn_state)) + continue; + + if (encoder == conn_state->best_encoder) + return conn_state; + } + drm_connector_list_iter_end(&conn_iter); + + return NULL; +} + +/** + * drm_atomic_helper_install_readout_state - Sets the state pointer from their readout state + * @state: drm_atomic_state to initialize the DRM device with. + * + * This function takes a &struct drm_atomic_state initialized by + * drm_atomic_build_readout_state() and sets up the entities state + * pointers (ie, &drm_crtc.state and similar) to properly setup the + * initial state. + */ +static void +drm_atomic_helper_install_readout_state(struct drm_atomic_state *state) +{ + struct drm_connector_state *old_conn_state; + struct drm_private_state *old_obj_state; + struct drm_plane_state *old_plane_state; + struct drm_crtc_state *old_crtc_state; + struct drm_connector *connector; + struct drm_private_obj *obj; + struct drm_plane *plane; + struct drm_crtc *crtc; + unsigned int i; + + for_each_old_connector_in_state(state, connector, old_conn_state, i) { + connector->state = old_conn_state; + + drm_connector_put(state->connectors[i].ptr); + state->connectors[i].ptr = NULL; + state->connectors[i].old_state = NULL; + } + + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + crtc->state = old_crtc_state; + + state->crtcs[i].ptr = NULL; + state->crtcs[i].old_state = NULL; + } + + for_each_old_plane_in_state(state, plane, old_plane_state, i) { + plane->state = old_plane_state; + + state->planes[i].ptr = NULL; + state->planes[i].old_state = NULL; + } + + for_each_old_private_obj_in_state(state, obj, old_obj_state, i) { + obj->state = old_obj_state; + + state->private_objs[i].ptr = NULL; + state->private_objs[i].old_state = NULL; + } +} + +static unsigned int count_private_obj(struct drm_device *dev) +{ + struct drm_mode_config *config = &dev->mode_config; + struct drm_private_obj *obj; + unsigned int count = 0; + + list_for_each_entry(obj, &config->privobj_list, head) + count++; + + return count; +} + +/** + * drm_atomic_build_readout_state - Creates an initial state from the hardware + * @dev: DRM device to build the state for + * + * This function allocates a &struct drm_atomic_state, calls the + * atomic_readout_state callbacks, and fills the global state old states + * by what the callbacks returned. + * + * Returns: + * + * A partially initialized &struct drm_atomic_state on success, an error + * pointer otherwise. + */ +static struct drm_atomic_state * +drm_atomic_build_readout_state(struct drm_device *dev) +{ + struct drm_connector_list_iter conn_iter; + struct drm_atomic_state *state; + struct drm_mode_config *config = + &dev->mode_config; + struct drm_connector *connector; + struct drm_printer p = + drm_info_printer(dev->dev); + struct drm_encoder *encoder; + struct drm_plane *plane; + struct drm_crtc *crtc; + int ret; + + drm_dbg_kms(dev, "Starting to build atomic state from hardware state.\n"); + + state = drm_atomic_state_alloc(dev); + if (WARN_ON(!state)) + return ERR_PTR(-ENOMEM); + + state->connectors = kcalloc(config->num_connector, sizeof(*state->connectors), GFP_KERNEL); + if (WARN_ON(!state->connectors)) { + ret = -ENOMEM; + goto err_state_put; + } + + state->private_objs = kcalloc(count_private_obj(dev), sizeof(*state->private_objs), GFP_KERNEL); + if (WARN_ON(!state->private_objs)) { + ret = -ENOMEM; + goto err_state_put; + } + + drm_for_each_crtc(crtc, dev) { + const struct drm_crtc_funcs *crtc_funcs = + crtc->funcs; + struct drm_crtc_state *crtc_state; + + drm_dbg_kms(dev, "Initializing CRTC %s state.\n", crtc->name); + + if (crtc_funcs->atomic_readout_state) { + crtc_state = crtc_funcs->atomic_readout_state(crtc); + } else if (crtc_funcs->reset) { + crtc_funcs->reset(crtc); + + /* + * We don't want to set crtc->state field yet. Let's save and clear it up. + */ + crtc_state = crtc->state; + crtc->state = NULL; + } else { + drm_warn(dev, "No CRTC readout or reset implementation."); + continue; + } + + if (WARN_ON(IS_ERR(crtc_state))) { + ret = PTR_ERR(crtc_state); + goto err_state_put; + } + + drm_atomic_set_old_crtc_state(state, crtc, crtc_state); + } + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(connector, &conn_iter) { + const struct drm_connector_funcs *conn_funcs = + connector->funcs; + struct drm_connector_state *conn_state; + + drm_dbg_kms(dev, "Initializing Connector %s state.\n", connector->name); + + if (conn_funcs->atomic_readout_state) { + conn_state = conn_funcs->atomic_readout_state(connector, state); + } else if (conn_funcs->reset) { + conn_funcs->reset(connector); + + /* + * We don't want to set connector->state field yet. Let's save and clear it + * up. + */ + conn_state = connector->state; + connector->state = NULL; + } else { + drm_warn(dev, "No Connector readout or reset implementation."); + continue; + } + + if (WARN_ON(IS_ERR(conn_state))) { + ret = PTR_ERR(conn_state); + goto err_state_put; + } + + drm_atomic_set_old_connector_state(state, connector, conn_state); + } + drm_connector_list_iter_end(&conn_iter); + + WARN_ON(state->num_connector != config->num_connector); + + drm_for_each_encoder(encoder, dev) { + struct drm_connector_state *enc_conn_state; + struct drm_crtc_state *enc_crtc_state; + struct drm_bridge *bridge; + + /* + * It works a bit differently for bridges. Because they are + * using a drm_private_state, and because + * drm_atomic_private_obj_init() asks for its initial state when + * initializing, instead of doing it later on through a reset + * call like the other entities, we can't have reset xor + * readout. + * + * We'll need a mandatory reset to create that initial, blank, + * state, and then readout will fill that state later on if the + * driver implements it. + * + * This also means we don't need to call the readout state + * function if we don't have the bridge enabled (ie, if no + * drm_connector_state->best_encoder points to bridge->encoder, + * and / or if drm_connector_state->crtc is NULL). + * + * In such a case, we would get the blank state reset created + * during registration. + */ + + enc_conn_state = find_connector_state_for_encoder(state, encoder); + if (!enc_conn_state) + continue; + + enc_crtc_state = drm_atomic_get_old_crtc_state(state, enc_conn_state->crtc); + if (!enc_crtc_state) + continue; + + list_for_each_entry(bridge, &encoder->bridge_chain, chain_node) { + const struct drm_bridge_funcs *bridge_funcs = bridge->funcs; + struct drm_bridge_state *bridge_state; + + bridge_state = drm_bridge_get_current_state(bridge); + if (WARN_ON(!bridge_state)) { + ret = -EINVAL; + goto err_state_put; + } + + if (bridge_funcs->atomic_readout_state) { + ret = bridge_funcs->atomic_readout_state(bridge, + bridge_state, + enc_crtc_state, + enc_conn_state); + if (WARN_ON(ret)) + goto err_state_put; + } + + drm_atomic_set_old_bridge_state(state, bridge, bridge_state); + } + } + + drm_for_each_plane(plane, dev) { + const struct drm_plane_funcs *plane_funcs = + plane->funcs; + struct drm_plane_state *plane_state; + + drm_dbg_kms(dev, "Initializing Plane %s state.\n", plane->name); + + if (plane_funcs->atomic_readout_state) { + plane_state = plane_funcs->atomic_readout_state(plane, state); + } else if (plane_funcs->reset) { + plane_funcs->reset(plane); + + /* + * We don't want to set conn->state field yet. Let's save and clear it up. + */ + plane_state = plane->state; + plane->state = NULL; + } else { + drm_warn(dev, "No plane readout or reset implementation."); + continue; + } + + if (WARN_ON(IS_ERR(plane_state))) { + ret = PTR_ERR(plane_state); + goto err_state_put; + } + + drm_atomic_set_old_plane_state(state, plane, plane_state); + } + + drm_atomic_print_old_state(state, &p); + + return state; + +err_state_put: + drm_atomic_state_put(state); + return ERR_PTR(ret); +} + +/** + * drm_atomic_helper_readout_state - Builds an initial state from hardware state + * @dev: DRM device to build the state for + * + * This function creates the initial state for all the entities on a + * @dev. Drivers can use this as their + * &drm_mode_config_helper_funcs.atomic_reset callback to implement + * hardware state readout suppport. + */ +void drm_atomic_helper_readout_state(struct drm_device *dev) +{ + struct drm_atomic_state *state; + + state = drm_atomic_build_readout_state(dev); + if (IS_ERR(state)) + return; + + drm_atomic_helper_install_readout_state(state); + drm_atomic_state_put(state); +} +EXPORT_SYMBOL(drm_atomic_helper_readout_state); /** * DOC: overview * * This helper library provides implementations of check and commit functions on diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c index 82180760032d3490d63fe83136465d2c26551d08..96d38a49be501a0090457cbe96135f82bb1358b5 100644 --- a/drivers/gpu/drm/drm_mode_config.c +++ b/drivers/gpu/drm/drm_mode_config.c @@ -26,10 +26,11 @@ #include <drm/drm_drv.h> #include <drm/drm_encoder.h> #include <drm/drm_file.h> #include <drm/drm_framebuffer.h> #include <drm/drm_managed.h> +#include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_mode_config.h> #include <drm/drm_modeset_helper_vtables.h> #include <drm/drm_print.h> #include <linux/dma-resv.h> diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h index 53382fe93537bbcda9cee8827bc95de9a515efb5..47902a9181727a08581fb808faabe67d92a755cf 100644 --- a/include/drm/drm_atomic_helper.h +++ b/include/drm/drm_atomic_helper.h @@ -45,10 +45,11 @@ struct drm_atomic_state; struct drm_private_obj; struct drm_private_state; +void drm_atomic_helper_readout_state(struct drm_device *dev); int drm_atomic_helper_check_modeset(struct drm_device *dev, struct drm_atomic_state *state); int drm_atomic_helper_check_wb_connector_state(struct drm_connector *connector, struct drm_atomic_state *state); int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 8d9d4fd078e72977677fd992d725261232754e3e..15b63053f01869786831936ba28b7efc1e55e2e8 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -490,10 +490,31 @@ struct drm_bridge_funcs { * The @atomic_post_disable callback is optional. */ void (*atomic_post_disable)(struct drm_bridge *bridge, struct drm_atomic_state *state); + /** + * @atomic_readout_state: + * + * Initializes,this bridge atomic state. + * + * It's meant to be used by drivers that wants to implement fast + * / flicker-free boot and allows to initialize the atomic state + * from the hardware state left by the firmware. + * + * It's used at initialization time, so drivers must make sure + * that the power state is sensible when accessing the hardware. + * + * RETURNS: + * + * 0 on success, an error code otherwise. + */ + int (*atomic_readout_state)(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); + /** * @atomic_duplicate_state: * * Duplicate the current bridge state object (which is guaranteed to be * non-NULL). diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 8f34f4b8183d83dccd3e820a444fbf74fb6c16f2..f68bd9627c085c6d2463b847aaa245ccc651f27b 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1464,10 +1464,36 @@ struct drm_connector_funcs { * when a connector is being hot-unplugged for drivers that support * connector hotplugging (e.g. DisplayPort MST). */ void (*destroy)(struct drm_connector *connector); + /** + * @atomic_readout_state: + * + * Allocates, initializes, and returns an atomic state for this + * connector. + * + * It's meant to be used by drivers that wants to implement fast + * / flicker-free boot and allows to initialize the atomic state + * from the hardware state left by the firmware. + * + * It's used at initialization time, so drivers must make sure + * that the power state is sensible when accessing the hardware. + * + * The drm_atomic_state being passed is not fully filled. Only + * the CRTC state are there when this hooks is called, and only + * their old state. The only safe operation one can do on this + * state in this hook is calling + * drm_atomic_get_old_crtc_state(). + * + * RETURNS: + * + * An atomic state on success, an error pointer otherwise. + */ + struct drm_connector_state *(*atomic_readout_state)(struct drm_connector *connector, + struct drm_atomic_state *state); + /** * @atomic_duplicate_state: * * Duplicate the current atomic state for this connector and return it. * The core and helpers guarantee that any atomic state duplicated with diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index caa56e039da2a748cf40ebf45b37158acda439d9..c462bd9b2f7d3ae08e669463717002e5f78122fe 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -613,10 +613,29 @@ struct drm_crtc_funcs { * 0 on success or a negative error code on failure. */ int (*set_property)(struct drm_crtc *crtc, struct drm_property *property, uint64_t val); + /** + * @atomic_readout_state: + * + * Allocates, initializes, and returns an atomic state for this + * CRTC. + * + * It's meant to be used by drivers that wants to implement fast + * / flicker-free boot and allows to initialize the atomic state + * from the hardware state left by the firmware. + * + * It's used at initialization time, so drivers must make sure + * that the power state is sensible when accessing the hardware. + * + * RETURNS: + * + * An atomic state on success, an error pointer otherwise. + */ + struct drm_crtc_state *(*atomic_readout_state)(struct drm_crtc *crtc); + /** * @atomic_duplicate_state: * * Duplicate the current atomic state for this CRTC and return it. * The core and helpers guarantee that any atomic state duplicated with diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h index 01479dd94e76a8389a0c9e9d6744400aa2291064..691a267c857a228f674ef02a63fb6d1ff9e379a8 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -378,10 +378,37 @@ struct drm_plane_funcs { * 0 on success or a negative error code on failure. */ int (*set_property)(struct drm_plane *plane, struct drm_property *property, uint64_t val); + /** + * @atomic_readout_state: + * + * Allocates, initializes, and returns an atomic state for this + * plane. + * + * It's meant to be used by drivers that wants to implement fast + * / flicker-free boot and allows to initialize the atomic state + * from the hardware state left by the firmware. + * + * It's used at initialization time, so drivers must make sure + * that the power state is sensible when accessing the hardware. + * + * The drm_atomic_state being passed is not fully filled. Only + * the CRTC and connector states are there when this hooks is + * called, and only their old state. The only safe operation one + * can do on this state in this hook is calling + * drm_atomic_get_old_crtc_state() and + * drm_atomic_get_old_connector_state(). + * + * RETURNS: + * + * An atomic state on success, an error pointer otherwise. + */ + struct drm_plane_state *(*atomic_readout_state)(struct drm_plane *plane, + struct drm_atomic_state *state); + /** * @atomic_duplicate_state: * * Duplicate the current atomic state for this plane and return it. * The core and helpers guarantee that any atomic state duplicated with -- 2.50.1