Linux内核获取EDID流程

drivers/gpu/drm/drm_edid.c中

/**
 * drm_get_edid - get EDID data, if available
 * @connector: connector we're probing
 * @adapter: I2C adapter to use for DDC
 *
 * Poke the given I2C channel to grab EDID data if possible.  If found,
 * attach it to the connector.
 *
 * Return: Pointer to valid EDID or NULL if we couldn't find any.
 */
struct edid *drm_get_edid(struct drm_connector *connector,
			  struct i2c_adapter *adapter)
{
	struct edid *edid;

	if (connector->force == DRM_FORCE_OFF)
		return NULL;

	if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
		return NULL;

	edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
	if (edid)
		drm_get_displayid(connector, edid);
	return edid;
}
EXPORT_SYMBOL(drm_get_edid);

drm_do_get_edit函数在同文件中实现,源码如下:

/**
 * drm_do_get_edid - get EDID data using a custom EDID block read function
 * @connector: connector we're probing
 * @get_edid_block: EDID block read function
 * @data: private data passed to the block read function
 *
 * When the I2C adapter connected to the DDC bus is hidden behind a device that
 * exposes a different interface to read EDID blocks this function can be used
 * to get EDID data using a custom block read function.
 *
 * As in the general case the DDC bus is accessible by the kernel at the I2C
 * level, drivers must make all reasonable efforts to expose it as an I2C
 * adapter and use drm_get_edid() instead of abusing this function.
 *
 * The EDID may be overridden using debugfs override_edid or firmare EDID
 * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
 * order. Having either of them bypasses actual EDID reads.
 *
 * Return: Pointer to valid EDID or NULL if we couldn't find any.
 */
