DRM framework (vkms) analysis - connector initialization

Posted by JellyFish on Thu, 20 Jan 2022 20:50:23 +0100

This paper mainly analyzes the initialization and configuration of connector, DRM_ The connector structure is as follows:

/**
 * struct drm_connector - central DRM connector control structure
 *
 * Each connector may be connected to one or more CRTCs, or may be clonable by
 * another connector if they can share a CRTC.  Each connector also has a specific
 * position in the broader display (referred to as a 'screen' though it could
 * span multiple monitors).
 */
struct drm_connector {
	/** @dev: parent DRM device */
	struct drm_device *dev;
	/** @kdev: kernel device for sysfs attributes */
	struct device *kdev;
	/** @attr: sysfs attributes */
	struct device_attribute *attr;

	/**
	 * @head:
	 *
	 * List of all connectors on a @dev, linked from
	 * &drm_mode_config.connector_list. Protected by
	 * &drm_mode_config.connector_list_lock, but please only use
	 * &drm_connector_list_iter to walk this list.
	 */
	struct list_head head;

	/** @base: base KMS object */
	struct drm_mode_object base;

	/** @name: human readable name, can be overwritten by the driver */
	char *name;

	/**
	 * @mutex: Lock for general connector state, but currently only protects
	 * @registered. Most of the connector state is still protected by
	 * &drm_mode_config.mutex.
	 */
	struct mutex mutex;

	/**
	 * @index: Compacted connector index, which matches the position inside
	 * the mode_config.list for drivers not supporting hot-add/removing. Can
	 * be used as an array index. It is invariant over the lifetime of the
	 * connector.
	 */
	unsigned index;

	/**
	 * @connector_type:
	 * one of the DRM_MODE_CONNECTOR_<foo> types from drm_mode.h
	 */
	int connector_type;
	/** @connector_type_id: index into connector type enum */
	int connector_type_id;
	/**
	 * @interlace_allowed:
	 * Can this connector handle interlaced modes? Only used by
	 * drm_helper_probe_single_connector_modes() for mode filtering.
	 */
	bool interlace_allowed;
	/**
	 * @doublescan_allowed:
	 * Can this connector handle doublescan? Only used by
	 * drm_helper_probe_single_connector_modes() for mode filtering.
	 */
	bool doublescan_allowed;
	/**
	 * @stereo_allowed:
	 * Can this connector handle stereo modes? Only used by
	 * drm_helper_probe_single_connector_modes() for mode filtering.
	 */
	bool stereo_allowed;

	/**
	 * @ycbcr_420_allowed : This bool indicates if this connector is
	 * capable of handling YCBCR 420 output. While parsing the EDID
	 * blocks it's very helpful to know if the source is capable of
	 * handling YCBCR 420 outputs.
	 */
	bool ycbcr_420_allowed;

	/**
	 * @registration_state: Is this connector initializing, exposed
	 * (registered) with userspace, or unregistered?
	 *
	 * Protected by @mutex.
	 */
	enum drm_connector_registration_state registration_state;

	/**
	 * @modes:
	 * Modes available on this connector (from fill_modes() + user).
	 * Protected by &drm_mode_config.mutex.
	 */
	struct list_head modes;

	/**
	 * @status:
	 * One of the drm_connector_status enums (connected, not, or unknown).
	 * Protected by &drm_mode_config.mutex.
	 */
	enum drm_connector_status status;

	/**
	 * @probed_modes:
	 * These are modes added by probing with DDC or the BIOS, before
	 * filtering is applied. Used by the probe helpers. Protected by
	 * &drm_mode_config.mutex.
	 */
	struct list_head probed_modes;

	/**
	 * @display_info: Display information is filled from EDID information
	 * when a display is detected. For non hot-pluggable displays such as
	 * flat panels in embedded systems, the driver should initialize the
	 * &drm_display_info.width_mm and &drm_display_info.height_mm fields
	 * with the physical size of the display.
	 *
	 * Protected by &drm_mode_config.mutex.
	 */
	struct drm_display_info display_info;

	/** @funcs: connector control functions */
	const struct drm_connector_funcs *funcs;

	/**
	 * @edid_blob_ptr: DRM property containing EDID if present. Protected by
	 * &drm_mode_config.mutex. This should be updated only by calling
	 * drm_connector_update_edid_property().
	 */
	struct drm_property_blob *edid_blob_ptr;

