Linux内核4.14版本——drm框架分析(9)——DRM_IOCTL_MODE_GETCONNECTOR(drm_mode_getconnector)
目录
1. drm_mode_getconnector分析
1.1 找到connector
1.2 计算该connector的encoder数量
1.3 把connector支持的encoder和数量返回给用户
1.4 把找到的connector的参数返回给应用
1.5 填充mode(很重要)
1.6 把找到的connector的参数返回给应用
1.7 计算mode的数量
1.8 把mode的参数和mode的数量返回给应用
1.9 得到该connector的best encoder
1.10 刷新参数
2. fill_mode --> drm_helper_probe_single_connector_modes
2.1 所有的mode标记为旧的
2.2 热插拔相关
2.3 加载modes
2.3.1 加载固件edid的mode
2.3.2 如果没有读到2.3.1描述的edid,那么加载drm框架自带的mode
2.3.3 把命令行指定的modes加入到connector->probed_modes链表中
2.4 把modes从probed_modes链表移到modes链表
2.5 一些flags的设置
2.6 一些mode的检查的valid检查
2.6.1 drm_mode_validate_pipeline
2.7 调用drm_mode_prune_invalid把mode状态不是OK的移除链表
3. 流程图调用
本文分析一下APP调用ioctl传入DRM_IOCTL_MODE_GETCONNECTOR的操作,其对应kernel为drm_mode_getconnector函数。
1. drm_mode_getconnector分析
1.1 找到connector
int drm_mode_getconnector(struct drm_device *dev, void *data,struct drm_file *file_priv)
{struct drm_mode_get_connector *out_resp = data;struct drm_connector *connector;struct drm_encoder *encoder;struct drm_display_mode *mode;int mode_count = 0;int encoders_count = 0;int ret = 0;int copied = 0;int i;struct drm_mode_modeinfo u_mode;struct drm_mode_modeinfo __user *mode_ptr;uint32_t __user *encoder_ptr;if (!drm_core_check_feature(dev, DRIVER_MODESET))return -EINVAL;memset(&u_mode, 0, sizeof(struct drm_mode_modeinfo));connector = drm_connector_lookup(dev, out_resp->connector_id); // (1)if (!connector)return -ENOENT;
1.2 计算该connector的encoder数量
for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) // (2)if (connector->encoder_ids[i] != 0)encoders_count++;
1.3 把connector支持的encoder和数量返回给用户
if ((out_resp->count_encoders >= encoders_count) && encoders_count) { // (3)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;
1.4 把找到的connector的参数返回给应用
out_resp->connector_id = connector->base.id; // (4)out_resp->connector_type = connector->connector_type;out_resp->connector_type_id = connector->connector_type_id;
1.5 填充mode(很重要)
mutex_lock(&dev->mode_config.mutex); if (out_resp->count_modes == 0) { // (5)connector->funcs->fill_modes(connector,dev->mode_config.max_width,dev->mode_config.max_height);}
1.6 把找到的connector的参数返回给应用
out_resp->mm_width = connector->display_info.width_mm; // (6)out_resp->mm_height = connector->display_info.height_mm;out_resp->subpixel = connector->display_info.subpixel_order;out_resp->connection = connector->status;
1.7 计算mode的数量
/* delayed so we get modes regardless of pre-fill_modes state */list_for_each_entry(mode, &connector->modes, head) // (7)if (drm_mode_expose_to_userspace(mode, file_priv))mode_count++;
1.8 把mode的参数和mode的数量返回给应用
/** This ioctl is called twice, once to determine how much space is* needed, and the 2nd time to fill it.*/if ((out_resp->count_modes >= mode_count) && mode_count) { // (8)copied = 0;mode_ptr = (struct drm_mode_modeinfo __user *)(unsigned long)out_resp->modes_ptr;list_for_each_entry(mode, &connector->modes, head) {if (!drm_mode_expose_to_userspace(mode, file_priv))continue;drm_mode_convert_to_umode(&u_mode, mode);if (copy_to_user(mode_ptr + copied,&u_mode, sizeof(u_mode))) {ret = -EFAULT;mutex_unlock(&dev->mode_config.mutex);goto out;}copied++;}}out_resp->count_modes = mode_count;
1.9 得到该connector的best encoder
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);encoder = drm_connector_get_encoder(connector); // (9)if (encoder)out_resp->encoder_id = encoder->base.id;elseout_resp->encoder_id = 0;
1.10 刷新参数
/* Only grab properties after probing, to make sure EDID and other* properties reflect the latest status. */ // (10)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);
2. fill_mode --> drm_helper_probe_single_connector_modes
static const struct drm_connector_funcs dw_hdmi_connector_funcs = {.fill_modes = drm_helper_probe_single_connector_modes,.detect = dw_hdmi_connector_detect,.destroy = drm_connector_cleanup,.force = dw_hdmi_connector_force,.reset = drm_atomic_helper_connector_reset,.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {.get_modes = dw_hdmi_connector_get_modes,.best_encoder = drm_atomic_helper_best_encoder,
};
下面我们来分析一下这个函数。
2.1 所有的mode标记为旧的
int drm_helper_probe_single_connector_modes(struct drm_connector *connector,uint32_t maxX, uint32_t maxY)
{......./* set all old modes to the stale state */list_for_each_entry(mode, &connector->modes, head)mode->status = MODE_STALE;
2.2 热插拔相关
old_status = connector->status;if (connector->force) {if (connector->force == DRM_FORCE_ON ||connector->force == DRM_FORCE_ON_DIGITAL)connector->status = connector_status_connected;elseconnector->status = connector_status_disconnected;if (connector->funcs->force)connector->funcs->force(connector);} else {ret = drm_helper_probe_detect(connector, &ctx, true);if (ret == -EDEADLK) {drm_modeset_backoff(&ctx);goto retry;} else if (WARN(ret < 0, "Invalid return value %i for connector detection\n", ret))ret = connector_status_unknown;connector->status = ret;}/** Normally either the driver's hpd code or the poll loop should* pick up any changes and fire the hotplug event. But if* userspace sneaks in a probe, we might miss a change. Hence* check here, and if anything changed start the hotplug code.*/if (old_status != connector->status) {DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",connector->base.id,connector->name,drm_get_connector_status_name(old_status),drm_get_connector_status_name(connector->status));/** The hotplug event code might call into the fb* helpers, and so expects that we do not hold any* locks. Fire up the poll struct instead, it will* disable itself again.*/dev->mode_config.delayed_event = true;if (dev->mode_config.poll_enabled)schedule_delayed_work(&dev->mode_config.output_poll_work,0);}/* Re-enable polling in case the global poll config changed. */if (drm_kms_helper_poll != dev->mode_config.poll_running)drm_kms_helper_poll_enable(dev);dev->mode_config.poll_running = drm_kms_helper_poll;
热插拔相关,这里不详细介绍
2.3 加载modes
2.3.1 加载固件edid的mode
if (connector->override_edid) {struct edid *edid = (struct edid *) connector->edid_blob_ptr->data;count = drm_add_edid_modes(connector, edid);drm_edid_to_eld(connector, edid);} else {struct edid *edid = drm_load_edid_firmware(connector);if (!IS_ERR_OR_NULL(edid)) {drm_mode_connector_update_edid_property(connector, edid);count = drm_add_edid_modes(connector, edid);drm_edid_to_eld(connector, edid);kfree(edid);}if (count == 0)count = (*connector_funcs->get_modes)(connector);}
connector->override_edid是测试相关的,正式开发中不会用到,这里不讨论。这里做了两件事,如下:
(1)直接加载edid固件文件,一般存储在固定的位置,会在环境变量中指定。后使用drm_add_edid_modes添加modes。
(2)如果没有指定edid固件文件,那么就从connector helper的get_modes中获取,这里一般和显示器有关,hdmi会读取外接显示器的edid,本文使用的是dw-hdmi,get_modes调用的是dw_hdmi_connector_get_modes。
static const struct drm_connector_funcs dw_hdmi_connector_funcs = {.fill_modes = drm_helper_probe_single_connector_modes,.detect = dw_hdmi_connector_detect,.destroy = drm_connector_cleanup,.force = dw_hdmi_connector_force,.reset = drm_atomic_helper_connector_reset,.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {.get_modes = dw_hdmi_connector_get_modes,.best_encoder = drm_atomic_helper_best_encoder,
};
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,connector);struct edid *edid;int ret = 0;if (!hdmi->ddc)return 0;edid = drm_get_edid(connector, hdmi->ddc);if (edid) {dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",edid->width_cm, edid->height_cm);hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);hdmi->sink_has_audio = drm_detect_monitor_audio(edid);drm_mode_connector_update_edid_property(connector, edid);cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid);ret = drm_add_edid_modes(connector, edid);/* Store the ELD */drm_edid_to_eld(connector, edid);kfree(edid);} else {dev_dbg(hdmi->dev, "failed to get edid\n");}return ret;
}
也是读取edid的相关信息,这里不做介绍了。
2.3.2 如果没有读到2.3.1描述的edid,那么加载drm框架自带的mode
if (count == 0 && connector->status == connector_status_connected)count = drm_add_modes_noedid(connector, 1024, 768);
2.3.3 把命令行指定的modes加入到connector->probed_modes链表中
count += drm_helper_probe_add_cmdline_mode(connector);if (count == 0)goto prune;
2.4 把modes从probed_modes链表移到modes链表
/*** drm_mode_connector_list_update - update the mode list for the connector* @connector: the connector to update** This moves the modes from the @connector probed_modes list* to the actual mode list. It compares the probed mode against the current* list and only adds different/new modes.** This is just a helper functions doesn't validate any modes itself and also* doesn't prune any invalid modes. Callers need to do that themselves.*/
void drm_mode_connector_list_update(struct drm_connector *connector)
{struct drm_display_mode *pmode, *pt;WARN_ON(!mutex_is_locked(&connector->dev->mode_config.mutex));list_for_each_entry_safe(pmode, pt, &connector->probed_modes, head) {struct drm_display_mode *mode;bool found_it = false;/* go through current modes checking for the new probed mode */list_for_each_entry(mode, &connector->modes, head) {if (!drm_mode_equal(pmode, mode))continue;found_it = true;/** If the old matching mode is stale (ie. left over* from a previous probe) just replace it outright.* Otherwise just merge the type bits between all* equal probed modes.** If two probed modes are considered equal, pick the* actual timings from the one that's marked as* preferred (in case the match isn't 100%). If* multiple or zero preferred modes are present, favor* the mode added to the probed_modes list first.*/if (mode->status == MODE_STALE) {drm_mode_copy(mode, pmode);} else if ((mode->type & DRM_MODE_TYPE_PREFERRED) == 0 &&(pmode->type & DRM_MODE_TYPE_PREFERRED) != 0) {pmode->type |= mode->type;drm_mode_copy(mode, pmode);} else {mode->type |= pmode->type;}list_del(&pmode->head);drm_mode_destroy(connector->dev, pmode);break;}if (!found_it) {list_move_tail(&pmode->head, &connector->modes);}}
}
主要是把重复的probe_modes链表和modes链表中重复的mode删除,此函数运行以后,probe_modes链表就为空了,为下次做准备。
2.5 一些flags的设置
if (connector->interlace_allowed)mode_flags |= DRM_MODE_FLAG_INTERLACE;if (connector->doublescan_allowed)mode_flags |= DRM_MODE_FLAG_DBLSCAN;if (connector->stereo_allowed)mode_flags |= DRM_MODE_FLAG_3D_MASK;
2.6 一些mode的检查的valid检查
list_for_each_entry(mode, &connector->modes, head) {if (mode->status == MODE_OK)mode->status = drm_mode_validate_basic(mode);if (mode->status == MODE_OK)mode->status = drm_mode_validate_size(mode, maxX, maxY);if (mode->status == MODE_OK)mode->status = drm_mode_validate_flag(mode, mode_flags);if (mode->status == MODE_OK)mode->status = drm_mode_validate_pipeline(mode,connector);if (mode->status == MODE_OK)mode->status = drm_mode_validate_ycbcr420(mode,connector);}
(1)drm_mode_validate_basic做一些简单的检查
(2)drm_mode_validate_size做一些越界的检查。
(3)drm_mode_validate_flag做一些flag的检查(interlace_allowed,doublescan_allowed,stereo_allowed)
(4)drm_mode_validate_pipeline调用connector、encoder、crtc的helper func mode_valid做一些检查。
(5)drm_mode_validate_ycbcr420检查是否支持YUV420。
2.6.1 drm_mode_validate_pipeline
static enum drm_mode_status
drm_mode_validate_pipeline(struct drm_display_mode *mode,struct drm_connector *connector)
{struct drm_device *dev = connector->dev;uint32_t *ids = connector->encoder_ids;enum drm_mode_status ret = MODE_OK;unsigned int i;/* Step 1: Validate against connector */ret = drm_connector_mode_valid(connector, mode);if (ret != MODE_OK)return ret;/* Step 2: Validate against encoders and crtcs */for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) {struct drm_encoder *encoder = drm_encoder_find(dev, ids[i]);struct drm_crtc *crtc;if (!encoder)continue;ret = drm_encoder_mode_valid(encoder, mode);ret = drm_bridge_mode_valid(encoder->bridge, mode);drm_for_each_crtc(crtc, dev) {if (!drm_encoder_crtc_ok(encoder, crtc))continue;ret = drm_crtc_mode_valid(crtc, mode);}}return ret;
}
drm_connector_mode_valid ---> connector_funcs->mode_valid(connector, mode);
drm_encoder_mode_valid ----> encoder_funcs->mode_valid(encoder, mode);
drm_bridge_mode_valid ----> bridge->funcs->mode_valid(bridge, mode);
drm_crtc_mode_valid ---> crtc_funcs->mode_valid(crtc, mode);
2.7 调用drm_mode_prune_invalid把mode状态不是OK的移除链表
void drm_mode_prune_invalid(struct drm_device *dev,struct list_head *mode_list, bool verbose)
{struct drm_display_mode *mode, *t;list_for_each_entry_safe(mode, t, mode_list, head) {if (mode->status != MODE_OK) {list_del(&mode->head);if (verbose) {drm_mode_debug_printmodeline(mode);DRM_DEBUG_KMS("Not using %s mode: %s\n",mode->name,drm_get_mode_status_name(mode->status));}drm_mode_destroy(dev, mode);}}
}
3. 流程图调用

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