struct edid *drm_do_get_edid(struct drm_connector *connector,
	int (*get_edid_block)(void *data, u8 *buf, unsigned int block,
			      size_t len),
	void *data)
{
	int i, j = 0, valid_extensions = 0;
	u8 *edid, *new;
	struct edid *override;

	override = drm_get_override_edid(connector);
	if (override)
		return override;

	if ((edid = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
		return NULL;

	/* base block fetch */
	for (i = 0; i < 4; i++) {
		if (get_edid_block(data, edid, 0, EDID_LENGTH))
			goto out;
		if (drm_edid_block_valid(edid, 0, false,
					 &connector->edid_corrupt))
			break;
		if (i == 0 && drm_edid_is_zero(edid, EDID_LENGTH)) {
			connector->null_edid_counter++;
			goto carp;
		}
	}
	if (i == 4)
		goto carp;

	/* if there's no extensions, we're done */
	valid_extensions = edid[0x7e];
	if (valid_extensions == 0)
		return (struct edid *)edid;

	new = krealloc(edid, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);
	if (!new)
		goto out;
	edid = new;

	for (j = 1; j <= edid[0x7e]; j++) {
		u8 *block = edid + j * EDID_LENGTH;

		for (i = 0; i < 4; i++) {
			if (get_edid_block(data, block, j, EDID_LENGTH))
				goto out;
			if (drm_edid_block_valid(block, j, false, NULL))
				break;
		}

		if (i == 4)
			valid_extensions--;
	}

	if (valid_extensions != edid[0x7e]) {
		u8 *base;

		connector_bad_edid(connector, edid, edid[0x7e] + 1);

		edid[EDID_LENGTH-1] += edid[0x7e] - valid_extensions;
		edid[0x7e] = valid_extensions;

		new = kmalloc_array(valid_extensions + 1, EDID_LENGTH,
				    GFP_KERNEL);
		if (!new)
			goto out;

		base = new;
		for (i = 0; i <= edid[0x7e]; i++) {
			u8 *block = edid + i * EDID_LENGTH;

			if (!drm_edid_block_valid(block, i, false, NULL))
				continue;

			memcpy(base, block, EDID_LENGTH);
			base += EDID_LENGTH;
		}

		kfree(edid);
		edid = new;
	}

	return (struct edid *)edid;

carp:
	connector_bad_edid(connector, edid, 1);
out:
	kfree(edid);
	return NULL;
}
EXPORT_SYMBOL_GPL(drm_do_get_edid);

drm_do_probe_ddc_edid函数也在同文件(drivers/gpu/drm/drm_edid.c)中实现,源码如下(此时函数形参对应的实参分别是drm_get_edid函数中的struct i2c_adapter *adapter的adapter,drm_do_get_edid函数中分配的edid,0和EDID_LENGTH(值为128)):

#define DDC_SEGMENT_ADDR 0x30
/**
 * drm_do_probe_ddc_edid() - get EDID information via I2C
 * @data: I2C device adapter
 * @buf: EDID data buffer to be filled
 * @block: 128 byte EDID block to start fetching from
 * @len: EDID data buffer length to fetch
 *
 * Try to fetch EDID information by calling I2C driver functions.
 *
 * Return: 0 on success or -1 on failure.
 */
static int
drm_do_probe_ddc_edid(void *data, u8 *buf, unsigned int block, size_t len)
{
	struct i2c_adapter *adapter = data;
	unsigned char start = block * EDID_LENGTH;
	unsigned char segment = block >> 1;
	unsigned char xfers = segment ? 3 : 2;
	int ret, retries = 5;

	/*
	 * The core I2C driver will automatically retry the transfer if the
	 * adapter reports EAGAIN. However, we find that bit-banging transfers
	 * are susceptible to errors under a heavily loaded machine and
	 * generate spurious NAKs and timeouts. Retrying the transfer
	 * of the individual block a few times seems to overcome this.
	 */
	do {
		struct i2c_msg msgs[] = {
			{
				.addr	= DDC_SEGMENT_ADDR,
				.flags	= 0,
				.len	= 1,
				.buf	= &segment,
			}, {
				.addr	= DDC_ADDR,
				.flags	= 0,
				.len	= 1,
				.buf	= &start,
			}, {
				.addr	= DDC_ADDR,
				.flags	= I2C_M_RD,
				.len	= len,
				.buf	= buf,
			}
		};

		/*
		 * Avoid sending the segment addr to not upset non-compliant
		 * DDC monitors.
		 */
		ret = i2c_transfer(adapter, &msgs[3 - xfers], xfers);

		if (ret == -ENXIO) {
			DRM_DEBUG_KMS("drm: skipping non-existent adapter %s\n",
					adapter->name);
			break;
		}
	} while (ret != xfers && --retries);

	return ret == xfers ? 0 : -1;
}

其中,DDC_ADDR在include/drm/drm_edid.h中定义:#define DDC_ADDR 0x50,代表DDC的从设备地址;DDC_SEGMENT_ADDR在本段代码开始处定义:#define DDC_SEGMENT_ADDR 0x30。

实际上这个函数就是调用i2c_transfer函数通过i2c也可以说是DDC读取edid,将返回值保存在buf即edid指向的内存中。

i2c_transfer函数就是Linux i2c框架的接口函数了,要详细了解可以看Linux i2c相关的内容,在此不深入讲解,只是把相关代码贴出来以供参考。

drivers/i2c/i2c-core-base.c中

/**
 * i2c_transfer - execute a single or combined I2C message
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 *	terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Note that there is no requirement that each message be sent to
 * the same slave address, although that is the most common model.
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;

	/* REVISIT the fault reporting model here is weak:
	 *
	 *  - When we get an error after receiving N bytes from a slave,
	 *    there is no way to report "N".
	 *
	 *  - When we get a NAK after transmitting N bytes to a slave,
	 *    there is no way to report "N" ... or to let the master
	 *    continue executing the rest of this combined message, if
	 *    that's the appropriate response.
	 *
	 *  - When for example "num" is two and we successfully complete
	 *    the first message but get an error part way through the
	 *    second, it's unclear whether that should be reported as
	 *    one (discarding status on the second message) or errno
	 *    (discarding status on the first one).
	 */

	if (adap->algo->master_xfer) {
#ifdef DEBUG
		for (ret = 0; ret < num; ret++) {
			dev_dbg(&adap->dev,
				"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",
				ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',
				msgs[ret].addr, msgs[ret].len,
				(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
		}
#endif

		if (in_atomic() || irqs_disabled()) {
			ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);
			if (!ret)
				/* I2C activity is ongoing. */
				return -EAGAIN;
		} else {
			i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
		}

		ret = __i2c_transfer(adap, msgs, num);
		i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);

		return ret;
	} else {
		dev_dbg(&adap->dev, "I2C level transfers not supported\n");
		return -EOPNOTSUPP;
	}
}
EXPORT_SYMBOL(i2c_transfer);

同文件中

/**
 * __i2c_transfer - unlocked flavor of i2c_transfer
 * @adap: Handle to I2C bus
 * @msgs: One or more messages to execute before STOP is issued to
 *	terminate the operation; each message begins with a START.
 * @num: Number of messages to be executed.
 *
 * Returns negative errno, else the number of messages executed.
 *
 * Adapter lock must be held when calling this function. No debug logging
 * takes place. adap->algo->master_xfer existence isn't checked.
 */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	unsigned long orig_jiffies;
	int ret, try;

	if (WARN_ON(!msgs || num < 1))
		return -EINVAL;

	if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
		return -EOPNOTSUPP;

	/*
	 * i2c_trace_msg_key gets enabled when tracepoint i2c_transfer gets
	 * enabled.  This is an efficient way of keeping the for-loop from
	 * being executed when not needed.
	 */
	if (static_branch_unlikely(&i2c_trace_msg_key)) {
		int i;
		for (i = 0; i < num; i++)
			if (msgs[i].flags & I2C_M_RD)
				trace_i2c_read(adap, &msgs[i], i);
			else
				trace_i2c_write(adap, &msgs[i], i);
	}

	/* Retry automatically on arbitration loss */
	orig_jiffies = jiffies;
	for (ret = 0, try = 0; try <= adap->retries; try++) {
		ret = adap->algo->master_xfer(adap, msgs, num);
		if (ret != -EAGAIN)
			break;
		if (time_after(jiffies, orig_jiffies + adap->timeout))
			break;
	}

	if (static_branch_unlikely(&i2c_trace_msg_key)) {
		int i;
		for (i = 0; i < ret; i++)
			if (msgs[i].flags & I2C_M_RD)
				trace_i2c_reply(adap, &msgs[i], i);
		trace_i2c_result(adap, num, ret);
	}

	return ret;
}
EXPORT_SYMBOL(__i2c_transfer);