	/** @properties: property tracking for this connector */
	struct drm_object_properties properties;

	/**
	 * @scaling_mode_property: Optional atomic property to control the
	 * upscaling. See drm_connector_attach_content_protection_property().
	 */
	struct drm_property *scaling_mode_property;

	/**
	 * @vrr_capable_property: Optional property to help userspace
	 * query hardware support for variable refresh rate on a connector.
	 * connector. Drivers can add the property to a connector by
	 * calling drm_connector_attach_vrr_capable_property().
	 *
	 * This should be updated only by calling
	 * drm_connector_set_vrr_capable_property().
	 */
	struct drm_property *vrr_capable_property;

	/**
	 * @colorspace_property: Connector property to set the suitable
	 * colorspace supported by the sink.
	 */
	struct drm_property *colorspace_property;

	/**
	 * @path_blob_ptr:
	 *
	 * DRM blob property data for the DP MST path property. This should only
	 * be updated by calling drm_connector_set_path_property().
	 */
	struct drm_property_blob *path_blob_ptr;

	/**
	 * @max_bpc_property: Default connector property for the max bpc to be
	 * driven out of the connector.
	 */
	struct drm_property *max_bpc_property;

#define DRM_CONNECTOR_POLL_HPD (1 << 0)
#define DRM_CONNECTOR_POLL_CONNECT (1 << 1)
#define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2)

	/**
	 * @polled:
	 *
	 * Connector polling mode, a combination of
	 *
	 * DRM_CONNECTOR_POLL_HPD
	 *     The connector generates hotplug events and doesn't need to be
	 *     periodically polled. The CONNECT and DISCONNECT flags must not
	 *     be set together with the HPD flag.
	 *
	 * DRM_CONNECTOR_POLL_CONNECT
	 *     Periodically poll the connector for connection.
	 *
	 * DRM_CONNECTOR_POLL_DISCONNECT
	 *     Periodically poll the connector for disconnection, without
	 *     causing flickering even when the connector is in use. DACs should
	 *     rarely do this without a lot of testing.
	 *
	 * Set to 0 for connectors that don't support connection status
	 * discovery.
	 */
	uint8_t polled;

	/**
	 * @dpms: Current dpms state. For legacy drivers the
	 * &drm_connector_funcs.dpms callback must update this. For atomic
	 * drivers, this is handled by the core atomic code, and drivers must
	 * only take &drm_crtc_state.active into account.
	 */
	int dpms;

	/** @helper_private: mid-layer private data */
	const struct drm_connector_helper_funcs *helper_private;

	/** @cmdline_mode: mode line parsed from the kernel cmdline for this connector */
	struct drm_cmdline_mode cmdline_mode;
	/** @force: a DRM_FORCE_<foo> state for forced mode sets */
	enum drm_connector_force force;
	/** @override_edid: has the EDID been overwritten through debugfs for testing? */
	bool override_edid;
	/** @epoch_counter: used to detect any other changes in connector, besides status */
	u64 epoch_counter;

	/**
	 * @possible_encoders: Bit mask of encoders that can drive this
	 * connector, drm_encoder_index() determines the index into the bitfield
	 * and the bits are set with drm_connector_attach_encoder().
	 */
	u32 possible_encoders;

	/**
	 * @encoder: Currently bound encoder driving this connector, if any.
	 * Only really meaningful for non-atomic drivers. Atomic drivers should
	 * instead look at &drm_connector_state.best_encoder, and in case they
	 * need the CRTC driving this output, &drm_connector_state.crtc.
	 */
	struct drm_encoder *encoder;

#define MAX_ELD_BYTES	128
	/** @eld: EDID-like data, if present */
	uint8_t eld[MAX_ELD_BYTES];
	/** @latency_present: AV delay info from ELD, if found */
	bool latency_present[2];
	/**
	 * @video_latency: Video latency info from ELD, if found.
	 * [0]: progressive, [1]: interlaced
	 */
	int video_latency[2];
	/**
	 * @audio_latency: audio latency info from ELD, if found
	 * [0]: progressive, [1]: interlaced
	 */
	int audio_latency[2];

	/**
	 * @ddc: associated ddc adapter.
	 * A connector usually has its associated ddc adapter. If a driver uses
	 * this field, then an appropriate symbolic link is created in connector
	 * sysfs directory to make it easy for the user to tell which i2c
	 * adapter is for a particular display.
	 *
	 * The field should be set by calling drm_connector_init_with_ddc().
	 */
	struct i2c_adapter *ddc;

