4.13.2. connector代码分析

4.13.2.1. drm_connector结构体

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;

#define DRM_CONNECTOR_MAX_ENCODER 3
    /**
     * @encoder_ids: Valid encoders for this connector. Please only use
     * drm_connector_for_each_possible_encoder() to enumerate these.
     */
    uint32_t encoder_ids[DRM_CONNECTOR_MAX_ENCODER];

    /**
     * @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;

    /** @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

  • drm_connector_init

int drm_connector_init(struct drm_device *dev,
               struct drm_connector *connector,
               const struct drm_connector_funcs *funcs,
               int connector_type)
{
    //获取drm_mode_config结构体,一个drm设备只有一个struct drm_mode_config结构体
    //里面配置了一些参数
    struct drm_mode_config *config = &dev->mode_config;
    int ret;
    //获取connector_ida,后面会分配
    struct ida *connector_ida =
        &drm_connector_enum_list[connector_type].ida;

    WARN_ON(drm_drv_uses_atomic_modeset(dev) &&
        (!funcs->atomic_destroy_state ||
         !funcs->atomic_duplicate_state));

    //生成类型为DRM_MODE_OBJECT_CONNECTOR的struct drm_mode_object结构体
    //connector->base.id会在用户态接口drmModeGetResources调用时,作为connector_id返回用户态
    //后续用户态可以通过connector_id,调用drmModeGetConnector找到drm_connector,并获取相关参数
    ret = __drm_mode_object_add(dev, &connector->base,
                    DRM_MODE_OBJECT_CONNECTOR,
                    false, drm_connector_free);
    if (ret)
        return ret;

    //得到properties
    connector->base.properties = &connector->properties;
    connector->dev = dev;
    connector->funcs = funcs;

    //生成drm_mode_config的connector_ida并作为该connector的index索引
    /* connector index is used with 32bit bitmasks */
    ret = ida_simple_get(&config->connector_ida, 0, 32, GFP_KERNEL);
    if (ret < 0) {
        DRM_DEBUG_KMS("Failed to allocate %s connector index: %d\n",
                  drm_connector_enum_list[connector_type].name,
                  ret);
        goto out_put;
    }
    connector->index = ret;
    ret = 0;

    //生成该connector的type的connector_ida
    connector->connector_type = connector_type;
    connector->connector_type_id =
        ida_simple_get(connector_ida, 1, 0, GFP_KERNEL);
    if (connector->connector_type_id < 0) {
        ret = connector->connector_type_id;
        goto out_put_id;
    }
    //初始化connector名称及链表,mutex等
    connector->name =
        kasprintf(GFP_KERNEL, "%s-%d",
              drm_connector_enum_list[connector_type].name,
              connector->connector_type_id);
    if (!connector->name) {
        ret = -ENOMEM;
        goto out_put_type_id;
    }

    INIT_LIST_HEAD(&connector->probed_modes);
    INIT_LIST_HEAD(&connector->modes);
    mutex_init(&connector->mutex);
    connector->edid_blob_ptr = NULL;
    connector->tile_blob_ptr = NULL;
    connector->status = connector_status_unknown;
    connector->display_info.panel_orientation =
        DRM_MODE_PANEL_ORIENTATION_UNKNOWN;

    drm_connector_get_cmdline_mode(connector);

    /* 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 &&
        connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
        drm_connector_attach_edid_property(connector);

    drm_object_attach_property(&connector->base,
                      config->dpms_property, 0);

    drm_object_attach_property(&connector->base,
                   config->link_status_property,
                   0);

    drm_object_attach_property(&connector->base,
                   config->non_desktop_property,
                   0);
    drm_object_attach_property(&connector->base,
                   config->tile_property,
                   0);

    if (drm_core_check_feature(dev, DRIVER_ATOMIC)) {
        drm_object_attach_property(&connector->base, config->prop_crtc_id, 0);
    }

    connector->debugfs_entry = NULL;
out_put_type_id:
    if (ret)
        ida_simple_remove(connector_ida, connector->connector_type_id);
out_put_id:
    if (ret)
        ida_simple_remove(&config->connector_ida, connector->index);
out_put:
    if (ret)
        drm_mode_object_unregister(dev, &connector->base);

    return ret;
}

4.13.2.2. 用户态操作

  • 用户态获取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
        mo = drm_mode_object_find(dev, file_priv, id, DRM_MODE_OBJECT_CONNECTOR)
        return obj_to_connector(mo)
  • base.properties

记录connector的属性(如CRTC_ID/EDID/等),这些属性首先在drm_mode_create_standard_properties创建初始化并保存在 dev->mode_config中

//以CRTC_ID属性为例
drm_mode_config_init
    drmm_mode_config_init
        drm_mode_create_standard_properties
            //创建名称为DRTC_ID的属性
            prop = drm_property_create_object(dev, DRM_MODE_PROP_ATOMIC, "CRTC_ID", DRM_MDOE_OBJECT_CRTC)
                //创建属性
                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
  • properties的获取

//用户态
drmModeObjectGetProperties
    //通过connector_id以及DRM_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
    drmIoctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop)
//内核态
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETPROPERTY, drm_mode_getproperty_ioctl, 0)

4.13.2.3. connector初始化

以下代码基于renesas rcar平台分析

//rcar-du/rcar_lvds.c

static int rcar_lvds_attach(struct drm_bridge *bridge)
{
    struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
    struct drm_connector *connector = &lvds->connector;
    struct drm_encoder *encoder = bridge->encoder;
    int ret;

    /* If we have a next bridge just attach it. */
    if (lvds->next_bridge)
        return drm_bridge_attach(bridge->encoder, lvds->next_bridge,
                     bridge);

    /* Otherwise if we have a panel, create a connector. */
    if (!lvds->panel)
        return 0;

    ret = drm_connector_init(bridge->dev, connector, &rcar_lvds_conn_funcs,
                 DRM_MODE_CONNECTOR_LVDS);
    if (ret < 0)
        return ret;

    drm_connector_helper_add(connector, &rcar_lvds_conn_helper_funcs);

    ret = drm_connector_attach_encoder(connector, encoder);
    if (ret < 0)
        return ret;

    return drm_panel_attach(lvds->panel, connector);
}


