Commit aba58254 authored by Jani Nikula's avatar Jani Nikula
Browse files

drm/edid: add iterator for CTA data blocks



Add an iterator for CTA Data Blocks across EDID CTA Extensions and
DisplayID CTA Data Blocks.

v2: Update references, note why we can trust displayid ranges (Ville)

Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: default avatarJani Nikula <jani.nikula@intel.com>
Reviewed-by: default avatarVille Syrjälä <ville.syrjala@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/37fdd2d9eabc73aaa9f95c56246dc47aea0e8e4e.1651569697.git.jani.nikula@intel.com
parent 94afc538
Loading
Loading
Loading
Loading
+190 −12
Original line number Diff line number Diff line
@@ -4336,24 +4336,12 @@ do_hdmi_vsdb_modes(struct drm_connector *connector, const u8 *db, u8 len,
	return modes;
}

static int
cea_db_payload_len(const u8 *db)
{
	return db[0] & 0x1f;
}

static int
cea_db_extended_tag(const u8 *db)
{
	return db[1];
}

static int
cea_db_tag(const u8 *db)
{
	return db[0] >> 5;
}

static int
cea_revision(const u8 *cea)
{
@@ -4409,6 +4397,196 @@ cea_db_offsets(const u8 *cea, int *start, int *end)
	return 0;
}

/*
 * CTA Data Block iterator.
 *
 * Iterate through all CTA Data Blocks in both EDID CTA Extensions and DisplayID
 * CTA Data Blocks.
 *
 * struct cea_db *db:
 * struct cea_db_iter iter;
 *
 * cea_db_iter_edid_begin(edid, &iter);
 * cea_db_iter_for_each(db, &iter) {
 *         // do stuff with db
 * }
 * cea_db_iter_end(&iter);
 */
struct cea_db_iter {
	struct drm_edid_iter edid_iter;
	struct displayid_iter displayid_iter;

	/* Current Data Block Collection. */
	const u8 *collection;

	/* Current Data Block index in current collection. */
	int index;

	/* End index in current collection. */
	int end;
};

/* CTA-861-H section 7.4 CTA Data BLock Collection */
struct cea_db {
	u8 tag_length;
	u8 data[];
} __packed;

static int cea_db_tag(const void *_db)
{
	/* FIXME: Transition to passing struct cea_db * everywhere. */
	const struct cea_db *db = _db;

	return db->tag_length >> 5;
}

static int cea_db_payload_len(const void *_db)
{
	/* FIXME: Transition to passing struct cea_db * everywhere. */
	const struct cea_db *db = _db;

	return db->tag_length & 0x1f;
}

static const void *cea_db_data(const struct cea_db *db)
{
	return db->data;
}

static void cea_db_iter_edid_begin(const struct edid *edid, struct cea_db_iter *iter)
{
	memset(iter, 0, sizeof(*iter));

	drm_edid_iter_begin(edid, &iter->edid_iter);
	displayid_iter_edid_begin(edid, &iter->displayid_iter);
}

static const struct cea_db *
__cea_db_iter_current_block(const struct cea_db_iter *iter)
{
	const struct cea_db *db;

	if (!iter->collection)
		return NULL;

	db = (const struct cea_db *)&iter->collection[iter->index];

	if (iter->index + sizeof(*db) <= iter->end &&
	    iter->index + sizeof(*db) + cea_db_payload_len(db) <= iter->end)
		return db;

	return NULL;
}

/*
 * References:
 * - VESA E-EDID v1.4
 * - CTA-861-H section 7.3.3 CTA Extension Version 3
 */
static const void *__cea_db_iter_edid_next(struct cea_db_iter *iter)
{
	const u8 *ext;

	drm_edid_iter_for_each(ext, &iter->edid_iter) {
		/* Only support CTA Extension revision 3+ */
		if (ext[0] != CEA_EXT || cea_revision(ext) < 3)
			continue;

		iter->index = 4;
		iter->end = ext[2];
		if (iter->end == 0)
			iter->end = 127;
		if (iter->end < 4 || iter->end > 127)
			continue;

		return ext;
	}

	return NULL;
}

/*
 * References:
 * - DisplayID v1.3 Appendix C: CEA Data Block within a DisplayID Data Block
 * - DisplayID v2.0 section 4.10 CTA DisplayID Data Block
 *
 * Note that the above do not specify any connection between DisplayID Data
 * Block revision and CTA Extension versions.
 */
static const void *__cea_db_iter_displayid_next(struct cea_db_iter *iter)
{
	const struct displayid_block *block;

	displayid_iter_for_each(block, &iter->displayid_iter) {
		if (block->tag != DATA_BLOCK_CTA)
			continue;

		/*
		 * The displayid iterator has already verified the block bounds
		 * in displayid_iter_block().
		 */
		iter->index = sizeof(*block);
		iter->end = iter->index + block->num_bytes;

		return block;
	}

	return NULL;
}

static const struct cea_db *__cea_db_iter_next(struct cea_db_iter *iter)
{
	const struct cea_db *db;

	if (iter->collection) {
		/* Current collection should always be valid. */
		db = __cea_db_iter_current_block(iter);
		if (WARN_ON(!db)) {
			iter->collection = NULL;
			return NULL;
		}

		/* Next block in CTA Data Block Collection */
		iter->index += sizeof(*db) + cea_db_payload_len(db);

		db = __cea_db_iter_current_block(iter);
		if (db)
			return db;
	}

	for (;;) {
		/*
		 * Find the next CTA Data Block Collection. First iterate all
		 * the EDID CTA Extensions, then all the DisplayID CTA blocks.
		 *
		 * Per DisplayID v1.3 Appendix B: DisplayID as an EDID
		 * Extension, it's recommended that DisplayID extensions are
		 * exposed after all of the CTA Extensions.
		 */
		iter->collection = __cea_db_iter_edid_next(iter);
		if (!iter->collection)
			iter->collection = __cea_db_iter_displayid_next(iter);

		if (!iter->collection)
			return NULL;

		db = __cea_db_iter_current_block(iter);
		if (db)
			return db;
	}
}

#define cea_db_iter_for_each(__db, __iter) \
	while (((__db) = __cea_db_iter_next(__iter)))

static void cea_db_iter_end(struct cea_db_iter *iter)
{
	displayid_iter_end(&iter->displayid_iter);
	drm_edid_iter_end(&iter->edid_iter);

	memset(iter, 0, sizeof(*iter));
}

static bool cea_db_is_hdmi_vsdb(const u8 *db)
{
	if (cea_db_tag(db) != CTA_DB_VENDOR)