	/**
	 * @null_edid_counter: track sinks that give us all zeros for the EDID.
	 * Needed to workaround some HW bugs where we get all 0s
	 */
	int null_edid_counter;

	/** @bad_edid_counter: track sinks that give us an EDID with invalid checksum */
	unsigned bad_edid_counter;

	/**
	 * @edid_corrupt: Indicates whether the last read EDID was corrupt. Used
	 * in Displayport compliance testing - Displayport Link CTS Core 1.2
	 * rev1.1 4.2.2.6
	 */
	bool edid_corrupt;
	/**
	 * @real_edid_checksum: real edid checksum for corrupted edid block.
	 * Required in Displayport 1.4 compliance testing
	 * rev1.1 4.2.2.6
	 */
	u8 real_edid_checksum;

	/** @debugfs_entry: debugfs directory for this connector */
	struct dentry *debugfs_entry;

	/**
	 * @state:
	 *
	 * Current atomic state for this connector.
	 *
	 * This is protected by &drm_mode_config.connection_mutex. Note that
	 * nonblocking atomic commits access the current connector state without
	 * taking locks. Either by going through the &struct drm_atomic_state
	 * pointers, see for_each_oldnew_connector_in_state(),
	 * for_each_old_connector_in_state() and
	 * for_each_new_connector_in_state(). Or through careful ordering of
	 * atomic commit operations as implemented in the atomic helpers, see
	 * &struct drm_crtc_commit.
	 */
	struct drm_connector_state *state;

	/* DisplayID bits. FIXME: Extract into a substruct? */

	/**
	 * @tile_blob_ptr:
	 *
	 * DRM blob property data for the tile property (used mostly by DP MST).
	 * This is meant for screens which are driven through separate display
	 * pipelines represented by &drm_crtc, which might not be running with
	 * genlocked clocks. For tiled panels which are genlocked, like
	 * dual-link LVDS or dual-link DSI, the driver should try to not expose
	 * the tiling and virtualize both &drm_crtc and &drm_plane if needed.
	 *
	 * This should only be updated by calling
	 * drm_connector_set_tile_property().
	 */
	struct drm_property_blob *tile_blob_ptr;

	/** @has_tile: is this connector connected to a tiled monitor */
	bool has_tile;
	/** @tile_group: tile group for the connected monitor */
	struct drm_tile_group *tile_group;
	/** @tile_is_single_monitor: whether the tile is one monitor housing */
	bool tile_is_single_monitor;

	/** @num_h_tile: number of horizontal tiles in the tile group */
	/** @num_v_tile: number of vertical tiles in the tile group */
	uint8_t num_h_tile, num_v_tile;
	/** @tile_h_loc: horizontal location of this tile */
	/** @tile_v_loc: vertical location of this tile */
	uint8_t tile_h_loc, tile_v_loc;
	/** @tile_h_size: horizontal size of this tile. */
	/** @tile_v_size: vertical size of this tile. */
	uint16_t tile_h_size, tile_v_size;

	/**
	 * @free_node:
	 *
	 * List used only by &drm_connector_list_iter to be able to clean up a
	 * connector from any context, in conjunction with
	 * &drm_mode_config.connector_free_work.
	 */
	struct llist_node free_node;

	/** @hdr_sink_metadata: HDR Metadata Information read from sink */
	struct hdr_sink_metadata hdr_sink_metadata;
};

drm_ The main initialization interface of connector is drm_connector_init

Here we analyze some common parameters.

(1)connector->head

After the connecotr instance is initialized, it is hung on the DRM through the head_ device. mode_ config. connector_ On the list

drm_connector_init
    list_add_tail(&connector->head, &config->connector_list)
    config->num_connector++

, note each DRM_ The device has only one mode_config member, so you can use mode_config.connector_list traverses all connector instances of the device, which can also be accessed in the following ways

drm_connector_list_iter_begin(dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter);
    // ... use connector to process
drm_connector_list_iter_end(&conn_iter);

(2) Connector - > base (drm_mode_object type)

Pass__ drm_mode_object_add to dev - > mode_ config. object_ IDR application id (base.id=id, base.type=DRM_MODE_OBJECT_CONNECTOR)