static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
    .attach = rcar_lvds_attach,
    .detach = rcar_lvds_detach,
    .enable = rcar_lvds_enable,
    .disable = rcar_lvds_disable,
    .mode_fixup = rcar_lvds_mode_fixup,
    .mode_set = rcar_lvds_mode_set,
};


static int rcar_lvds_probe(struct platform_device *pdev)
{
    const struct soc_device_attribute *attr;
    struct rcar_lvds *lvds;
    struct resource *mem;
    int ret;

    lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
    if (lvds == NULL)
        return -ENOMEM;

    platform_set_drvdata(pdev, lvds);

    lvds->dev = &pdev->dev;
    lvds->info = of_device_get_match_data(&pdev->dev);

    attr = soc_device_match(lvds_quirk_matches);
    if (attr)
        lvds->info = attr->data;

    ret = rcar_lvds_parse_dt(lvds);
    if (ret < 0)
        return ret;

    lvds->bridge.driver_private = lvds;
    lvds->bridge.funcs = &rcar_lvds_bridge_ops;
    lvds->bridge.of_node = pdev->dev.of_node;

    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
    if (IS_ERR(lvds->mmio))
        return PTR_ERR(lvds->mmio);

    ret = rcar_lvds_get_clocks(lvds);
    if (ret < 0)
        return ret;

    lvds->rstc = devm_reset_control_get(&pdev->dev, NULL);
    if (IS_ERR(lvds->rstc)) {
        dev_err(&pdev->dev, "failed to get cpg reset\n");
        return PTR_ERR(lvds->rstc);
    }

    drm_bridge_add(&lvds->bridge);

    return 0;
}

static struct platform_driver rcar_lvds_platform_driver = {
    .probe          = rcar_lvds_probe,
    .remove         = rcar_lvds_remove,
    .driver         = {
        .name       = "rcar-lvds",
        .of_match_table = rcar_lvds_of_table,
    },
};