Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 731db77a authored by Ajay Singh Parmar's avatar Ajay Singh Parmar Committed by Divya Sharma
Browse files

drm/msm/dp: fix hdcp version check at sink



Some sinks do not enable set RxCaps until a valid video
stream is received. Checking RxCaps before that may return
an invalid data. Let the video stream start before checking
RxCaps to fetch correct RxCaps from sink.

Also, check for hdcp availability at source level during
boot-up to avoid unnecessary hdcp start failures during
hot plugs when source doesn't support hdcp.

CRs-Fixed: 2298350
Change-Id: Ieb95208286a8ca0fc084551bf5117291462cede4
Signed-off-by: default avatarAjay Singh Parmar <aparmar@codeaurora.org>
parent 27068eb8
Loading
Loading
Loading
Loading
+65 −0
Original line number Diff line number Diff line
@@ -598,6 +598,56 @@ static ssize_t dp_debug_read_connected(struct file *file,
	return len;
}

static ssize_t dp_debug_write_hdcp(struct file *file,
		const char __user *user_buff, size_t count, loff_t *ppos)
{
	struct dp_debug_private *debug = file->private_data;
	char buf[SZ_8];
	size_t len = 0;
	int hdcp = 0;

	if (!debug)
		return -ENODEV;

	if (*ppos)
		return 0;

	/* Leave room for termination char */
	len = min_t(size_t, count, SZ_8 - 1);
	if (copy_from_user(buf, user_buff, len))
		goto end;

	buf[len] = '\0';

	if (kstrtoint(buf, 10, &hdcp) != 0)
		goto end;

	debug->dp_debug.hdcp_disabled = !hdcp;
end:
	return len;
}

static ssize_t dp_debug_read_hdcp(struct file *file,
		char __user *user_buff, size_t count, loff_t *ppos)
{
	struct dp_debug_private *debug = file->private_data;
	u32 len = 0;

	if (!debug)
		return -ENODEV;

	if (*ppos)
		return 0;

	len = sizeof(debug->dp_debug.hdcp_status);

	if (copy_to_user(user_buff, debug->dp_debug.hdcp_status, len))
		return -EFAULT;

	*ppos += len;
	return len;
}