drm_connector_init
    ret=__drm_mode_object_add(dev, &connector->base,
            DRM_MODE_OBJECT_CONNECTOR, false, drm_connector_free);
        ret=idr_alloc(&dev->mode_config.object_idr, register_obj ? obj : NULL,
                1, 0, GFP_KERNEL);
        0bj->id = ret;
        obj->type = obj_type
        return ret;

Note that connector - > base ID will be used as the connector when the user interface drmModeGetResource is called_ The ID is returned to the user status, and the subsequent user status can be through the connector_id, call drmModeGetConnector to find drm_connector and get its related parameters

Get connector in user mode_ The ID logic is as follows:

//User state
drmModeGetResources
    drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res)
        //res.crtc_id_ptr points to all crtcs of the device_ id
//Kernel state
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0)
   drm_mode_getresources
        drm_connector_list_iter_begin(dev, &conn_iter);
            drm_for_each_connector_iter(connector, &conn_iter); //Get connector recursively
                put_user(connector->base.id, connector_id+count); //Copy id to user status
        drm_connector_list_iter_end(&conn_iter);

User status according to connector_id get DRM_ The logic of the connector is as follows:

//User state
drmModeGetConnector()
    conn.connector_id = connector_id;
    drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)
//Kernel state
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, 0)
    drm_mode_getconnector
        connector = drm_connector_lookup(dev, file_priv, out_resp->connector_id)
                        //According to the id and type passed in from the user status (drm_mode_object_connector finds the drm mode obj type)
                        mo = drm_mode_object_find(dev, file_priv, id, DRM_MODE_OBJECT_CONNECTOR);
                        return obj_to_connector(mo)

(3)connector->base.properties

Record the attributes of the connector (such as crtc_id / EDID / DPMS / link status / non desktop / title, etc.). These attributes are first recorded in drm_mode_create_standard_properties are created, initialized and saved in dev - > mode_ Config, as follows:

//With 'CRTC'_ 'ID' attribute as an example
drm_mode_config_init
    drmm_mode_config_init
        drm_mode_create_standard_properties
            //Create an attribute named "CRTC_ID"
            prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC, 
                    "CRTC_ID", DRM_MODE_OBJECT_CRTTC);
                    //Create attribute
                    drm_property_create
                        //Save the property in mode_config property_list linked list
                        list_add_tail(&property->head, &dev->mode_config.property_list)
            //Save to mode_ config. prop_ crtc_ In ID
            dev->mode_config.prop_crtc_id = prop

In drm_connector_init will attach the initialized attribute to base properties

drm_connector_init
    connector->base.properties = &connector->properties
    drm_object_attach_property(&connector->base,
                            config->dpms_property)
    drm_object_attach_property(&connector->base,
                            config->link_status_property)
    drm_object_attach_property(&connector->base, config->prop_crtc_id,0)
    ......

Getting properties

//User state
drmModeObjectGetProperties
    //Via connector_id and DM_MODE_OBJECT_CONNECTOR gets all its properties
    drmIoctl(fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &properties)
//Kernel state
 DRM_IOCTL_DEF(DRM_IOCTL_MODE_OBJ_GETPROPERTIES, drm_mode_obj_get_properties_ioctl,0)
    //According to obj_id(connector_id), and obj_type(DRM_MODE_OBJECT_CONNECTOR) gets the DRM of the connector_ mode_ Object instance
    obj = drm_mode_object_find(dev, file_priv, arg->obj_id, arg->obj_type)
    //Traverse connector - > base Properties and copy them to the user status,
    //Here, only the id and value of the properties are copied, and no name is copied
    drm_mode_object_get_properties(obj, file_priv->atomic,
                                    arg->props_ptr, arg->prop_values_ptr, ...);


//All attribute IDs and value s of the connector are obtained above. If you want to obtain an attribute with a specific name, you also need to do the following:
//User state
drmModeGetProperty
    //According to prop_id get property instance object
    drmIoctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop) 
//Kernel state
    DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getProperty_ioctl,0)
        //According to prop_id gets the attribute instance and copies it to the user state      

There are three methods to set properties:

1) All properties can be set directly through prop through drmModeObjSetProperty_ Find the prop instance ID and set it

drmModeObjectSetProperty
    DRM_IOCTL(fd, DRM_IOCTL_MODE_OBJ_SETPROPERTY, &prop)

