本文主要分析connector的初始化和配置,drm_connector结构体如下:
/**
* 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_connector的主要初始化接口为drm_connector_init
我们这里分析一些常用参数。
(1)connector->head
connecotr实例初始化完成后,通过head挂在drm_device.mode_config.connector_list上
drm_connector_init
list_add_tail(&connector->head, &config->connector_list)
config->num_connector++
,注意每个drm_device设备只有一个mode_config成员,因此可以通过mode_config.connector_list遍历出这个设备的所有connector实例,也可以通过如下方式访问
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类型)
通过__drm_mode_object_add向dev->mode_config.object_idr申请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;
注意, connector->base.id会在用户态接口drmModeGetResource调用时, 作为connector_id返回给用户态,后续用户态可以通过connector_id,调用drmModeGetConnector找到drm_connector,并获取其相关参数
用户态获取connector_id逻辑如下:
//用户态
drmModeGetResources
drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res)
//res.crtc_id_ptr指向设备的所有crtc_id
//内核态
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); //递归获取connector
put_user(connector->base.id, connector_id+count); //拷贝id到用户态
drm_connector_list_iter_end(&conn_iter);
用户态根据connector_id获取drm_connector的逻辑如下:
//用户态
drmModeGetConnector()
conn.connector_id = connector_id;
drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)
//内核态
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)
//根据用户态传进来的id, 和type(DRM_MODE_OBJECT_CONNECTOR查找drm mode obj类型)
mo = drm_mode_object_find(dev, file_priv, id, DRM_MODE_OBJECT_CONNECTOR);
return obj_to_connector(mo)
(3)connector->base.properties
记录connector的属性(如CRTC_ID/EDID/DPMS/link-status/non-desktop/TITLE等),这些属性首先在drm_mode_create_standard_properties创建初始化并保存在dev->mode_config中,如下:
//以'CRTC_ID'属性为例
drm_mode_config_init
drmm_mode_config_init
drm_mode_create_standard_properties
//创建名称为“CRTC_ID”的属性
prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC,
"CRTC_ID", DRM_MODE_OBJECT_CRTTC);
//创建属性
drm_property_create
//将该property保存在mode_config的property_list链表找
list_add_tail(&property->head, &dev->mode_config.property_list)
//保存到mode_config.prop_crtc_id中
dev->mode_config.prop_crtc_id = prop
在drm_connector_init中会将初始化的属性attach到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)
......
properties的获取
//用户态
drmModeObjectGetProperties
//通过connector_id以及DM_MODE_OBJECT_CONNECTOR获取其所有属性
drmIoctl(fd, DRM_IOCTL_MODE_OBJ_GETPROPERTIES, &properties)
//内核态
DRM_IOCTL_DEF(DRM_IOCTL_MODE_OBJ_GETPROPERTIES, drm_mode_obj_get_properties_ioctl,0)
//根据obj_id(connector_id),和obj_type(DRM_MODE_OBJECT_CONNECTOR)获取connector的drm_mode_object实例obj
obj = drm_mode_object_find(dev, file_priv, arg->obj_id, arg->obj_type)
//遍历connector->base.properties并拷贝到用户态,
//这里仅拷贝了properties的id和value,并没有拷贝名称
drm_mode_object_get_properties(obj, file_priv->atomic,
arg->props_ptr, arg->prop_values_ptr, ...);
//上述获取的是connector的所有属性id、value,如果要获取特定名称的属性,还需要如下操作:
//用户态
drmModeGetProperty
//根据prop_id获取属性实例对象
drmIoctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)
//内核态
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getProperty_ioctl,0)
//根据prop_id获取属性实例并拷贝到用户态
properties的设置方法有三种:
1) 所有属性的设置都可以通过drmModeObjSetProperty直接通过prop_id查找到prop实例并设置
drmModeObjectSetProperty
DRM_IOCTL(fd, DRM_IOCTL_MODE_OBJ_SETPROPERTY, &prop)
2) connector的属性可以通过drmModeConnectorSetProperty设置, 这种方法会先通过connector_id找到drm_connector实例,然后遍历connector.base.properties,通过匹配prop_id的方式找到对应的prop实例
drmModeConnectorSetProperty
DRM_IOCTL(fd, DRM_IOCTL_MODE_SETPROPERTY, &osp)
3) Atomic操作,drmModeAtomicAddProperty,这种方法后续单独分析。当然上述两种方法,在内核态也是通过原子操作修改属性的
(4)connector->cmdline_mode
用于保存cmdline设置的显示模式,初始化流程如下, cmdline如何写参考文档Documentation/fb/modedb.txt:
drm_connector_init
drm_connector_get_cmdline_mode(connector)
cmdline_mode主要用于创建fbdev的framebuffer,这块逻辑后续梳理,可以参考博客:
drm 驱动是如何创建 fb device 的_增阅笔记-CSDN博客_drm fb
(5)connector->possible_encoders
标识其连接的encoders,在drm设备的connector和encoder初始化结束后,调用drm_connector_attach_encoder绑定。
通过该参数可以找到connector连接的encoder,通过宏
drm_connector_for_each_possible_encoder
(6)connector->probed_modes
通过DDC或者BIOS等获取的探测出来的drm_display_mode类型实例mode,通过drm_mode_probed_add,接口添加到connector->probed_modes链表,其常用的调用有三种如下:
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有效的drm_display_mode类型实例mode链表,该链表的更新,目前仅找到一处,如下:
//connector->funcs->fill_modes在drm_mode_getconnector中调用
//.fill_modes = drm_helper_probe_single_connector_modes
//从probed_modes链表中的modes 添加到connector->modes链表中
drm_connector_list_update
connector->modes链表中的mode实例会在drm_mode_getconnector中拷贝到用户态,用户态根据获取的modes的宽高信息创建fb
//用户态
drmModeGetConnector()
conn.connector_id = connector_id;
drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn)
//内核态
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)
//根据用户态传进来的id, 和type(DRM_MODE_OBJECT_CONNECTOR查找drm mode obj类型)
list_for_each_entry(mode, &connector->modes, head)
//用户态是drm_mode_modeinfo类型对象, 内核态是drm_display_mode类型对象
//这里需要做转换
drm_mode_convert_to_umode(&u_mode, mode);
(8)connector->func && connector->helper_private
这两个都是函数指针,配合起来helper函数实现atomic调用。
connector->func在drm_connector_init时初始化, func都是一些标准的helper函数,其内部会最终通过connector->helper_private调用drm驱动自定的接口
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_private的初始化如下.
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_private的使用逻辑在下一节分析