static int dp_debug_check_buffer_overflow(int rc, int *max_size, int *len)
{
	if (rc >= *max_size) {
@@ -1237,6 +1287,12 @@ static const struct file_operations max_pclk_khz_fops = {
	.read = dp_debug_max_pclk_khz_read,
};

static const struct file_operations hdcp_fops = {
	.open = simple_open,
	.write = dp_debug_write_hdcp,
	.read = dp_debug_read_hdcp,
};

static int dp_debug_init(struct dp_debug *dp_debug)
{
	int rc = 0;
@@ -1412,6 +1468,15 @@ static int dp_debug_init(struct dp_debug *dp_debug)
		goto error_remove_dir;
	}

	file = debugfs_create_file("hdcp", 0644, dir,
					debug, &hdcp_fops);
	if (IS_ERR_OR_NULL(file)) {
		rc = PTR_ERR(file);
		pr_err("[%s] debugfs hdcp failed, rc=%d\n",
			DEBUG_NAME, rc);
		goto error_remove_dir;
	}

	return 0;

error_remove_dir:
+2 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ struct dp_debug {
	bool debug_en;
	bool sim_mode;
	bool psm_enabled;
	bool hdcp_disabled;
	int aspect_ratio;
	int vdisplay;
	int hdisplay;
@@ -41,6 +42,7 @@ struct dp_debug {
	bool tpg_state;
	u32 max_pclk_khz;
	bool force_encryption;
	char hdcp_status[SZ_128];

	u8 *(*get_edid)(struct dp_debug *dp_debug);
};
+124 −76
Original line number Diff line number Diff line
@@ -43,14 +43,19 @@
static struct dp_display *g_dp_display;
#define HPD_STRING_SIZE 30

struct dp_hdcp_dev {
	void *fd;
	struct sde_hdcp_ops *ops;
	enum sde_hdcp_version ver;
};

struct dp_hdcp {
	void *data;
	struct sde_hdcp_ops *ops;

	void *hdcp1;
	void *hdcp2;
	u32 source_cap;

	bool feature_enabled;
	struct dp_hdcp_dev dev[HDCP_VERSION_MAX];
};

struct dp_mst {
@@ -116,8 +121,7 @@ static bool dp_display_framework_ready(struct dp_display_private *dp)

static inline bool dp_display_is_hdcp_enabled(struct dp_display_private *dp)
{
	return dp->hdcp.feature_enabled && dp->link->hdcp_status.hdcp_version
		&& dp->hdcp.ops;
	return dp->link->hdcp_status.hdcp_version && dp->hdcp.ops;
}

static irqreturn_t dp_display_irq(int irq, void *dev_id)
@@ -162,16 +166,83 @@ static bool dp_display_is_ready(struct dp_display_private *dp)
		dp->hpd->alt_mode_cfg_done;
}

static void dp_display_update_hdcp_status(struct dp_display_private *dp,
					bool reset)
{
	if (reset) {
		dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE;
		dp->link->hdcp_status.hdcp_version = HDCP_VERSION_NONE;
	}

	memset(dp->debug->hdcp_status, 0, sizeof(dp->debug->hdcp_status));

	snprintf(dp->debug->hdcp_status, sizeof(dp->debug->hdcp_status),
		"%s: %s\ncaps: %d\n",
		sde_hdcp_version(dp->link->hdcp_status.hdcp_version),
		sde_hdcp_state_name(dp->link->hdcp_status.hdcp_state),
		dp->hdcp.source_cap);
}

static void dp_display_update_hdcp_info(struct dp_display_private *dp)
{
	void *fd = NULL;
	struct dp_hdcp_dev *dev = NULL;
	struct sde_hdcp_ops *ops = NULL;
	int i = HDCP_VERSION_2P2;

	dp_display_update_hdcp_status(dp, true);

	dp->hdcp.data = NULL;
	dp->hdcp.ops = NULL;

	if (dp->debug->hdcp_disabled || dp->debug->sim_mode)
		return;

	while (i) {
		dev = &dp->hdcp.dev[i];
		ops = dev->ops;
		fd = dev->fd;

		i >>= 1;

		if (!(dp->hdcp.source_cap & dev->ver))
			continue;

		if (ops->sink_support(fd)) {
			dp->hdcp.data = fd;
			dp->hdcp.ops = ops;
			dp->link->hdcp_status.hdcp_version = dev->ver;
			break;
		}
	}

	pr_debug("HDCP version supported: %s\n",
		sde_hdcp_version(dp->link->hdcp_status.hdcp_version));
}

static void dp_display_hdcp_cb_work(struct work_struct *work)
{
	struct dp_display_private *dp;
	struct delayed_work *dw = to_delayed_work(work);
	struct sde_hdcp_ops *ops;
	struct dp_link_hdcp_status *status;
	void *data;
	int rc = 0;
	u32 hdcp_auth_state;

	dp = container_of(dw, struct dp_display_private, hdcp_cb_work);
	status = &dp->link->hdcp_status;

	if (status->hdcp_state == HDCP_STATE_INACTIVE) {
		dp_display_update_hdcp_info(dp);

		if (dp_display_is_hdcp_enabled(dp)) {
			status->hdcp_state = HDCP_STATE_AUTHENTICATING;
		} else {
			dp_display_update_hdcp_status(dp, true);
			return;
		}
	}

	rc = dp->catalog->ctrl.read_hdcp_status(&dp->catalog->ctrl);
	if (rc >= 0) {
@@ -182,14 +253,15 @@ static void dp_display_hdcp_cb_work(struct work_struct *work)
	ops = dp->hdcp.ops;
	data = dp->hdcp.data;

	pr_debug("%s: %s\n",
		sde_hdcp_version(dp->link->hdcp_status.hdcp_version),
		sde_hdcp_state_name(dp->link->hdcp_status.hdcp_state));
	pr_debug("%s: %s\n", sde_hdcp_version(status->hdcp_version),
		sde_hdcp_state_name(status->hdcp_state));

	dp_display_update_hdcp_status(dp, false);

	if (dp->debug->force_encryption && ops && ops->force_encryption)
		ops->force_encryption(data, dp->debug->force_encryption);

	switch (dp->link->hdcp_status.hdcp_state) {
	switch (status->hdcp_state) {
	case HDCP_STATE_AUTHENTICATING:
		if (dp->hdcp.ops && dp->hdcp.ops->authenticate)
			rc = dp->hdcp.ops->authenticate(data);
@@ -226,62 +298,35 @@ static void dp_display_notify_hdcp_status_cb(void *ptr,
		queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ/4);
}

static void dp_display_update_hdcp_info(struct dp_display_private *dp)
static void dp_display_check_source_hdcp_caps(struct dp_display_private *dp)
{
	void *fd = NULL;
	struct sde_hdcp_ops *ops = NULL;
	bool hdcp2_present = false, hdcp1_present = false;
	int i;
	struct dp_hdcp_dev *hdcp_dev = dp->hdcp.dev;

	if (!dp) {
		pr_err("invalid input\n");
	if (dp->debug->hdcp_disabled) {
		pr_debug("hdcp disabled\n");
		return;
	}

	dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE;
	dp->link->hdcp_status.hdcp_version = HDCP_VERSION_NONE;
	for (i = 0; i < HDCP_VERSION_MAX; i++) {
		struct dp_hdcp_dev *dev = &hdcp_dev[i];
		struct sde_hdcp_ops *ops = dev->ops;
		void *fd = dev->fd;

	if (!dp->hdcp.feature_enabled) {
		pr_debug("feature not enabled\n");
		return;
	}
		if (!fd || !ops)
			continue;

	if (dp->debug->sim_mode) {
		pr_debug("skip HDCP version checks for simulation mode\n");
		return;
		if (ops->feature_supported(fd))
			dp->hdcp.source_cap |= dev->ver;
		else
			pr_warn("This device doesn't support %s\n",
				sde_hdcp_version(dev->ver));
	}

	fd = dp->hdcp.hdcp2;
	if (fd)
		ops = sde_dp_hdcp2p2_start(fd);

	if (ops && ops->feature_supported)
		hdcp2_present = ops->feature_supported(fd);

	if (hdcp2_present)
		dp->link->hdcp_status.hdcp_version = HDCP_VERSION_2P2;
	if (!dp->hdcp.source_cap)
		dp->debug->hdcp_disabled = true;

	if (!hdcp2_present) {
		fd = dp->hdcp.hdcp1;
		if (fd)
			ops = sde_hdcp_1x_start(fd);

		if (ops && ops->feature_supported)
			hdcp1_present = ops->feature_supported(fd);

		if (hdcp1_present)
			dp->link->hdcp_status.hdcp_version = HDCP_VERSION_1X;
	}

	if (hdcp2_present || hdcp1_present) {
		dp->hdcp.data = fd;
		dp->hdcp.ops = ops;
	} else {
		dp->hdcp.data = NULL;
		dp->hdcp.ops = NULL;
	}

	pr_debug("HDCP version supported: %s\n",
		sde_hdcp_version(dp->link->hdcp_status.hdcp_version));
	dp_display_update_hdcp_status(dp, false);
}

static void dp_display_deinitialize_hdcp(struct dp_display_private *dp)
@@ -298,6 +343,7 @@ static int dp_display_initialize_hdcp(struct dp_display_private *dp)
{
	struct sde_hdcp_init_data hdcp_init_data;
	struct dp_parser *parser;
	void *fd;
	int rc = 0;

	if (!dp) {
@@ -324,29 +370,35 @@ static int dp_display_initialize_hdcp(struct dp_display_private *dp)
	hdcp_init_data.revision      = &dp->panel->link_info.revision;
	hdcp_init_data.msm_hdcp_dev  = dp->parser->msm_hdcp_dev;

	dp->hdcp.hdcp1 = sde_hdcp_1x_init(&hdcp_init_data);
	if (IS_ERR_OR_NULL(dp->hdcp.hdcp1)) {
	fd = sde_hdcp_1x_init(&hdcp_init_data);
	if (IS_ERR_OR_NULL(fd)) {
		pr_err("Error initializing HDCP 1.x\n");
		rc = -EINVAL;
		goto error;
	}

	dp->hdcp.dev[HDCP_VERSION_1X].fd = fd;
	dp->hdcp.dev[HDCP_VERSION_1X].ops = sde_hdcp_1x_get(fd);
	dp->hdcp.dev[HDCP_VERSION_1X].ver = HDCP_VERSION_1X;
	pr_debug("HDCP 1.3 initialized\n");

	dp->hdcp.hdcp2 = sde_dp_hdcp2p2_init(&hdcp_init_data);
	if (IS_ERR_OR_NULL(dp->hdcp.hdcp2)) {
	fd = sde_dp_hdcp2p2_init(&hdcp_init_data);
	if (IS_ERR_OR_NULL(fd)) {
		pr_err("Error initializing HDCP 2.x\n");
		rc = -EINVAL;
		goto error;
	}

	dp->hdcp.dev[HDCP_VERSION_2P2].fd = fd;
	dp->hdcp.dev[HDCP_VERSION_2P2].ops = sde_dp_hdcp2p2_get(fd);
	dp->hdcp.dev[HDCP_VERSION_2P2].ver = HDCP_VERSION_2P2;
	pr_debug("HDCP 2.2 initialized\n");

	dp->hdcp.feature_enabled = true;

	return 0;
error:
	dp_display_deinitialize_hdcp(dp);
	dp->debug->hdcp_disabled = true;

	return rc;
}

@@ -475,6 +527,9 @@ static void dp_display_post_open(struct dp_display *dp_display)
		return;
	}

	dp_display_update_hdcp_status(dp, true);
	dp_display_check_source_hdcp_caps(dp);

	/* if cable is already connected, send notification */
	if (dp->hpd->hpd_high)
		queue_delayed_work(dp->wq, &dp->connect_work, HZ * 10);
@@ -720,11 +775,11 @@ static void dp_display_clean(struct dp_display_private *dp)
	struct dp_panel *dp_panel;

	if (dp_display_is_hdcp_enabled(dp)) {
		dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE;

		cancel_delayed_work_sync(&dp->hdcp_cb_work);
		if (dp->hdcp.ops->off)
			dp->hdcp.ops->off(dp->hdcp.data);

		dp_display_update_hdcp_status(dp, true);
	}

	for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) {
@@ -863,7 +918,7 @@ static void dp_display_attention_work(struct work_struct *work)
			struct dp_display_private, attention_work);

	if (dp->link->process_request(dp->link))
		goto mst_attention;
		goto cp_irq;

	if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) {
		if (dp_display_is_sink_count_zero(dp)) {
@@ -899,7 +954,7 @@ static void dp_display_attention_work(struct work_struct *work)

	if (dp->link->sink_request & DP_LINK_STATUS_UPDATED)
		dp->ctrl->link_maintenance(dp->ctrl);

cp_irq:
	if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq)
		dp->hdcp.ops->cp_irq(dp->hdcp.data);
mst_attention:
@@ -1401,15 +1456,8 @@ static int dp_display_post_enable(struct dp_display *dp_display, void *panel)
		dp_panel->audio->on(dp_panel->audio);
	}

	dp_display_update_hdcp_info(dp);

	if (dp_display_is_hdcp_enabled(dp)) {
	cancel_delayed_work_sync(&dp->hdcp_cb_work);

		dp->link->hdcp_status.hdcp_state = HDCP_STATE_AUTHENTICATING;
		queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ / 2);
	}

	queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ);
end:
	/* clear framework event notifier */
	dp_display->post_open = NULL;
@@ -1449,11 +1497,11 @@ static int dp_display_pre_disable(struct dp_display *dp_display, void *panel)
	}

	if (dp_display_is_hdcp_enabled(dp)) {
		dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE;

		cancel_delayed_work_sync(&dp->hdcp_cb_work);
		if (dp->hdcp.ops->off)
			dp->hdcp.ops->off(dp->hdcp.data);

		dp_display_update_hdcp_status(dp, true);
	}

	if (dp_panel->audio_supported)
+28 −31
Original line number Diff line number Diff line
@@ -777,6 +777,31 @@ static int dp_hdcp2p2_isr(void *input)
	return rc;
}

static bool dp_hdcp2p2_supported(void *input)
{
	struct dp_hdcp2p2_ctrl *ctrl = input;
	u32 const rxcaps_dpcd_offset = 0x6921d;
	ssize_t bytes_read = 0;
	u8 buf[DP_HDCP_RXCAPS_LENGTH];

	pr_debug("Checking sink capability\n");

	bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux,
			rxcaps_dpcd_offset, &buf, DP_HDCP_RXCAPS_LENGTH);
	if (bytes_read != DP_HDCP_RXCAPS_LENGTH) {
		pr_err("RxCaps read failed\n");
		goto error;
	}

	pr_debug("HDCP_CAPABLE=%lu\n", (buf[2] & BIT(1)) >> 1);
	pr_debug("VERSION=%d\n", buf[0]);

	if ((buf[2] & BIT(1)) && (buf[0] == 0x2))
		return true;
error:
	return false;
}

void sde_dp_hdcp2p2_deinit(void *input)
{
	struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input;
@@ -809,6 +834,7 @@ void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data)
		.authenticate = dp_hdcp2p2_authenticate,
		.feature_supported = dp_hdcp2p2_feature_supported,
		.force_encryption = dp_hdcp2p2_force_encryption,
		.sink_support = dp_hdcp2p2_supported,
		.off = dp_hdcp2p2_off,
		.cp_irq = dp_hdcp2p2_cp_irq,
	};
@@ -900,37 +926,8 @@ void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data)
	return ERR_PTR(rc);
}