2) The properties of the connector can be set through drmmodiconnectorsetproperty. This method will first pass through the connector_id found drm_connector instance, and then traverse the connector base. Properties by matching prop_ Find the corresponding prop instance by ID

drmModeConnectorSetProperty
    DRM_IOCTL(fd, DRM_IOCTL_MODE_SETPROPERTY, &osp)

3) Atomic operation, drmmodeatomicaddroperty. This method is analyzed separately later. Of course, the above two methods also modify attributes through atomic operation in the kernel state

(4)connector->cmdline_mode

It is used to save the display mode set by cmdline. The initialization process is as follows. How does cmdline write the reference document documentation / FB / modedb txt:

drm_connector_init
    drm_connector_get_cmdline_mode(connector)

cmdline_mode is mainly used to create the framebuffer of fbdev. This logic can be sorted out later. Please refer to the blog:

How does the DRM driver create fb device_ Additional reading notes - CSDN blog_ drm fb

(5)connector->possible_encoders

The encoders that identifies its connection is called drm_ after the connector and encoder initialization of the DRM device. connector_ attach_ Encoder binding.

Through this parameter, you can find the encoder connected by the connector. Through the macro

drm_connector_for_each_possible_encoder

(6)connector->probed_modesĀ 

The detected DRM obtained through DDC or BIOS_ display_ Mode type instance mode, through drm_mode_probed_add, add the interface to connector - > probe_ Modes linked list. There are three common calls as follows:

1. drm_add_modes_noedid
        mode = drm_mode_duplicate(dev, ptr);
        drm_mode_probed_add(connector, mode);
                list_add_tail(&mode->head, &connector->probed_modes);
2. drm_add_edid_modes
        add_standard_modes
        .....
3.drm_helper_probe_add_cmdline_mode
    mode = drm_mode_create_from_cmdline_mode(connector->dev, cmdline_mode);
    drm_mode_probed_add(connector,mode)

(7)connector->modes

connector valid drm_display_mode type instance mode linked list. There is only one update of the linked list, as follows:

//connector->funcs->fill_ Modes in DRM_ mode_ Call in getconnector
//.fill_modes = drm_helper_probe_single_connector_modes    
                    //From probe_ The modes in the modes linked list are added to the connector - > modes linked list
                    drm_connector_list_update

The mode instance in the connector - > modes linked list will be displayed in DRM_ mode_ Copy the getconnector to the user state, and the user state creates fb according to the width and height information of the obtained modes

//User state
drmModeGetConnector()
    conn.connector_id = connector_id;
    drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)
//Kernel state
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, 0)
    drm_mode_getconnector
        connector = drm_connector_lookup(dev, file_priv, out_resp->connector_id)
                        //According to the id and type passed in from the user status (drm_mode_object_connector finds the drm mode obj type)
        list_for_each_entry(mode, &connector->modes, head)
                //The user status is drm_mode_modeinfo type object. The kernel state is drm_display_mode type object
                //There needs to be a conversion
                drm_mode_convert_to_umode(&u_mode, mode); 

(8)connector->func && connector->helper_private

These two are both function pointers. Together, the helper function implements atomic calls.

Connector - > func in drm_ connector_ When init is initialized, func is some standard helper functions, and its internal will be finally through connector - > helper_ Private calls the interface customized by drm driver

static const struct drm_connector_funcs vkms_connector_funcs = {
	.fill_modes = drm_helper_probe_single_connector_modes,
	.destroy = vkms_connector_destroy,
	.reset = drm_atomic_helper_connector_reset,
	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};


vkms_output_init
    ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
                            DRM_MODE_CONNECTOR_VIRTUAL)
                connector->funcs =funcs ---->vkms_connector_funcs

connector->helper_ The initialization of private is as follows

static const struct drm_connector_helper_funcs vkms_conn_helper_funcs = {
	.get_modes    = vkms_conn_get_modes,
};

vkms_output_init
    ret = drm_connector_init(dev, connector, &vkms_connector_funcs,
				 DRM_MODE_CONNECTOR_VIRTUAL);
	drm_connector_helper_add(connector, &vkms_conn_helper_funcs);
        connector->helper_private = funcs-----> vkms_conn_helper_funcs

connector->func && connector->helper_ The usage logic of private is analyzed in the next section

Topics: gpu drm