__i2c_transfer函数的核心是adap->algo->master_xfer(adap, msgs, num);这显然是之前已经建立好的函数指针指向的函数,也叫钩子函数。此时的master_xfer指向了谁呢?这个还真不好说,因为不同的显卡对应的adapter也不一样,比如amdgpu就与rk3399的不相同,机制也不完全一样。应该这样说,谁调用了drm_get_edid函数,谁在drm_get_edid函数的参数中提供了struct i2c_adapter *adapter,比如:

drivers/gpu/drm/radeon/radeon_connectors.c中的

radeon_connector->edid = drm_get_edid(connector, &radeon_connector->ddc_bus->aux.ddc);

drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c中的

amdgpu_connector->edid = drm_get_edid(connector, &amdgpu_connector->ddc_bus->aux.ddc);

drivers/gpu/drm/nouveau/nouveau_connector.c中的

nv_connector->edid = drm_get_edid(connector, i2c);

等。

因此在这里就不深入探究了,只看宏观功能,就是进行了i2c通信。

翻回头来看一下struct edid结构体的定义,include/drm/drm_edid.h中,定义如下:

struct edid {
	u8 header[8];
	/* Vendor & product info */
	u8 mfg_id[2];
	u8 prod_code[2];
	u32 serial; /* FIXME: byte order */
	u8 mfg_week;
	u8 mfg_year;
	/* EDID version */
	u8 version;
	u8 revision;
	/* Display info: */
	u8 input;
	u8 width_cm;
	u8 height_cm;
	u8 gamma;
	u8 features;
	/* Color characteristics */
	u8 red_green_lo;
	u8 black_white_lo;
	u8 red_x;
	u8 red_y;
	u8 green_x;
	u8 green_y;
	u8 blue_x;
	u8 blue_y;
	u8 white_x;
	u8 white_y;
	/* Est. timings and mfg rsvd timings*/
	struct est_timings established_timings;
	/* Standard timings 1-8*/
	struct std_timing standard_timings[8];
	/* Detailing timings 1-4 */
	struct detailed_timing detailed_timings[4];
	/* Number of 128 byte ext. blocks */
	u8 extensions;
	/* Checksum */
	u8 checksum;
} __attribute__((packed));

至此,Linux通过DRM获取EDID的流程就介绍完了。当然这只是粗线条的,更细节、更深入的内容还有待于进一步研究。

参考资料:

Linux DRM那些事-HDMI接口EDID获取(https://my.oschina.net/u/4702401/blog/5049682)

你可能感兴趣的:(GPU,Linux内核,DRM,EDID,Linux内核,DRM)