static bool dp_hdcp2p2_supported(struct dp_hdcp2p2_ctrl *ctrl)
{
	u32 const rxcaps_dpcd_offset = 0x6921d;
	ssize_t bytes_read = 0;
	u8 buf[DP_HDCP_RXCAPS_LENGTH];

	bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux,
			rxcaps_dpcd_offset, &buf, DP_HDCP_RXCAPS_LENGTH);
	if (bytes_read != DP_HDCP_RXCAPS_LENGTH) {
		pr_err("RxCaps read failed\n");
		goto error;
	}

	pr_debug("HDCP_CAPABLE=%lu\n", (buf[2] & BIT(1)) >> 1);
	pr_debug("VERSION=%d\n", buf[0]);

	if ((buf[2] & BIT(1)) && (buf[0] == 0x2))
		return true;

error:
	return false;
}

struct sde_hdcp_ops *sde_dp_hdcp2p2_start(void *input)
struct sde_hdcp_ops *sde_dp_hdcp2p2_get(void *input)
{
	struct dp_hdcp2p2_ctrl *ctrl = input;

	pr_debug("Checking sink capability\n");
	if (dp_hdcp2p2_supported(ctrl))
		return ctrl->ops;
	else
		return NULL;
	return ((struct dp_hdcp2p2_ctrl *)input)->ops;
}
+6 −4
Original line number Diff line number Diff line
@@ -40,8 +40,9 @@ enum sde_hdcp_state {

enum sde_hdcp_version {
	HDCP_VERSION_NONE,
	HDCP_VERSION_1X,
	HDCP_VERSION_2P2
	HDCP_VERSION_1X = BIT(0),
	HDCP_VERSION_2P2 = BIT(1),
	HDCP_VERSION_MAX = BIT(2),
};

struct sde_hdcp_init_data {
@@ -72,6 +73,7 @@ struct sde_hdcp_ops {
	int (*authenticate)(void *hdcp_ctrl);
	bool (*feature_supported)(void *input);
	void (*force_encryption)(void *input, bool enable);
	bool (*sink_support)(void *input);
	void (*off)(void *hdcp_ctrl);
};

@@ -98,8 +100,8 @@ static inline const char *sde_hdcp_version(enum sde_hdcp_version hdcp_version)

void *sde_hdcp_1x_init(struct sde_hdcp_init_data *init_data);
void sde_hdcp_1x_deinit(void *input);
struct sde_hdcp_ops *sde_hdcp_1x_start(void *input);
struct sde_hdcp_ops *sde_hdcp_1x_get(void *input);
void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data);
void sde_dp_hdcp2p2_deinit(void *input);
struct sde_hdcp_ops *sde_dp_hdcp2p2_start(void *input);
struct sde_hdcp_ops *sde_dp_hdcp2p2_get(void *input);
#endif /* __SDE_HDCP_H__ */
Loading