2629 lines
65 KiB
C
2629 lines
65 KiB
C
/*
|
|
* hdmihdcp.c: hdmi hdcp functions.
|
|
*
|
|
* Copyright (c) 2014-2021, NVIDIA CORPORATION, All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/workqueue.h>
|
|
#include <asm/atomic.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/tsec.h>
|
|
|
|
#include <soc/tegra/kfuse.h>
|
|
#include <soc/tegra/fuse.h>
|
|
#include <linux/trusty/trusty_ipc.h>
|
|
#include <linux/ote_protocol.h>
|
|
|
|
#include <uapi/video/nvhdcp.h>
|
|
|
|
#include "dc.h"
|
|
#include "dc_reg.h"
|
|
#include "dc_priv.h"
|
|
#include "edid.h"
|
|
#include "hdmi2.0.h"
|
|
#include "sor.h"
|
|
#include "sor_regs.h"
|
|
#include "hdmihdcp.h"
|
|
#include "hdmi_reg.h"
|
|
#include "host1x/host1x01_hardware.h"
|
|
#include "tsec/tsec.h"
|
|
#include "class_ids.h"
|
|
#include "tsec_drv.h"
|
|
#include "tsec/tsec_methods.h"
|
|
#include "nvhdcp_hdcp22_methods.h"
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(wq_worker);
|
|
|
|
/* for 0x40 Bcaps */
|
|
#define BCAPS_REPEATER (1 << 6)
|
|
#define BCAPS_READY (1 << 5)
|
|
#define BCAPS_11 (1 << 1) /* used for both Bcaps and Ainfo */
|
|
|
|
/* for 0x41 Bstatus */
|
|
#define BSTATUS_MAX_DEVS_EXCEEDED (1 << 7)
|
|
#define BSTATUS_MAX_CASCADE_EXCEEDED (1 << 11)
|
|
|
|
/* for HDCP2.2 */
|
|
#define HDCP_READY 1
|
|
#define HDCP_REAUTH 2
|
|
#define HDCP_REAUTH_MASK (1 << 11)
|
|
#define HDCP_MSG_SIZE_MASK 0x03FF
|
|
#define HDCP22_PROTOCOL 1
|
|
#define HDCP1X_PROTOCOL 0
|
|
#define HDCP_INFINITE_RETRIES -1
|
|
#define HDCP_DEBUG 0
|
|
#define SEQ_NUM_M_MAX_RETRIES 1
|
|
#define RX_CERT_POLL_TIME 1000
|
|
|
|
#define HDCP_FALLBACK_1X 0xdeadbeef
|
|
#define HDCP_NON_22_RX 0x0300
|
|
|
|
#define HDCP_EESS_ENABLE (0x1)
|
|
#define HDCP22_EESS_START (0x201)
|
|
#define HDCP22_EESS_END (0x211)
|
|
#define HDCP1X_EESS_START (0x200)
|
|
#define HDCP1X_EESS_END (0x210)
|
|
#define HDMI_VSYNC_WINDOW (0xc2)
|
|
|
|
#define HDCP_TA_CMD_CTRL 0
|
|
#define HDCP_TA_CMD_AKSV 1
|
|
#define HDCP_TA_CMD_ENC 2
|
|
#define HDCP_TA_CMD_REP 3
|
|
#define HDCP_TA_CMD_BKSV 4
|
|
|
|
#define PKT_SIZE 256
|
|
|
|
#define HDCP_TA_CTRL_ENABLE 1
|
|
#define HDCP_TA_CTRL_DISABLE 0
|
|
|
|
#define HDCP_CMD_OFFSET 1
|
|
#define HDCP_CMD_BYTE_OFFSET 8
|
|
#define HDCP_AUTH_CMD 0x5
|
|
#define MAX_CERT_RETRY 10
|
|
|
|
#ifdef VERBOSE_DEBUG
|
|
#define nvhdcp_vdbg(...) \
|
|
pr_debug("nvhdcp: " __VA_ARGS__)
|
|
#else
|
|
#define nvhdcp_vdbg(...) \
|
|
({ \
|
|
if (0) \
|
|
pr_debug("nvhdcp: " __VA_ARGS__); \
|
|
0; \
|
|
})
|
|
#endif
|
|
#define nvhdcp_debug(...) \
|
|
pr_debug("nvhdcp: " __VA_ARGS__)
|
|
#define nvhdcp_err(...) \
|
|
pr_err("nvhdcp: Error: " __VA_ARGS__)
|
|
#define nvhdcp_info(...) \
|
|
pr_info("nvhdcp: " __VA_ARGS__)
|
|
#define HDCP_PORT_NAME "com.nvidia.tos.13f616f9-8572-4a6f-a1f104aa9b05f9ff"
|
|
#define HDCP_SERVICE_UUID {0x13F616F9, 0x4A6F8572,\
|
|
0xAA04F1A1, 0xFFF9059B}
|
|
static u8 g_seq_num_m_retries;
|
|
static u8 g_fallback;
|
|
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
static uint32_t hdcp_uuid[4] = HDCP_SERVICE_UUID;
|
|
static uint32_t session_id;
|
|
#endif
|
|
static DEFINE_MUTEX(kfuse_lock);
|
|
|
|
static struct tegra_dc *tegra_dc_hdmi_get_dc(struct tegra_hdmi *hdmi)
|
|
{
|
|
return hdmi ? hdmi->dc : NULL;
|
|
}
|
|
|
|
static inline bool nvhdcp_is_plugged(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
rmb();
|
|
return nvhdcp->plugged;
|
|
}
|
|
|
|
static inline bool nvhdcp_set_plugged(struct tegra_nvhdcp *nvhdcp, bool plugged)
|
|
{
|
|
nvhdcp->plugged = plugged;
|
|
wmb();
|
|
return plugged;
|
|
}
|
|
|
|
static int nvhdcp_i2c_read(struct tegra_nvhdcp *nvhdcp, u8 reg,
|
|
size_t len, void *data)
|
|
{
|
|
int status;
|
|
int retries = 10;
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0x74 >> 1, /* primary link */
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = ®,
|
|
},
|
|
{
|
|
.addr = 0x74 >> 1, /* primary link */
|
|
.flags = I2C_M_RD,
|
|
.len = len,
|
|
.buf = data,
|
|
},
|
|
};
|
|
struct tegra_dc *dc = tegra_dc_hdmi_get_dc(nvhdcp->hdmi);
|
|
|
|
if (dc->vedid)
|
|
goto skip_hdcp_i2c;
|
|
|
|
tegra_dc_ddc_enable(dc, true);
|
|
do {
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("disconnect during i2c xfer\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
tegra_dc_ddc_enable(dc, false);
|
|
return -EIO;
|
|
}
|
|
mutex_unlock(&nvhdcp->lock);
|
|
status = i2c_transfer(nvhdcp->client->adapter,
|
|
msg, ARRAY_SIZE(msg));
|
|
if ((status < 0) && (retries > 1))
|
|
msleep(250);
|
|
} while ((status < 0) && retries--);
|
|
tegra_dc_ddc_enable(dc, false);
|
|
|
|
if (status < 0) {
|
|
nvhdcp_err("i2c xfer error %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
return 0;
|
|
|
|
skip_hdcp_i2c:
|
|
nvhdcp_err("vedid active\n");
|
|
return -EIO;
|
|
}
|
|
|
|
static int nvhdcp_i2c_write(struct tegra_nvhdcp *nvhdcp, u8 reg,
|
|
size_t len, const void *data)
|
|
{
|
|
int status;
|
|
u8 buf[len + 1];
|
|
struct i2c_msg msg[] = {
|
|
{
|
|
.addr = 0x74 >> 1, /* primary link */
|
|
.flags = 0,
|
|
.len = len + 1,
|
|
.buf = buf,
|
|
},
|
|
};
|
|
int retries = 15;
|
|
struct tegra_dc *dc = tegra_dc_hdmi_get_dc(nvhdcp->hdmi);
|
|
|
|
if (dc->vedid)
|
|
goto skip_hdcp_i2c;
|
|
|
|
buf[0] = reg;
|
|
memcpy(buf + 1, data, len);
|
|
|
|
tegra_dc_ddc_enable(dc, true);
|
|
do {
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("disconnect during i2c xfer\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
tegra_dc_ddc_enable(dc, false);
|
|
return -EIO;
|
|
}
|
|
mutex_unlock(&nvhdcp->lock);
|
|
status = i2c_transfer(nvhdcp->client->adapter,
|
|
msg, ARRAY_SIZE(msg));
|
|
if ((status < 0) && (retries > 1))
|
|
msleep(250);
|
|
} while ((status < 0) && retries--);
|
|
tegra_dc_ddc_enable(dc, false);
|
|
|
|
if (status < 0) {
|
|
nvhdcp_err("i2c xfer error %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
return 0;
|
|
|
|
skip_hdcp_i2c:
|
|
nvhdcp_err("vedid active\n");
|
|
return -EIO;
|
|
}
|
|
|
|
static inline int nvhdcp_i2c_read8(struct tegra_nvhdcp *nvhdcp, u8 reg, u8 *val)
|
|
{
|
|
return nvhdcp_i2c_read(nvhdcp, reg, 1, val);
|
|
}
|
|
|
|
static inline int nvhdcp_i2c_write8(struct tegra_nvhdcp *nvhdcp, u8 reg, u8 val)
|
|
{
|
|
return nvhdcp_i2c_write(nvhdcp, reg, 1, &val);
|
|
}
|
|
|
|
static inline int nvhdcp_i2c_read16(struct tegra_nvhdcp *nvhdcp,
|
|
u8 reg, u16 *val)
|
|
{
|
|
u8 buf[2];
|
|
int e;
|
|
|
|
e = nvhdcp_i2c_read(nvhdcp, reg, sizeof(buf), buf);
|
|
if (e)
|
|
return e;
|
|
|
|
if (val)
|
|
*val = buf[0] | (u16)buf[1] << 8;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvhdcp_i2c_read40(struct tegra_nvhdcp *nvhdcp, u8 reg, u64 *val)
|
|
{
|
|
u8 buf[5];
|
|
int e, i;
|
|
u64 n;
|
|
|
|
e = nvhdcp_i2c_read(nvhdcp, reg, sizeof(buf), buf);
|
|
if (e)
|
|
return e;
|
|
|
|
for (i = 0, n = 0; i < 5; i++) {
|
|
n <<= 8;
|
|
n |= buf[4 - i];
|
|
}
|
|
|
|
if (val)
|
|
*val = n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvhdcp_i2c_write40(struct tegra_nvhdcp *nvhdcp, u8 reg, u64 val)
|
|
{
|
|
char buf[5];
|
|
int i;
|
|
for (i = 0; i < 5; i++) {
|
|
buf[i] = val;
|
|
val >>= 8;
|
|
}
|
|
return nvhdcp_i2c_write(nvhdcp, reg, sizeof(buf), buf);
|
|
}
|
|
|
|
static int nvhdcp_i2c_write64(struct tegra_nvhdcp *nvhdcp, u8 reg, u64 val)
|
|
{
|
|
char buf[8];
|
|
int i;
|
|
for (i = 0; i < 8; i++) {
|
|
buf[i] = val;
|
|
val >>= 8;
|
|
}
|
|
return nvhdcp_i2c_write(nvhdcp, reg, sizeof(buf), buf);
|
|
}
|
|
|
|
/* 64-bit link encryption session random number */
|
|
static inline u64 __maybe_unused get_an(struct tegra_hdmi *hdmi)
|
|
{
|
|
u64 r;
|
|
r = (u64)tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_AN_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_AN_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 64-bit upstream exchange random number */
|
|
static inline void __maybe_unused set_cn(struct tegra_hdmi *hdmi,
|
|
u64 c_n)
|
|
{
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CN_LSB, (u32)c_n);
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CN_MSB, c_n >> 32);
|
|
}
|
|
|
|
|
|
/* 40-bit transmitter's key selection vector */
|
|
static inline u64 __maybe_unused get_aksv(struct tegra_hdmi *hdmi)
|
|
{
|
|
u64 r;
|
|
r = (u64)tegra_sor_readl_ext(hdmi->sor,
|
|
NV_SOR_TMDS_HDCP_AKSV_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_AKSV_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 40-bit receiver's key selection vector */
|
|
static inline void __maybe_unused set_bksv(struct tegra_hdmi *hdmi,
|
|
u64 b_ksv, bool repeater)
|
|
{
|
|
if (repeater)
|
|
b_ksv |= (u64)REPEATER << 32;
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_BKSV_LSB, (u32)b_ksv);
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_BKSV_MSB, b_ksv >> 32);
|
|
}
|
|
|
|
|
|
/* 40-bit software's key selection vector */
|
|
static inline void set_cksv(struct tegra_hdmi *hdmi, u64 c_ksv)
|
|
{
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CKSV_LSB, (u32)c_ksv);
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CKSV_MSB, c_ksv >> 32);
|
|
}
|
|
|
|
/* 40-bit connection state */
|
|
static inline u64 get_cs(struct tegra_hdmi *hdmi)
|
|
{
|
|
u64 r;
|
|
r = (u64)tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CS_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CS_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 40-bit upstream key selection vector */
|
|
static inline u64 get_dksv(struct tegra_hdmi *hdmi)
|
|
{
|
|
u64 r;
|
|
r = (u64)tegra_sor_readl_ext(hdmi->sor,
|
|
NV_SOR_TMDS_HDCP_DKSV_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_DKSV_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 64-bit encrypted M0 value */
|
|
static inline u64 get_mprime(struct tegra_hdmi *hdmi)
|
|
{
|
|
u64 r;
|
|
r = (u64)tegra_sor_readl_ext(hdmi->sor,
|
|
NV_SOR_TMDS_HDCP_MPRIME_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_MPRIME_LSB);
|
|
return r;
|
|
}
|
|
|
|
static inline u16 get_transmitter_ri(struct tegra_hdmi *hdmi)
|
|
{
|
|
return tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_RI);
|
|
}
|
|
|
|
static inline int get_receiver_ri(struct tegra_nvhdcp *nvhdcp, u16 *r)
|
|
{
|
|
return nvhdcp_i2c_read16(nvhdcp, 0x8, r); /* long read */
|
|
}
|
|
|
|
static int get_bcaps(struct tegra_nvhdcp *nvhdcp, u8 *b_caps)
|
|
{
|
|
return nvhdcp_i2c_read8(nvhdcp, 0x40, b_caps);
|
|
}
|
|
|
|
static int get_ksvfifo(struct tegra_nvhdcp *nvhdcp,
|
|
unsigned num_bksv_list, u64 *ksv_list)
|
|
{
|
|
u8 *buf, *p;
|
|
int e;
|
|
unsigned i;
|
|
size_t buf_len = num_bksv_list * 5;
|
|
|
|
if (!ksv_list || num_bksv_list > TEGRA_NVHDCP_MAX_DEVS)
|
|
return -EINVAL;
|
|
|
|
if (num_bksv_list == 0)
|
|
return 0;
|
|
|
|
buf = kmalloc(buf_len, GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(buf))
|
|
return -ENOMEM;
|
|
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x43, buf_len, buf);
|
|
if (e) {
|
|
kfree(buf);
|
|
return e;
|
|
}
|
|
|
|
/* load 40-bit keys from repeater into array of u64 */
|
|
p = buf;
|
|
for (i = 0; i < num_bksv_list; i++) {
|
|
ksv_list[i] = p[0] | ((u64)p[1] << 8) | ((u64)p[2] << 16)
|
|
| ((u64)p[3] << 24) | ((u64)p[4] << 32);
|
|
p += 5;
|
|
}
|
|
|
|
kfree(buf);
|
|
return 0;
|
|
}
|
|
|
|
/* get V' 160-bit SHA-1 hash from repeater */
|
|
static int get_vprime(struct tegra_nvhdcp *nvhdcp, u8 *v_prime)
|
|
{
|
|
int e, i;
|
|
|
|
for (i = 0; i < 20; i += 4) {
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x20 + i, 4, v_prime + i);
|
|
if (e)
|
|
return e;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* set or clear RUN_YES */
|
|
static void hdcp_ctrl_run(struct tegra_hdmi *hdmi, bool v)
|
|
{
|
|
u32 ctrl;
|
|
|
|
if (v) {
|
|
ctrl = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CTRL);
|
|
ctrl |= HDCP_RUN_YES;
|
|
} else {
|
|
ctrl = 0;
|
|
}
|
|
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CTRL, ctrl);
|
|
}
|
|
|
|
/* wait for any bits in mask to be set in NV_SOR_TMDS_HDCP_CTRL
|
|
* sleeps up to 120mS */
|
|
static int wait_hdcp_ctrl(struct tegra_hdmi *hdmi, u32 mask, u32 *v)
|
|
{
|
|
int retries = 13;
|
|
u32 ctrl;
|
|
|
|
do {
|
|
ctrl = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CTRL);
|
|
if ((ctrl & mask)) {
|
|
if (v)
|
|
*v = ctrl;
|
|
break;
|
|
}
|
|
if (retries > 1)
|
|
usleep_range(10, 15);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
nvhdcp_err("ctrl read timeout (mask=0x%x)\n", mask);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* wait for bits in mask to be set to value in NV_SOR_KEY_CTRL
|
|
* waits up to 100mS */
|
|
static int wait_key_ctrl(struct tegra_hdmi *hdmi, u32 mask, u32 value)
|
|
{
|
|
int retries = 101;
|
|
u32 ctrl;
|
|
|
|
do {
|
|
usleep_range(1, 2);
|
|
ctrl = tegra_sor_readl_ext(hdmi->sor, NV_SOR_KEY_CTRL);
|
|
if (((ctrl ^ value) & mask) == 0)
|
|
break;
|
|
} while (--retries);
|
|
if (!retries) {
|
|
nvhdcp_err("key ctrl read timeout (mask=0x%x)\n", mask);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* check that key selection vector is well formed.
|
|
* NOTE: this function assumes KSV has already been checked against
|
|
* revocation list.
|
|
*/
|
|
static int verify_ksv(u64 k)
|
|
{
|
|
unsigned i;
|
|
|
|
/* count set bits, must be exactly 20 set to be valid */
|
|
for (i = 0; k; i++)
|
|
k ^= k & -k;
|
|
|
|
return (i != 20) ? -EINVAL : 0;
|
|
}
|
|
|
|
static int get_nvhdcp_state(struct tegra_nvhdcp *nvhdcp,
|
|
struct tegra_nvhdcp_packet *pkt)
|
|
{
|
|
int i;
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (nvhdcp->state != STATE_LINK_VERIFY) {
|
|
memset(pkt, 0, sizeof(*pkt));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED;
|
|
} else {
|
|
pkt->num_bksv_list = nvhdcp->num_bksv_list;
|
|
for (i = 0; i < pkt->num_bksv_list; i++)
|
|
pkt->bksv_list[i] = nvhdcp->bksv_list[i];
|
|
pkt->b_status = nvhdcp->b_status;
|
|
pkt->b_ksv = nvhdcp->b_ksv;
|
|
memcpy(pkt->v_prime, nvhdcp->v_prime, sizeof(nvhdcp->v_prime));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
|
|
pkt->hdcp22 = nvhdcp->hdcp22;
|
|
pkt->port = TEGRA_NVHDCP_PORT_HDMI;
|
|
}
|
|
pkt->sor = nvhdcp->hdmi->sor->ctrl_num;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return 0;
|
|
}
|
|
|
|
/* get Status and Kprime signature - READ_S on TMDS0_LINK0 only */
|
|
static int get_s_prime(struct tegra_nvhdcp *nvhdcp,
|
|
struct tegra_nvhdcp_packet *pkt)
|
|
{
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
u32 sp_msb, sp_lsb1, sp_lsb2;
|
|
int e;
|
|
|
|
/* if connection isn't authenticated ... */
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (nvhdcp->state != STATE_LINK_VERIFY) {
|
|
memset(pkt, 0, sizeof(*pkt));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED;
|
|
e = 0;
|
|
goto err;
|
|
}
|
|
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
|
|
|
|
/* we will be taking c_n, c_ksv as input */
|
|
if (!(pkt->value_flags & TEGRA_NVHDCP_FLAG_CN)
|
|
|| !(pkt->value_flags & TEGRA_NVHDCP_FLAG_CKSV)) {
|
|
nvhdcp_err("missing value_flags (0x%x)\n", pkt->value_flags);
|
|
e = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
pkt->value_flags = 0;
|
|
|
|
pkt->a_ksv = nvhdcp->a_ksv;
|
|
pkt->a_n = nvhdcp->a_n;
|
|
pkt->value_flags = TEGRA_NVHDCP_FLAG_AKSV | TEGRA_NVHDCP_FLAG_AN;
|
|
|
|
nvhdcp_vdbg("%s():cn %llx cksv %llx\n", __func__, pkt->c_n, pkt->c_ksv);
|
|
|
|
set_cn(hdmi, pkt->c_n);
|
|
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CMODE,
|
|
TMDS0_LINK0 | READ_S);
|
|
|
|
set_cksv(hdmi, pkt->c_ksv);
|
|
|
|
e = wait_hdcp_ctrl(hdmi, SPRIME_VALID, NULL);
|
|
if (e) {
|
|
nvhdcp_err("Sprime read timeout\n");
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
msleep(50);
|
|
|
|
/* read 56-bit Sprime plus 16 status bits */
|
|
sp_msb = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_SPRIME_MSB);
|
|
sp_lsb1 = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_SPRIME_LSB1);
|
|
sp_lsb2 = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_SPRIME_LSB2);
|
|
|
|
/* top 8 bits of LSB2 and bottom 8 bits of MSB hold status bits. */
|
|
pkt->hdcp_status = (sp_msb << 8) | (sp_lsb2 >> 24);
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_S;
|
|
|
|
/* 56-bit Kprime */
|
|
pkt->k_prime = ((u64)(sp_lsb2 & 0xffffff) << 32) | sp_lsb1;
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_KP;
|
|
|
|
/* is connection state supported? */
|
|
if (sp_msb & STATUS_CS) {
|
|
pkt->cs = get_cs(hdmi);
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_CS;
|
|
}
|
|
|
|
/* load Dksv */
|
|
pkt->d_ksv = get_dksv(hdmi);
|
|
if (verify_ksv(pkt->d_ksv)) {
|
|
nvhdcp_err("Dksv invalid!\n");
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
|
|
e = -EIO; /* treat bad Dksv as I/O error */
|
|
}
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV;
|
|
|
|
/* copy current Bksv */
|
|
pkt->b_ksv = nvhdcp->b_ksv;
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSV;
|
|
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return 0;
|
|
|
|
err:
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return e;
|
|
}
|
|
|
|
/* get M prime - READ_M on TMDS0_LINK0 only */
|
|
static inline int get_m_prime(struct tegra_nvhdcp *nvhdcp,
|
|
struct tegra_nvhdcp_packet *pkt)
|
|
{
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
int e;
|
|
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_UNSUCCESSFUL;
|
|
|
|
/* if connection isn't authenticated ... */
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (nvhdcp->state != STATE_LINK_VERIFY) {
|
|
memset(pkt, 0, sizeof(*pkt));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED;
|
|
e = 0;
|
|
goto err;
|
|
}
|
|
|
|
pkt->a_ksv = nvhdcp->a_ksv;
|
|
pkt->a_n = nvhdcp->a_n;
|
|
pkt->value_flags = TEGRA_NVHDCP_FLAG_AKSV | TEGRA_NVHDCP_FLAG_AN;
|
|
|
|
set_cn(hdmi, pkt->c_n);
|
|
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CMODE,
|
|
TMDS0_LINK0 | READ_M);
|
|
|
|
/* Cksv write triggers Mprime update */
|
|
set_cksv(hdmi, pkt->c_ksv);
|
|
|
|
e = wait_hdcp_ctrl(hdmi, MPRIME_VALID, NULL);
|
|
if (e) {
|
|
nvhdcp_err("Mprime read timeout\n");
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
msleep(50);
|
|
|
|
/* load Mprime */
|
|
pkt->m_prime = get_mprime(hdmi);
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_MP;
|
|
|
|
pkt->b_status = nvhdcp->b_status;
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_BSTATUS;
|
|
|
|
/* copy most recent KSVFIFO, if it is non-zero */
|
|
pkt->num_bksv_list = nvhdcp->num_bksv_list;
|
|
if (nvhdcp->num_bksv_list) {
|
|
BUILD_BUG_ON(sizeof(pkt->bksv_list) !=
|
|
sizeof(nvhdcp->bksv_list));
|
|
memcpy(pkt->bksv_list, nvhdcp->bksv_list,
|
|
nvhdcp->num_bksv_list * sizeof(*pkt->bksv_list));
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSVLIST;
|
|
}
|
|
|
|
/* copy v_prime */
|
|
BUILD_BUG_ON(sizeof(pkt->v_prime) != sizeof(nvhdcp->v_prime));
|
|
memcpy(pkt->v_prime, nvhdcp->v_prime, sizeof(nvhdcp->v_prime));
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_V;
|
|
|
|
/* load Dksv */
|
|
pkt->d_ksv = get_dksv(hdmi);
|
|
if (verify_ksv(pkt->d_ksv)) {
|
|
nvhdcp_err("Dksv invalid!\n");
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_DKSV;
|
|
|
|
/* copy current Bksv */
|
|
pkt->b_ksv = nvhdcp->b_ksv;
|
|
pkt->value_flags |= TEGRA_NVHDCP_FLAG_BKSV;
|
|
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return 0;
|
|
|
|
err:
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return e;
|
|
}
|
|
|
|
static int load_kfuse(struct tegra_hdmi *hdmi)
|
|
{
|
|
unsigned buf[KFUSE_DATA_SZ / 4];
|
|
int e, i;
|
|
u32 ctrl;
|
|
u32 tmp;
|
|
int retries;
|
|
|
|
mutex_lock(&kfuse_lock);
|
|
/* copy load kfuse into buffer - only needed for early Tegra parts */
|
|
e = tegra_kfuse_read(buf, sizeof(buf));
|
|
if (e) {
|
|
nvhdcp_err("Kfuse read failure\n");
|
|
goto err;
|
|
}
|
|
|
|
/* write the kfuse to HDMI SRAM */
|
|
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_KEY_CTRL, 1); /* LOAD_KEYS */
|
|
|
|
/* issue a reload */
|
|
ctrl = tegra_sor_readl_ext(hdmi->sor, NV_SOR_KEY_CTRL);
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_KEY_CTRL,
|
|
ctrl | PKEY_RELOAD_TRIGGER | LOCAL_KEYS);
|
|
|
|
e = wait_key_ctrl(hdmi, PKEY_LOADED, PKEY_LOADED);
|
|
if (e) {
|
|
nvhdcp_err("key reload timeout\n");
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_KEY_SKEY_INDEX, 0);
|
|
|
|
/* wait for SRAM to be cleared */
|
|
retries = 6;
|
|
do {
|
|
tmp = tegra_sor_readl_ext(hdmi->sor, NV_SOR_KEY_DEBUG0);
|
|
if ((tmp & 1) == 0)
|
|
break;
|
|
if (retries > 1)
|
|
mdelay(1);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
nvhdcp_err("key SRAM clear timeout\n");
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
|
|
for (i = 0; i < KFUSE_DATA_SZ / 4; i += 4) {
|
|
|
|
/* load 128-bits*/
|
|
tegra_sor_writel_ext(hdmi->sor,
|
|
NV_SOR_KEY_HDCP_KEY_0, buf[i]);
|
|
tegra_sor_writel_ext(hdmi->sor,
|
|
NV_SOR_KEY_HDCP_KEY_1, buf[i+1]);
|
|
tegra_sor_writel_ext(hdmi->sor,
|
|
NV_SOR_KEY_HDCP_KEY_2, buf[i+2]);
|
|
tegra_sor_writel_ext(hdmi->sor,
|
|
NV_SOR_KEY_HDCP_KEY_3, buf[i+3]);
|
|
|
|
/* trigger LOAD_HDCP_KEY */
|
|
tegra_sor_writel_ext(hdmi->sor,
|
|
NV_SOR_KEY_HDCP_KEY_TRIG, 0x100);
|
|
|
|
tmp = LOCAL_KEYS | WRITE16;
|
|
if (i)
|
|
tmp |= AUTOINC;
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_KEY_CTRL, tmp);
|
|
|
|
/* wait for WRITE16 to complete */
|
|
e = wait_key_ctrl(hdmi, 0x10, 0); /* WRITE16 */
|
|
if (e) {
|
|
nvhdcp_err("key write timeout\n");
|
|
e = -EIO;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&kfuse_lock);
|
|
return 0;
|
|
|
|
err:
|
|
mutex_unlock(&kfuse_lock);
|
|
return e;
|
|
}
|
|
|
|
/* validate srm signature for hdcp 2.2 */
|
|
static int get_srm_signature(struct hdcp_context_t *hdcp_context,
|
|
char *nonce, uint64_t *pkt, void *ta_ctx, uint8_t version)
|
|
{
|
|
int err = 0;
|
|
/* generate nonce in the ucode */
|
|
err = tsec_hdcp_generate_nonce(hdcp_context, nonce);
|
|
if (err) {
|
|
nvhdcp_err("Error generating nonce!\n");
|
|
return err;
|
|
}
|
|
|
|
/* pass the nonce to hdcp TA and get the signature back */
|
|
memcpy(pkt, nonce, HDCP_NONCE_SIZE);
|
|
*(pkt + HDCP_NONCE_SIZE) = version;
|
|
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
if (te_is_secos_dev_enabled())
|
|
err = te_launch_trusted_oper_tlk(pkt, PKT_SIZE, session_id,
|
|
hdcp_uuid, HDCP_CMD_GEN_CMAC, sizeof(hdcp_uuid));
|
|
else
|
|
err = te_launch_trusted_oper(pkt, PKT_SIZE,
|
|
HDCP_CMD_GEN_CMAC, ta_ctx);
|
|
#else
|
|
err = te_launch_trusted_oper(pkt, PKT_SIZE, HDCP_CMD_GEN_CMAC, ta_ctx);
|
|
#endif
|
|
|
|
if (err)
|
|
nvhdcp_err("te launch operation failed with error %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
static int verify_link(struct tegra_nvhdcp *nvhdcp, bool wait_ri)
|
|
{
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
int retries = 3;
|
|
u16 old, rx, tx;
|
|
int e;
|
|
|
|
old = 0;
|
|
rx = 0;
|
|
tx = 0;
|
|
/* retry 3 times to deal with I2C link issues */
|
|
do {
|
|
if (wait_ri)
|
|
old = get_transmitter_ri(hdmi);
|
|
|
|
e = get_receiver_ri(nvhdcp, &rx);
|
|
if (!e) {
|
|
if (!rx) {
|
|
nvhdcp_err("Ri is 0!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tx = get_transmitter_ri(hdmi);
|
|
} else {
|
|
rx = ~tx;
|
|
msleep(50);
|
|
}
|
|
|
|
} while (wait_ri && --retries && old != tx);
|
|
|
|
nvhdcp_debug("R0 Ri poll:rx=0x%04x tx=0x%04x\n", rx, tx);
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("aborting verify links - lost hdmi connection\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return -EIO;
|
|
}
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
if (rx != tx)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verify_vprime(struct tegra_nvhdcp *nvhdcp, u8 repeater)
|
|
{
|
|
int i;
|
|
u8 *p;
|
|
u8 buf[RCVR_ID_LIST_SIZE];
|
|
unsigned char nonce[HDCP_NONCE_SIZE];
|
|
struct hdcp_verify_vprime_param verify_vprime_param;
|
|
int e = 0;
|
|
uint64_t *pkt = NULL;
|
|
struct hdcp_context_t *hdcp_context =
|
|
kmalloc(sizeof(struct hdcp_context_t), GFP_KERNEL);
|
|
|
|
e = tsec_hdcp_context_creation(hdcp_context,
|
|
DISPLAY_TYPE_HDMI, nvhdcp->hdmi->sor->ctrl_num);
|
|
if (e) {
|
|
nvhdcp_err("hdcp context create/init failed\n");
|
|
goto exit;
|
|
}
|
|
pkt = kzalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
if (!pkt || !hdcp_context)
|
|
goto exit;
|
|
|
|
e = get_srm_signature(hdcp_context, nonce, pkt, nvhdcp->ta_ctx,
|
|
HDCP_1x);
|
|
if (e) {
|
|
nvhdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
|
|
memset(&verify_vprime_param, 0x0,
|
|
sizeof(struct hdcp_verify_vprime_param));
|
|
if (!repeater) {
|
|
nvhdcp->num_bksv_list = 1;
|
|
nvhdcp->bksv_list[0] = nvhdcp->b_ksv;
|
|
verify_vprime_param.reserved1[0] = TSEC_SRM_REVOCATION_CHECK;
|
|
}
|
|
p = buf;
|
|
for (i = 0; i < nvhdcp->num_bksv_list; i++) {
|
|
p[0] = (u8)(nvhdcp->bksv_list[i] & 0xff);
|
|
p[1] = (u8)((nvhdcp->bksv_list[i]>>8) & 0xff);
|
|
p[2] = (u8)((nvhdcp->bksv_list[i]>>16) & 0xff);
|
|
p[3] = (u8)((nvhdcp->bksv_list[i]>>24) & 0xff);
|
|
p[4] = (u8)((nvhdcp->bksv_list[i]>>32) & 0xff);
|
|
p += 5;
|
|
}
|
|
memcpy((void *)verify_vprime_param.vprime, nvhdcp->v_prime,
|
|
HDCP_SIZE_VPRIME_1X_8);
|
|
verify_vprime_param.bstatus = nvhdcp->b_status;
|
|
verify_vprime_param.port = TEGRA_NVHDCP_PORT_HDMI; /* hdcp 1.x */
|
|
e = tsec_hdcp1x_verify_vprime(verify_vprime_param, hdcp_context,
|
|
buf, nvhdcp->num_bksv_list, pkt);
|
|
exit:
|
|
tsec_hdcp_free_context(hdcp_context);
|
|
kfree(pkt);
|
|
return e;
|
|
}
|
|
|
|
static void reset_repeater_info(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
nvhdcp_vdbg("reset repeater info\n");
|
|
|
|
memset(nvhdcp->v_prime, 0, sizeof(nvhdcp->v_prime));
|
|
nvhdcp->b_status = 0;
|
|
nvhdcp->num_bksv_list = 0;
|
|
memset(nvhdcp->bksv_list, 0, sizeof(nvhdcp->bksv_list));
|
|
}
|
|
|
|
static int get_repeater_info(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
int e, retries;
|
|
u8 b_caps;
|
|
u16 b_status;
|
|
|
|
nvhdcp_vdbg("repeater found:fetching repeater info\n");
|
|
|
|
/* wait up to 5 seconds for READY on repeater */
|
|
retries = 51;
|
|
do {
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("disconnect while waiting for repeater\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return -EIO;
|
|
}
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
e = get_bcaps(nvhdcp, &b_caps);
|
|
if (!e && (b_caps & BCAPS_READY)) {
|
|
nvhdcp_debug("Bcaps READY from repeater\n");
|
|
break;
|
|
}
|
|
if (retries > 1)
|
|
msleep(100);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
nvhdcp_err("repeater Bcaps read timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
memset(nvhdcp->v_prime, 0, sizeof(nvhdcp->v_prime));
|
|
e = get_vprime(nvhdcp, nvhdcp->v_prime);
|
|
if (e) {
|
|
nvhdcp_err("repeater Vprime read failure!\n");
|
|
return e;
|
|
}
|
|
|
|
e = nvhdcp_i2c_read16(nvhdcp, 0x41, &b_status);
|
|
if (e) {
|
|
nvhdcp_err("Bstatus read failure!\n");
|
|
return e;
|
|
}
|
|
|
|
if (b_status & BSTATUS_MAX_DEVS_EXCEEDED) {
|
|
nvhdcp_err("repeater:max devices (0x%04x)\n", b_status);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (b_status & BSTATUS_MAX_CASCADE_EXCEEDED) {
|
|
nvhdcp_err("repeater:max cascade (0x%04x)\n", b_status);
|
|
return -EINVAL;
|
|
}
|
|
|
|
nvhdcp->b_status = b_status;
|
|
nvhdcp->num_bksv_list = b_status & 0x7f;
|
|
nvhdcp_vdbg("Bstatus 0x%x (devices: %d)\n",
|
|
b_status, nvhdcp->num_bksv_list);
|
|
|
|
memset(nvhdcp->bksv_list, 0, sizeof(nvhdcp->bksv_list));
|
|
e = get_ksvfifo(nvhdcp, nvhdcp->num_bksv_list, nvhdcp->bksv_list);
|
|
if (e) {
|
|
nvhdcp_err("repeater:could not read KSVFIFO (err %d)\n", e);
|
|
return e;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nvhdcp_ake_init_send(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_AKE_INIT, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_ake_cert_receive(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_AKE_SEND_CERT, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_ake_no_stored_km_send(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_AKE_NO_STORED_KM, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_ake_hprime_receive(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_AKE_SEND_HPRIME, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_ake_pairing_info_receive(struct tegra_nvhdcp *nvhdcp,
|
|
u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_AKE_SEND_PAIRING_INFO, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_lc_init_send(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_LC_INIT, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_lc_lprime_receive(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_LC_SEND_LPRIME, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_ske_eks_send(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_SKE_SEND_EKS, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_receiverid_list_receive(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_SEND_RCVR_ID_LIST, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_rptr_ack_send(struct tegra_nvhdcp *nvhdcp, u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_SEND_RPTR_ACK, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_rptr_stream_manage_send(struct tegra_nvhdcp *nvhdcp,
|
|
u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_write(nvhdcp, 0x60, SIZE_SEND_RPTR_STREAM_MANAGE, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_rptr_stream_ready_receive(struct tegra_nvhdcp *nvhdcp,
|
|
u8 *buf)
|
|
{
|
|
int e;
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x80, SIZE_SEND_RPTR_STREAM_READY, buf);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_poll(struct tegra_nvhdcp *nvhdcp, int timeout, int status)
|
|
{
|
|
int e;
|
|
u16 val;
|
|
s64 start_time;
|
|
s64 end_time;
|
|
struct timespec tm;
|
|
ktime_get_ts(&tm);
|
|
start_time = timespec_to_ns(&tm);
|
|
while (1) {
|
|
ktime_get_ts(&tm);
|
|
end_time = timespec_to_ns(&tm);
|
|
if ((end_time - start_time)/1000 >= (s64)timeout*1000)
|
|
return -ETIMEDOUT;
|
|
else {
|
|
e = nvhdcp_i2c_read(nvhdcp, 0x70, 2, &val);
|
|
if (e) {
|
|
nvhdcp_err("nvhdcp_poll_ready failed\n");
|
|
goto exit;
|
|
}
|
|
if (status == HDCP_READY) {
|
|
if (val & HDCP_MSG_SIZE_MASK)
|
|
break;
|
|
} else if (status == HDCP_REAUTH) {
|
|
if (cpu_to_be16(val) & HDCP_REAUTH_MASK)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
e = 0;
|
|
exit:
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_poll_ready(struct tegra_nvhdcp *nvhdcp, int timeout)
|
|
{
|
|
int e;
|
|
e = nvhdcp_poll(nvhdcp, timeout, HDCP_READY);
|
|
return e;
|
|
}
|
|
|
|
|
|
static int tsec_hdcp_authentication(struct tegra_nvhdcp *nvhdcp,
|
|
struct hdcp_context_t *hdcp_context)
|
|
{
|
|
int err = 0;
|
|
u8 version = 2;
|
|
u16 caps = 0;
|
|
u16 txcaps = 0x0;
|
|
uint64_t *pkt = NULL;
|
|
unsigned char nonce[HDCP_NONCE_SIZE];
|
|
int cert_retry_count;
|
|
|
|
pkt = kzalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
if (!pkt)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_readcaps(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_init(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
down_read(&nvhdcp->hdmi->sor->reset_lock);
|
|
err = tsec_hdcp_create_session(hdcp_context, DISPLAY_TYPE_HDMI,
|
|
nvhdcp->hdmi->sor->ctrl_num);
|
|
up_read(&nvhdcp->hdmi->sor->reset_lock);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_exchange_info(hdcp_context,
|
|
HDCP_EXCHANGE_INFO_GET_TMTR_INFO,
|
|
&version,
|
|
&caps);
|
|
if (err)
|
|
goto exit;
|
|
|
|
msleep(50);
|
|
|
|
hdcp_context->msg.txcaps_version = version;
|
|
hdcp_context->msg.txcaps_capmask = txcaps;
|
|
hdcp_context->msg.ake_init_msg_id = ID_AKE_INIT;
|
|
|
|
err = nvhdcp_ake_init_send(nvhdcp,
|
|
(u8 *)&hdcp_context->msg.ake_init_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
|
|
nvhdcp_vdbg("ake init sent is %x %llx %x %x\n",
|
|
hdcp_context->msg.ake_init_msg_id, hdcp_context->msg.rtx,
|
|
hdcp_context->msg.txcaps_version, hdcp_context->msg.txcaps_capmask);
|
|
|
|
/* Certain receivers show a latency between asserting the ready bit on
|
|
* rxstatus register and writing the certificate on the i2c channel. Since
|
|
* the number of such receivers is quite high in the field, we do not want
|
|
* to regress by not letting those TVs authenticate HDCP. The fallback
|
|
* option is to give some time to the TVs to be ready to write on the
|
|
* channel
|
|
*/
|
|
cert_retry_count = MAX_CERT_RETRY;
|
|
while (cert_retry_count) {
|
|
|
|
err = nvhdcp_poll_ready(nvhdcp, RX_CERT_POLL_TIME);
|
|
if (err) {
|
|
/* RX should be ready for ceritificate within the stipulated period of time */
|
|
nvhdcp_err("Rx not ready yet, bailing out!\n");
|
|
goto exit;
|
|
} else {
|
|
/* else if rx is claiming to be ready within the retry count; read the certificate */
|
|
/* clear old messages */
|
|
memset(&hdcp_context->msg, 0, sizeof(hdcp_context->msg));
|
|
err = nvhdcp_ake_cert_receive(nvhdcp,
|
|
&hdcp_context->msg.ake_send_cert_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.ake_send_cert_msg_id != ID_AKE_SEND_CERT) {
|
|
nvhdcp_err("Not ID_AKE_SEND_CERT but %d instead\n",
|
|
hdcp_context->msg.ake_send_cert_msg_id);
|
|
|
|
/* for error, print the first few bytes for debugging;
|
|
* the first few bytes will give us some clarity on whether
|
|
* the RX is sending the same buffer as what was sent by TX
|
|
* during ake_init
|
|
*/
|
|
nvhdcp_vdbg("first few bytes of cert %x %x %x %x\n",
|
|
hdcp_context->msg.cert_rx[0], hdcp_context->msg.cert_rx[1],
|
|
hdcp_context->msg.cert_rx[2], hdcp_context->msg.cert_rx[3]);
|
|
} else
|
|
/* received the cert, move ahead */
|
|
break;
|
|
}
|
|
cert_retry_count--;
|
|
/* sleep because there is nothing better to do ! :) */
|
|
msleep(1);
|
|
}
|
|
if (hdcp_context->msg.ake_send_cert_msg_id != ID_AKE_SEND_CERT) {
|
|
nvhdcp_err("Not ID_AKE_SEND_CERT but %d instead\n",
|
|
hdcp_context->msg.ake_send_cert_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_verify_cert(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_update_rrx(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_generate_ekm(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.ake_no_stored_km_msg_id = ID_AKE_NO_STORED_KM;
|
|
err = nvhdcp_ake_no_stored_km_send(nvhdcp,
|
|
&hdcp_context->msg.ake_no_stored_km_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_exchange_info(hdcp_context,
|
|
HDCP_EXCHANGE_INFO_SET_RCVR_INFO,
|
|
&hdcp_context->msg.rxcaps_version,
|
|
&hdcp_context->msg.rxcaps_capmask);
|
|
if (err)
|
|
goto exit;
|
|
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
/* differentiate between TLK and trusty */
|
|
if (te_is_secos_dev_enabled()) {
|
|
err = te_open_trusted_session_tlk(hdcp_uuid, sizeof(hdcp_uuid),
|
|
&session_id);
|
|
} else {
|
|
nvhdcp->ta_ctx = NULL;
|
|
/* Open a trusted sesion with HDCP TA */
|
|
err = te_open_trusted_session(HDCP_PORT_NAME, &nvhdcp->ta_ctx);
|
|
}
|
|
#else
|
|
nvhdcp->ta_ctx = NULL;
|
|
/* Open a trusted sesion with HDCP TA */
|
|
err = te_open_trusted_session(HDCP_PORT_NAME, &nvhdcp->ta_ctx);
|
|
#endif
|
|
|
|
if (err) {
|
|
nvhdcp_err("Error opening trusted session\n");
|
|
goto exit;
|
|
}
|
|
err = get_srm_signature(hdcp_context, nonce, pkt, nvhdcp->ta_ctx,
|
|
HDCP_22);
|
|
if (err) {
|
|
nvhdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_revocation_check(hdcp_context,
|
|
(unsigned char *)(pkt + HDCP_CMAC_OFFSET),
|
|
*((unsigned int *)(pkt + HDCP_TSEC_ADDR_OFFSET)),
|
|
TEGRA_NVHDCP_PORT_HDMI, HDCP_22);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_poll_ready(nvhdcp, 1000);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_ake_hprime_receive(nvhdcp,
|
|
&hdcp_context->msg.ake_send_hprime_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.ake_send_hprime_msg_id != ID_AKE_SEND_HPRIME) {
|
|
nvhdcp_err("Not ID_AKE_SEND_HPRIME but %d instead\n",
|
|
hdcp_context->msg.ake_send_hprime_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_verify_hprime(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_poll_ready(nvhdcp, 200);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_ake_pairing_info_receive(nvhdcp,
|
|
&hdcp_context->msg.ake_send_pairing_info_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.ake_send_pairing_info_msg_id !=
|
|
ID_AKE_SEND_PAIRING_INFO) {
|
|
nvhdcp_err("Not ID_AKE_SEND_PAIRING_INFO but %d instead\n",
|
|
hdcp_context->msg.ake_send_hprime_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_encrypt_pairing_info(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_generate_lc_init(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.lc_init_msg_id = ID_LC_INIT;
|
|
err = nvhdcp_lc_init_send(nvhdcp, &hdcp_context->msg.lc_init_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_poll_ready(nvhdcp, 20);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_lc_lprime_receive(nvhdcp,
|
|
&hdcp_context->msg.lc_send_lprime_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.lc_send_lprime_msg_id != ID_LC_SEND_LPRIME) {
|
|
nvhdcp_err("Not ID_LC_SEND_LPRIME but %d instead\n",
|
|
hdcp_context->msg.lc_send_lprime_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_verify_lprime(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_ske_init(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.ske_send_eks_msg_id = ID_SKE_SEND_EKS;
|
|
err = nvhdcp_ske_eks_send(nvhdcp,
|
|
&hdcp_context->msg.ske_send_eks_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.rxcaps_capmask & HDCP_22_REPEATER) {
|
|
nvhdcp->repeater = 1;
|
|
err = nvhdcp_poll_ready(nvhdcp, 3000);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_receiverid_list_receive(nvhdcp,
|
|
&hdcp_context->msg.send_receiverid_list_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.send_receiverid_list_msg_id !=
|
|
ID_SEND_RCVR_ID_LIST) {
|
|
nvhdcp_err("Not ID_SEND_RCVR_ID_LIST but %d instead\n",
|
|
hdcp_context->msg.send_receiverid_list_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (hdcp_context->msg.rxinfo & HDCP_NON_22_RX) {
|
|
err = HDCP_FALLBACK_1X;
|
|
g_fallback = 1;
|
|
goto exit;
|
|
}
|
|
err = get_srm_signature(hdcp_context, nonce, pkt,
|
|
nvhdcp->ta_ctx, HDCP_22);
|
|
if (err) {
|
|
nvhdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_verify_vprime(hdcp_context,
|
|
(char *)(pkt + HDCP_CMAC_OFFSET),
|
|
*((unsigned int *)(pkt + HDCP_TSEC_ADDR_OFFSET)),
|
|
TEGRA_NVHDCP_PORT_HDMI);
|
|
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.rptr_send_ack_msg_id = ID_SEND_RPTR_ACK;
|
|
err = nvhdcp_rptr_ack_send(nvhdcp,
|
|
&hdcp_context->msg.rptr_send_ack_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
stream_manage_send:
|
|
err = tsec_hdcp_rptr_stream_manage(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.rptr_auth_stream_manage_msg_id =
|
|
ID_SEND_RPTR_STREAM_MANAGE;
|
|
/* Num of streams = 1, only video, big endian */
|
|
hdcp_context->msg.k = 0x0100;
|
|
/* STREAM_ID = 0 and Type = 0 */
|
|
hdcp_context->msg.streamid_type[0] = 0x0100;
|
|
|
|
err = nvhdcp_rptr_stream_manage_send(nvhdcp,
|
|
&hdcp_context->msg.rptr_auth_stream_manage_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
err = nvhdcp_poll_ready(nvhdcp, 100);
|
|
if (err) {
|
|
/* HDCP 2.2 analyzer expects to retry atleast once */
|
|
if (g_seq_num_m_retries >= SEQ_NUM_M_MAX_RETRIES)
|
|
goto exit;
|
|
else {
|
|
g_seq_num_m_retries++;
|
|
goto stream_manage_send;
|
|
}
|
|
}
|
|
err = nvhdcp_rptr_stream_ready_receive(nvhdcp,
|
|
&hdcp_context->msg.rptr_auth_stream_ready_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.rptr_auth_stream_ready_msg_id !=
|
|
ID_SEND_RPTR_STREAM_READY) {
|
|
nvhdcp_err("Not ID_SEND_RPTR_STREAM_READY but %d\n",
|
|
hdcp_context->msg.rptr_auth_stream_ready_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_rptr_stream_ready(hdcp_context);
|
|
if (err) {
|
|
/* HDCP 2.2 analyzer expects to retry atleast once */
|
|
if (g_seq_num_m_retries >= SEQ_NUM_M_MAX_RETRIES)
|
|
goto exit;
|
|
else {
|
|
g_seq_num_m_retries++;
|
|
goto stream_manage_send;
|
|
}
|
|
}
|
|
}
|
|
|
|
nvhdcp_info("HDCP Authentication successful!\n");
|
|
|
|
exit:
|
|
if (err)
|
|
nvhdcp_err("HDCP authentication failed with err %d\n", err);
|
|
kfree(pkt);
|
|
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
if (te_is_secos_dev_enabled()) {
|
|
if (session_id) {
|
|
te_close_trusted_session_tlk(session_id, hdcp_uuid,
|
|
sizeof(hdcp_uuid));
|
|
session_id = 0;
|
|
}
|
|
} else {
|
|
if (nvhdcp->ta_ctx) {
|
|
te_close_trusted_session(nvhdcp->ta_ctx);
|
|
nvhdcp->ta_ctx = NULL;
|
|
}
|
|
}
|
|
#else
|
|
if (nvhdcp->ta_ctx) {
|
|
te_close_trusted_session(nvhdcp->ta_ctx);
|
|
nvhdcp->ta_ctx = NULL;
|
|
}
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
void tegra_nvhdcp_clear_fallback(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
g_fallback = 0;
|
|
}
|
|
|
|
static void nvhdcp_fallback_worker(struct work_struct *work)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp =
|
|
container_of(to_delayed_work(work), struct tegra_nvhdcp, fallback_work);
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
struct tegra_dc *dc = tegra_dc_hdmi_get_dc(hdmi);
|
|
int hotplug_state;
|
|
bool dc_enabled;
|
|
|
|
hotplug_state = tegra_hdmi_get_hotplug_state(hdmi);
|
|
|
|
mutex_lock(&dc->lock);
|
|
dc_enabled = dc->enabled;
|
|
mutex_unlock(&dc->lock);
|
|
|
|
if (hotplug_state == TEGRA_HPD_STATE_NORMAL) {
|
|
tegra_hdmi_set_hotplug_state(hdmi, TEGRA_HPD_STATE_FORCE_DEASSERT);
|
|
cancel_delayed_work(&nvhdcp->fallback_work);
|
|
queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
|
|
msecs_to_jiffies(1000));
|
|
} else if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT && dc_enabled) {
|
|
cancel_delayed_work(&nvhdcp->fallback_work);
|
|
queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
|
|
msecs_to_jiffies(1000));
|
|
} else if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT && !dc_enabled) {
|
|
tegra_hdmi_set_hotplug_state(hdmi, TEGRA_HPD_STATE_NORMAL);
|
|
}
|
|
}
|
|
|
|
static void nvhdcp1_downstream_worker(struct work_struct *work)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp =
|
|
container_of(to_delayed_work(work), struct tegra_nvhdcp, work);
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
struct tegra_dc *dc = tegra_dc_hdmi_get_dc(hdmi);
|
|
int e;
|
|
int alloc_err = 0;
|
|
u8 b_caps;
|
|
int hdcp_ta_ret; /* track returns from TA */
|
|
uint32_t ta_cmd = HDCP_AUTH_CMD;
|
|
bool enc = false;
|
|
u32 tmp;
|
|
u32 res;
|
|
uint64_t *pkt = NULL;
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
pkt = kzalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
if (!pkt) {
|
|
nvhdcp_err("Memory allocation failed\n");
|
|
alloc_err = -ENOMEM;
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
|
|
nvhdcp_vdbg("%s():started thread %s for sor: %x\n", __func__,
|
|
nvhdcp->name, nvhdcp->hdmi->sor->ctrl_num);
|
|
tegra_dc_io_start(dc);
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (nvhdcp->state == STATE_OFF) {
|
|
nvhdcp_err("nvhdcp failure - giving up\n");
|
|
goto err;
|
|
}
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
/* check plug state to terminate early in case flush_workqueue() */
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("worker started while unplugged!\n");
|
|
goto lost_hdmi;
|
|
}
|
|
nvhdcp_vdbg("%s():hpd=%d\n", __func__, nvhdcp->plugged);
|
|
|
|
nvhdcp->a_ksv = 0;
|
|
nvhdcp->b_ksv = 0;
|
|
nvhdcp->a_n = 0;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
e = get_bcaps(nvhdcp, &b_caps);
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (e) {
|
|
nvhdcp_err("Bcaps read failure\n");
|
|
goto failure;
|
|
}
|
|
|
|
nvhdcp_vdbg("read Bcaps = 0x%02x\n", b_caps);
|
|
|
|
nvhdcp->ta_ctx = NULL;
|
|
e = te_open_trusted_session(HDCP_PORT_NAME, &nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("Error opening trusted session\n");
|
|
goto failure;
|
|
}
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
/* if session successfully opened, launch operations
|
|
* repeater flag in Bskv must be configured before loading fuses
|
|
*/
|
|
*pkt = HDCP_TA_CMD_REP;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = 0;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = b_caps & BCAPS_REPEATER;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = false;
|
|
*(pkt + 5*HDCP_CMD_OFFSET) = nvhdcp->hdmi->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("te launch operation failed with error %d\n",
|
|
e);
|
|
goto failure;
|
|
} else {
|
|
nvhdcp_vdbg("Loading kfuse\n");
|
|
e = load_kfuse(hdmi);
|
|
if (e) {
|
|
nvhdcp_err("kfuse could not be loaded\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
usleep_range(20000, 25000);
|
|
*pkt = HDCP_TA_CMD_CTRL;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = HDCP_TA_CTRL_ENABLE;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = false;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = nvhdcp->hdmi->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("te launch operation failed with error %d\n",
|
|
e);
|
|
goto failure;
|
|
} else {
|
|
nvhdcp_vdbg("wait AN_VALID ...\n");
|
|
|
|
hdcp_ta_ret = *pkt;
|
|
nvhdcp_vdbg("An returned %x\n", e);
|
|
if (hdcp_ta_ret) {
|
|
nvhdcp_err("An key generation timeout\n");
|
|
goto failure;
|
|
}
|
|
|
|
/* check SROM return */
|
|
hdcp_ta_ret = *(pkt + HDCP_CMD_BYTE_OFFSET);
|
|
if (hdcp_ta_ret) {
|
|
nvhdcp_err("SROM error\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
msleep(25);
|
|
*pkt = HDCP_TA_CMD_AKSV;
|
|
*(pkt + 1) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2) = false;
|
|
*(pkt + 3) = nvhdcp->hdmi->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("te launch operation failed with error %d\n",
|
|
e);
|
|
goto failure;
|
|
} else {
|
|
hdcp_ta_ret = (u64)*pkt;
|
|
nvhdcp->a_ksv = (u64)*(pkt + 1*HDCP_CMD_BYTE_OFFSET);
|
|
nvhdcp->a_n = (u64)*(pkt + 2*HDCP_CMD_BYTE_OFFSET);
|
|
nvhdcp_vdbg("Aksv is 0x%016llx\n", nvhdcp->a_ksv);
|
|
nvhdcp_vdbg("An is 0x%016llx\n", nvhdcp->a_n);
|
|
/* check if verification of Aksv failed */
|
|
if (hdcp_ta_ret) {
|
|
nvhdcp_err("Aksv verify failure\n");
|
|
goto disable;
|
|
}
|
|
}
|
|
} else {
|
|
set_bksv(hdmi, 0, (b_caps & BCAPS_REPEATER));
|
|
e = load_kfuse(hdmi);
|
|
if (e) {
|
|
nvhdcp_err("kfuse could not be loaded\n");
|
|
goto failure;
|
|
}
|
|
|
|
usleep_range(20000, 25000);
|
|
hdcp_ctrl_run(hdmi, 1);
|
|
|
|
nvhdcp_vdbg("wait AN_VALID ...\n");
|
|
|
|
/* wait for hardware to generate HDCP values */
|
|
e = wait_hdcp_ctrl(hdmi, AN_VALID | SROM_ERR, &res);
|
|
if (e) {
|
|
nvhdcp_err("An key generation timeout\n");
|
|
goto failure;
|
|
}
|
|
if (res & SROM_ERR) {
|
|
nvhdcp_err("SROM error\n");
|
|
goto failure;
|
|
}
|
|
|
|
msleep(25);
|
|
|
|
nvhdcp->a_ksv = get_aksv(hdmi);
|
|
nvhdcp->a_n = get_an(hdmi);
|
|
nvhdcp_vdbg("Aksv is %016llx\n", nvhdcp->a_ksv);
|
|
nvhdcp_vdbg("An is 0x%016llx\n", nvhdcp->a_n);
|
|
|
|
if (verify_ksv(nvhdcp->a_ksv)) {
|
|
nvhdcp_err("Aksv verify failure! (0x%016llx)\n",
|
|
nvhdcp->a_ksv);
|
|
goto disable;
|
|
}
|
|
}
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
/* write Ainfo to receiver - set 1.1 only if b_caps supports it */
|
|
e = nvhdcp_i2c_write8(nvhdcp, 0x15, b_caps & BCAPS_11);
|
|
if (e) {
|
|
nvhdcp_err("Ainfo write failure\n");
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
/* write An to receiver */
|
|
e = nvhdcp_i2c_write64(nvhdcp, 0x18, nvhdcp->a_n);
|
|
if (e) {
|
|
nvhdcp_err("An write failure\n");
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
nvhdcp_vdbg("wrote An = 0x%016llx\n", nvhdcp->a_n);
|
|
|
|
/* write Aksv to receiver - triggers auth sequence */
|
|
e = nvhdcp_i2c_write40(nvhdcp, 0x10, nvhdcp->a_ksv);
|
|
if (e) {
|
|
nvhdcp_err("Aksv write failure\n");
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
nvhdcp_vdbg("wrote Aksv = 0x%010llx\n", nvhdcp->a_ksv);
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
/* bail out if unplugged in the middle of negotiation */
|
|
if (!nvhdcp_is_plugged(nvhdcp))
|
|
goto lost_hdmi;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
/* get Bksv from receiver */
|
|
e = nvhdcp_i2c_read40(nvhdcp, 0x00, &nvhdcp->b_ksv);
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (e) {
|
|
nvhdcp_err("Bksv read failure\n");
|
|
goto failure;
|
|
}
|
|
nvhdcp_vdbg("Bksv is 0x%016llx\n", nvhdcp->b_ksv);
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_BKSV;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = nvhdcp->b_ksv;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = b_caps & BCAPS_REPEATER;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = false;
|
|
*(pkt + 5*HDCP_CMD_OFFSET) = nvhdcp->hdmi->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("te launch operation failed with error %d\n",
|
|
e);
|
|
} else {
|
|
/* check if Bksv verification was successful */
|
|
hdcp_ta_ret = (int)*pkt;
|
|
if (hdcp_ta_ret) {
|
|
nvhdcp_err("Bksv verify failure\n");
|
|
goto failure;
|
|
} else {
|
|
nvhdcp_vdbg("loaded Bksv into controller\n");
|
|
/* check if R0 read was successful */
|
|
hdcp_ta_ret = (int)*pkt;
|
|
if (hdcp_ta_ret) {
|
|
nvhdcp_err("R0 read failure\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (verify_ksv(nvhdcp->b_ksv)) {
|
|
nvhdcp_err("Bksv verify failure!\n");
|
|
goto failure;
|
|
}
|
|
|
|
set_bksv(hdmi, nvhdcp->b_ksv, (b_caps & BCAPS_REPEATER));
|
|
nvhdcp_vdbg("loaded Bksv into controller\n");
|
|
e = wait_hdcp_ctrl(hdmi, R0_VALID, NULL);
|
|
if (e) {
|
|
nvhdcp_err("R0 read failure!\n");
|
|
goto failure;
|
|
}
|
|
nvhdcp_vdbg("R0 valid\n");
|
|
}
|
|
|
|
msleep(100); /* can't read R0' within 100ms of writing Aksv */
|
|
|
|
nvhdcp_vdbg("verifying links ...\n");
|
|
|
|
mutex_unlock(&nvhdcp->lock);
|
|
e = verify_link(nvhdcp, false);
|
|
if (e) {
|
|
nvhdcp_err("link verification failed err %d\n", e);
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_ENC;
|
|
*(pkt + HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = b_caps;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = nvhdcp->hdmi->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE/4, ta_cmd, nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err("te launch operation failed with error %d\n",
|
|
e);
|
|
goto failure;
|
|
}
|
|
enc = true;
|
|
} else {
|
|
tmp = tegra_sor_readl_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CTRL);
|
|
tmp |= CRYPT_ENABLED;
|
|
if (b_caps & BCAPS_11) /* HDCP 1.1 ? */
|
|
tmp |= ONEONE_ENABLED;
|
|
tegra_sor_writel_ext(hdmi->sor, NV_SOR_TMDS_HDCP_CTRL, tmp);
|
|
}
|
|
|
|
nvhdcp_vdbg("CRYPT enabled\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
/* if repeater then get repeater info */
|
|
if (b_caps & BCAPS_REPEATER) {
|
|
e = get_repeater_info(nvhdcp);
|
|
if (e) {
|
|
nvhdcp_err("get repeater info failed\n");
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
} else {
|
|
/* if not repeater reset repeater info, so it does not linger when a receiver
|
|
* is connected */
|
|
reset_repeater_info(nvhdcp);
|
|
}
|
|
|
|
/* perform vprime verification for repeater or SRM
|
|
* revocation check for receiver
|
|
*/
|
|
e = verify_vprime(nvhdcp, b_caps & BCAPS_REPEATER);
|
|
if (e) {
|
|
nvhdcp_err("get vprime params failed\n");
|
|
goto failure;
|
|
} else
|
|
nvhdcp_vdbg("vprime verification passed\n");
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp->state = STATE_LINK_VERIFY;
|
|
nvhdcp_info("link verified!\n");
|
|
|
|
while (1) {
|
|
if (!nvhdcp_is_plugged(nvhdcp))
|
|
goto lost_hdmi;
|
|
|
|
if (nvhdcp->state != STATE_LINK_VERIFY)
|
|
goto failure;
|
|
|
|
mutex_unlock(&nvhdcp->lock);
|
|
e = verify_link(nvhdcp, true);
|
|
if (e) {
|
|
nvhdcp_err("link verification failed err %d\n", e);
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
wait_event_interruptible_timeout(wq_worker,
|
|
!nvhdcp_is_plugged(nvhdcp), msecs_to_jiffies(1500));
|
|
tegra_dc_io_start(dc);
|
|
mutex_lock(&nvhdcp->lock);
|
|
}
|
|
failure:
|
|
nvhdcp->fail_count++;
|
|
if (nvhdcp->max_retries <= HDCP_INFINITE_RETRIES ||
|
|
nvhdcp->fail_count < nvhdcp->max_retries) {
|
|
nvhdcp_err("nvhdcp failure - renegotiating in 1 second\n");
|
|
if (!nvhdcp_is_plugged(nvhdcp))
|
|
goto lost_hdmi;
|
|
queue_delayed_work(nvhdcp->downstream_wq, &nvhdcp->work,
|
|
msecs_to_jiffies(1000));
|
|
} else
|
|
nvhdcp_err("nvhdcp failure - too many failures, giving up!\n");
|
|
|
|
/* Failed because of lack of memory */
|
|
if (alloc_err == -ENOMEM) {
|
|
g_fallback = 0;
|
|
/* No need to unlock the mutex, memory failure
|
|
* only happens when the mutex is not held.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
lost_hdmi:
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_CTRL;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_HDMI;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = HDCP_TA_CTRL_DISABLE;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = false;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = nvhdcp->hdmi->sor->ctrl_num;
|
|
if (enc) {
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd,
|
|
nvhdcp->ta_ctx);
|
|
if (e) {
|
|
nvhdcp_err(
|
|
"te launch operation failed with error: %d\n",
|
|
e);
|
|
goto failure;
|
|
}
|
|
}
|
|
} else {
|
|
hdcp_ctrl_run(hdmi, 0);
|
|
}
|
|
err:
|
|
mutex_unlock(&nvhdcp->lock);
|
|
kfree(pkt);
|
|
if (nvhdcp->ta_ctx) {
|
|
te_close_trusted_session(nvhdcp->ta_ctx);
|
|
nvhdcp->ta_ctx = NULL;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
return;
|
|
disable:
|
|
nvhdcp->state = STATE_OFF;
|
|
kfree(pkt);
|
|
if (nvhdcp->ta_ctx) {
|
|
te_close_trusted_session(nvhdcp->ta_ctx);
|
|
nvhdcp->ta_ctx = NULL;
|
|
}
|
|
nvhdcp_set_plugged(nvhdcp, false);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
tegra_dc_io_end(dc);
|
|
return;
|
|
}
|
|
|
|
static int link_integrity_check(struct tegra_nvhdcp *nvhdcp,
|
|
struct hdcp_context_t *hdcp_context)
|
|
{
|
|
u16 rx_status = 0;
|
|
uint64_t *pkt = NULL;
|
|
int err = 0;
|
|
unsigned char nonce[HDCP_NONCE_SIZE];
|
|
|
|
pkt = kzalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
if (!pkt)
|
|
goto exit;
|
|
|
|
nvhdcp_i2c_read16(nvhdcp, HDCP_RX_STATUS, &rx_status);
|
|
if (nvhdcp->repeater && (rx_status & HDCP_RX_STATUS_MSG_READY_YES)) {
|
|
err = nvhdcp_poll_ready(nvhdcp, 1000);
|
|
if (err) {
|
|
nvhdcp_err("Failed to get RX list\n");
|
|
goto exit;
|
|
}
|
|
err = nvhdcp_receiverid_list_receive(nvhdcp,
|
|
&hdcp_context->msg.send_receiverid_list_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
if (hdcp_context->msg.send_receiverid_list_msg_id !=
|
|
ID_SEND_RCVR_ID_LIST) {
|
|
nvhdcp_err("Not ID_SEND_RCVR_ID_LIST but %d instead\n",
|
|
hdcp_context->msg.send_receiverid_list_msg_id);
|
|
err = -EINVAL;
|
|
goto exit;
|
|
}
|
|
if (hdcp_context->msg.rxinfo & HDCP_NON_22_RX) {
|
|
g_fallback = 1;
|
|
cancel_delayed_work(&nvhdcp->fallback_work);
|
|
queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
|
|
msecs_to_jiffies(10));
|
|
goto exit;
|
|
}
|
|
#ifdef CONFIG_TRUSTED_LITTLE_KERNEL
|
|
/* differentiate between TLK and trusty */
|
|
if (te_is_secos_dev_enabled()) {
|
|
err = te_open_trusted_session_tlk(hdcp_uuid, sizeof(hdcp_uuid),
|
|
&session_id);
|
|
} else {
|
|
nvhdcp->ta_ctx = NULL;
|
|
/* Open a trusted sesion with HDCP TA */
|
|
err = te_open_trusted_session(HDCP_PORT_NAME, &nvhdcp->ta_ctx);
|
|
}
|
|
#else
|
|
nvhdcp->ta_ctx = NULL;
|
|
/* Open a trusted sesion with HDCP TA */
|
|
err = te_open_trusted_session(HDCP_PORT_NAME, &nvhdcp->ta_ctx);
|
|
#endif
|
|
if (err) {
|
|
nvhdcp_err("Error opening trusted session\n");
|
|
goto exit;
|
|
}
|
|
err = get_srm_signature(hdcp_context, nonce, pkt,
|
|
nvhdcp->ta_ctx, HDCP_22);
|
|
if (err) {
|
|
nvhdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
err = tsec_hdcp_verify_vprime(hdcp_context,
|
|
(char *)(pkt + HDCP_CMAC_OFFSET),
|
|
*((unsigned int *)(pkt + HDCP_TSEC_ADDR_OFFSET)),
|
|
TEGRA_NVHDCP_PORT_HDMI);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.rptr_send_ack_msg_id = ID_SEND_RPTR_ACK;
|
|
err = nvhdcp_rptr_ack_send(nvhdcp,
|
|
&hdcp_context->msg.rptr_send_ack_msg_id);
|
|
if (err)
|
|
goto exit;
|
|
} else
|
|
err = (rx_status & HDCP_RX_STATUS_MSG_REAUTH_REQ);
|
|
exit:
|
|
kfree(pkt);
|
|
if (nvhdcp->ta_ctx) {
|
|
te_close_trusted_session(nvhdcp->ta_ctx);
|
|
nvhdcp->ta_ctx = NULL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static void nvhdcp2_downstream_worker(struct work_struct *work)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp =
|
|
container_of(to_delayed_work(work), struct tegra_nvhdcp, work);
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
struct tegra_dc *dc = tegra_dc_hdmi_get_dc(hdmi);
|
|
int e;
|
|
int ret;
|
|
struct hdcp_context_t *hdcp_context;
|
|
|
|
hdcp_context = kmalloc(sizeof(struct hdcp_context_t), GFP_KERNEL);
|
|
if (!hdcp_context) {
|
|
e = -ENOMEM;
|
|
goto failure;
|
|
}
|
|
|
|
g_seq_num_m_retries = 0;
|
|
|
|
e = tsec_hdcp_create_context(hdcp_context);
|
|
if (e) {
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto err;
|
|
}
|
|
|
|
nvhdcp_vdbg("%s():started thread for sor:%s\n", __func__, nvhdcp->name);
|
|
tegra_dc_io_start(dc);
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
if (nvhdcp->state == STATE_OFF) {
|
|
nvhdcp_err("nvhdcp failure - giving up\n");
|
|
goto err;
|
|
}
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
/* check plug state to terminate early in case flush_workqueue() */
|
|
if (!nvhdcp_is_plugged(nvhdcp)) {
|
|
nvhdcp_err("worker started while unplugged!\n");
|
|
goto lost_hdmi;
|
|
}
|
|
nvhdcp_vdbg("%s():hpd=%d\n", __func__, nvhdcp->plugged);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
ret = tsec_hdcp_authentication(nvhdcp, hdcp_context);
|
|
if (ret == HDCP_FALLBACK_1X) {
|
|
cancel_delayed_work(&nvhdcp->fallback_work);
|
|
queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
|
|
msecs_to_jiffies(10));
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto lost_hdmi;
|
|
} else if (ret) {
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
mdelay(350);
|
|
nvhdcp_vdbg("link integrity check ...\n");
|
|
e = link_integrity_check(nvhdcp, hdcp_context);
|
|
if (e) {
|
|
nvhdcp_err("link integrity check failed err %d\n", e);
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp->state = STATE_LINK_VERIFY;
|
|
nvhdcp_info("link integrity check passed!\n");
|
|
mutex_unlock(&nvhdcp->lock);
|
|
|
|
down_read(&hdmi->sor->reset_lock);
|
|
e = tsec_hdcp_session_ctrl(hdcp_context,
|
|
HDCP_SESSION_CTRL_FLAG_ACTIVATE);
|
|
up_read(&hdmi->sor->reset_lock);
|
|
if (e) {
|
|
nvhdcp_info("tsec_hdcp_session_ctrl failed\n");
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
nvhdcp_info("HDCP 2.2 crypt enabled!\n");
|
|
|
|
|
|
mutex_lock(&nvhdcp->lock);
|
|
while (1) {
|
|
if (!nvhdcp_is_plugged(nvhdcp))
|
|
goto lost_hdmi;
|
|
|
|
if (nvhdcp->state != STATE_LINK_VERIFY)
|
|
goto failure;
|
|
|
|
mutex_unlock(&nvhdcp->lock);
|
|
e = link_integrity_check(nvhdcp, hdcp_context);
|
|
if (e) {
|
|
nvhdcp_err("link integrity check failed err %d\n", e);
|
|
mutex_lock(&nvhdcp->lock);
|
|
goto failure;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
/* HDCP 2.2 Analyzer expects to check rx status more */
|
|
/* frequently for repeaters */
|
|
if (nvhdcp->repeater)
|
|
wait_event_interruptible_timeout(wq_worker,
|
|
!nvhdcp_is_plugged(nvhdcp),
|
|
msecs_to_jiffies(300));
|
|
else
|
|
wait_event_interruptible_timeout(wq_worker,
|
|
!nvhdcp_is_plugged(nvhdcp),
|
|
msecs_to_jiffies(500));
|
|
tegra_dc_io_start(dc);
|
|
mutex_lock(&nvhdcp->lock);
|
|
|
|
}
|
|
|
|
failure:
|
|
nvhdcp->fail_count++;
|
|
if (nvhdcp->max_retries <= HDCP_INFINITE_RETRIES ||
|
|
nvhdcp->fail_count < nvhdcp->max_retries) {
|
|
nvhdcp_err("nvhdcp failure - renegotiating in 750ms\n");
|
|
if (!nvhdcp_is_plugged(nvhdcp))
|
|
goto lost_hdmi;
|
|
queue_delayed_work(nvhdcp->downstream_wq, &nvhdcp->work,
|
|
msecs_to_jiffies(750));
|
|
} else
|
|
nvhdcp_err("nvhdcp failure - too many failures, giving up!\n");
|
|
|
|
lost_hdmi:
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
err:
|
|
nvhdcp->repeater = 0;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
tegra_dc_io_end(dc);
|
|
e = tsec_hdcp_free_context(hdcp_context);
|
|
kfree(hdcp_context);
|
|
return;
|
|
}
|
|
|
|
static void nvhdcp_downstream_worker(struct work_struct *work)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp =
|
|
container_of(to_delayed_work(work), struct tegra_nvhdcp, work);
|
|
|
|
u8 hdcp2version = 0;
|
|
int e;
|
|
int val;
|
|
|
|
nvhdcp->fail_count = 0;
|
|
e = nvhdcp_i2c_read8(nvhdcp, HDCP_HDCP2_VERSION, &hdcp2version);
|
|
if (e)
|
|
nvhdcp_err("nvhdcp i2c HDCP22 version read failed\n");
|
|
/* Do not stop nauthentication if i2c version reads fail as */
|
|
/* HDCP 1.x test 1A-04 expects reading HDCP regs */
|
|
if ((hdcp2version & HDCP_HDCP2_VERSION_HDCP22_YES) && !g_fallback) {
|
|
val = HDCP_EESS_ENABLE<<31|
|
|
HDCP22_EESS_START<<16|
|
|
HDCP22_EESS_END;
|
|
tegra_sor_writel_ext(nvhdcp->hdmi->sor,
|
|
HDMI_VSYNC_WINDOW, val);
|
|
nvhdcp->hdcp22 = HDCP22_PROTOCOL;
|
|
nvhdcp2_downstream_worker(work);
|
|
} else {
|
|
val = HDCP_EESS_ENABLE<<31|
|
|
HDCP1X_EESS_START<<16|
|
|
HDCP1X_EESS_END;
|
|
tegra_sor_writel_ext(nvhdcp->hdmi->sor, HDMI_VSYNC_WINDOW,
|
|
val);
|
|
nvhdcp->hdcp22 = HDCP1X_PROTOCOL;
|
|
nvhdcp1_downstream_worker(work);
|
|
}
|
|
}
|
|
|
|
static int tegra_nvhdcp_on(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
int delay = tegra_edid_get_quirks(nvhdcp->hdmi->edid) &
|
|
TEGRA_EDID_QUIRK_DELAY_HDCP ? 5000 : 100;
|
|
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
if (nvhdcp_is_plugged(nvhdcp) &&
|
|
atomic_read(&nvhdcp->policy) !=
|
|
TEGRA_DC_HDCP_POLICY_ALWAYS_OFF &&
|
|
!(tegra_edid_get_quirks(nvhdcp->hdmi->edid) &
|
|
TEGRA_EDID_QUIRK_NO_HDCP)) {
|
|
queue_delayed_work(nvhdcp->downstream_wq, &nvhdcp->work,
|
|
msecs_to_jiffies(delay));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_nvhdcp_off(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp->state = STATE_OFF;
|
|
nvhdcp_set_plugged(nvhdcp, false);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
wake_up_interruptible(&wq_worker);
|
|
cancel_delayed_work_sync(&nvhdcp->work);
|
|
return 0;
|
|
}
|
|
|
|
void tegra_nvhdcp_set_plug(struct tegra_nvhdcp *nvhdcp, bool hpd)
|
|
{
|
|
if (tegra_platform_is_sim())
|
|
return;
|
|
|
|
if (tegra_dc_is_t19x()) {
|
|
uint32_t ft_info;
|
|
/* enable HDCP only if board has SFK */
|
|
tegra_fuse_readl(FUSE_OPT_FT_REV_0, &ft_info);
|
|
/* only fuses with revision id greater than or equal to 0x5 have SFK */
|
|
if (ft_info < FUSE_START_SFK) {
|
|
nvhdcp_err("device does not have SFK fused\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
nvhdcp_debug("hdmi hotplug detected (hpd = %d)\n", hpd);
|
|
|
|
if (hpd) {
|
|
nvhdcp_set_plugged(nvhdcp, true);
|
|
tegra_nvhdcp_on(nvhdcp);
|
|
} else {
|
|
tegra_nvhdcp_off(nvhdcp);
|
|
}
|
|
}
|
|
|
|
int tegra_nvhdcp_set_policy(struct tegra_nvhdcp *nvhdcp, int pol)
|
|
{
|
|
if (pol == TEGRA_DC_HDCP_POLICY_ALWAYS_ON) {
|
|
nvhdcp_info("using \"always on\" policy.\n");
|
|
if (atomic_xchg(&nvhdcp->policy, pol) != pol) {
|
|
/* policy changed, start working */
|
|
tegra_nvhdcp_on(nvhdcp);
|
|
}
|
|
} else if (pol == TEGRA_DC_HDCP_POLICY_ALWAYS_OFF) {
|
|
nvhdcp_info("using \"always off\" policy.\n");
|
|
if (atomic_xchg(&nvhdcp->policy, pol) != pol) {
|
|
/* policy changed, stop working */
|
|
tegra_nvhdcp_off(nvhdcp);
|
|
}
|
|
} else {
|
|
/* unsupported policy */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_nvhdcp_renegotiate(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp->state = STATE_RENEGOTIATE;
|
|
mutex_unlock(&nvhdcp->lock);
|
|
tegra_nvhdcp_on(nvhdcp);
|
|
return 0;
|
|
}
|
|
|
|
void tegra_nvhdcp_suspend(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
if (!nvhdcp)
|
|
return;
|
|
tegra_nvhdcp_off(nvhdcp);
|
|
}
|
|
|
|
void tegra_nvhdcp_resume(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
if (!nvhdcp)
|
|
return;
|
|
tegra_nvhdcp_renegotiate(nvhdcp);
|
|
}
|
|
|
|
void tegra_nvhdcp_shutdown(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
if (!nvhdcp)
|
|
return;
|
|
tegra_nvhdcp_off(nvhdcp);
|
|
}
|
|
|
|
static int tegra_nvhdcp_recv_capable(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
if (!nvhdcp)
|
|
return 0;
|
|
|
|
if (nvhdcp->state == STATE_LINK_VERIFY)
|
|
return 1;
|
|
else {
|
|
__u64 b_ksv;
|
|
/* get Bksv from receiver */
|
|
if (!nvhdcp_i2c_read40(nvhdcp, 0x00, &b_ksv))
|
|
return !verify_ksv(b_ksv);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long nvhdcp_dev_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp = filp->private_data;
|
|
struct tegra_nvhdcp_packet *pkt;
|
|
struct tegra_hdmi *hdmi = nvhdcp->hdmi;
|
|
int e = -ENOTTY;
|
|
|
|
switch (cmd) {
|
|
case TEGRAIO_NVHDCP_ON:
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp_set_plugged(nvhdcp, hdmi->enabled);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return tegra_nvhdcp_on(nvhdcp);
|
|
|
|
case TEGRAIO_NVHDCP_OFF:
|
|
return tegra_nvhdcp_off(nvhdcp);
|
|
|
|
case TEGRAIO_NVHDCP_SET_POLICY:
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp_set_plugged(nvhdcp, hdmi->enabled);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
return tegra_nvhdcp_set_policy(nvhdcp, arg);
|
|
|
|
case TEGRAIO_NVHDCP_READ_M:
|
|
pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
|
|
if (!pkt)
|
|
return -ENOMEM;
|
|
if (copy_from_user(pkt, (void __user *)arg, sizeof(*pkt))) {
|
|
e = -EFAULT;
|
|
goto kfree_pkt;
|
|
}
|
|
e = get_m_prime(nvhdcp, pkt);
|
|
if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
|
|
e = -EFAULT;
|
|
goto kfree_pkt;
|
|
}
|
|
kfree(pkt);
|
|
return e;
|
|
|
|
case TEGRAIO_NVHDCP_READ_S:
|
|
pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
|
|
if (!pkt)
|
|
return -ENOMEM;
|
|
if (copy_from_user(pkt, (void __user *)arg, sizeof(*pkt))) {
|
|
e = -EFAULT;
|
|
goto kfree_pkt;
|
|
}
|
|
e = get_s_prime(nvhdcp, pkt);
|
|
if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
|
|
e = -EFAULT;
|
|
goto kfree_pkt;
|
|
}
|
|
kfree(pkt);
|
|
return e;
|
|
|
|
case TEGRAIO_NVHDCP_RENEGOTIATE:
|
|
mutex_lock(&nvhdcp->lock);
|
|
nvhdcp_set_plugged(nvhdcp, hdmi->enabled);
|
|
mutex_unlock(&nvhdcp->lock);
|
|
e = tegra_nvhdcp_renegotiate(nvhdcp);
|
|
break;
|
|
|
|
case TEGRAIO_NVHDCP_HDCP_STATE:
|
|
pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
|
|
if (!pkt)
|
|
return -ENOMEM;
|
|
e = get_nvhdcp_state(nvhdcp, pkt);
|
|
if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
|
|
e = -EFAULT;
|
|
goto kfree_pkt;
|
|
}
|
|
kfree(pkt);
|
|
return e;
|
|
|
|
case TEGRAIO_NVHDCP_RECV_CAPABLE:
|
|
{
|
|
__u32 recv_capable = tegra_nvhdcp_recv_capable(nvhdcp);
|
|
if (copy_to_user((void __user *)arg, &recv_capable,
|
|
sizeof(recv_capable)))
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return e;
|
|
kfree_pkt:
|
|
kfree(pkt);
|
|
return e;
|
|
}
|
|
|
|
static int nvhdcp_dev_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct miscdevice *miscdev = filp->private_data;
|
|
struct tegra_nvhdcp *nvhdcp =
|
|
container_of(miscdev, struct tegra_nvhdcp, miscdev);
|
|
filp->private_data = nvhdcp;
|
|
return 0;
|
|
}
|
|
|
|
static int nvhdcp_dev_release(struct inode *inode, struct file *filp)
|
|
{
|
|
filp->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations nvhdcp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = nvhdcp_dev_ioctl,
|
|
.open = nvhdcp_dev_open,
|
|
.release = nvhdcp_dev_release,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = nvhdcp_dev_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static struct tegra_nvhdcp **nvhdcp_head;
|
|
|
|
/* we only support one AP right now, so should only call this once. */
|
|
struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_hdmi *hdmi,
|
|
int id, int bus)
|
|
{
|
|
struct tegra_nvhdcp *nvhdcp;
|
|
struct i2c_adapter *adapter;
|
|
int e;
|
|
int num_heads;
|
|
|
|
num_heads = tegra_dc_get_numof_dispheads();
|
|
|
|
if (id >= num_heads) {
|
|
nvhdcp_err("head id greater than what's available!");
|
|
return ERR_PTR(-EMFILE);
|
|
}
|
|
|
|
if (!nvhdcp_head) {
|
|
nvhdcp_head =
|
|
kzalloc(sizeof(void *) * num_heads, GFP_KERNEL);
|
|
if (!nvhdcp_head)
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
nvhdcp = nvhdcp_head[id];
|
|
/* do not allow duplicate node creation */
|
|
if (nvhdcp)
|
|
return ERR_PTR(-EMFILE);
|
|
|
|
nvhdcp = kzalloc(sizeof(*nvhdcp), GFP_KERNEL);
|
|
if (!nvhdcp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
nvhdcp->id = id;
|
|
snprintf(nvhdcp->name, sizeof(nvhdcp->name), "nvhdcp%u", id);
|
|
nvhdcp->hdmi = hdmi;
|
|
mutex_init(&nvhdcp->lock);
|
|
|
|
strlcpy(nvhdcp->info.type, nvhdcp->name, sizeof(nvhdcp->info.type));
|
|
nvhdcp->bus = bus;
|
|
nvhdcp->info.addr = 0x74 >> 1;
|
|
nvhdcp->info.platform_data = nvhdcp;
|
|
nvhdcp->fail_count = 0;
|
|
nvhdcp->max_retries = HDCP_INFINITE_RETRIES;
|
|
atomic_set(&nvhdcp->policy, hdmi->dc->pdata->default_out->hdcp_policy);
|
|
|
|
adapter = i2c_get_adapter(bus);
|
|
if (!adapter) {
|
|
nvhdcp_err("can't get adapter for bus %d\n", bus);
|
|
e = -EBUSY;
|
|
goto free_nvhdcp;
|
|
}
|
|
|
|
nvhdcp->client = i2c_new_device(adapter, &nvhdcp->info);
|
|
i2c_put_adapter(adapter);
|
|
|
|
if (!nvhdcp->client) {
|
|
nvhdcp_err("can't create new device\n");
|
|
e = -EBUSY;
|
|
goto free_nvhdcp;
|
|
}
|
|
|
|
nvhdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
nvhdcp->downstream_wq = create_singlethread_workqueue(nvhdcp->name);
|
|
nvhdcp->fallback_wq = create_singlethread_workqueue(nvhdcp->name);
|
|
|
|
INIT_DELAYED_WORK(&nvhdcp->work, nvhdcp_downstream_worker);
|
|
INIT_DELAYED_WORK(&nvhdcp->fallback_work, nvhdcp_fallback_worker);
|
|
|
|
nvhdcp->miscdev.minor = MISC_DYNAMIC_MINOR;
|
|
nvhdcp->miscdev.name = nvhdcp->name;
|
|
nvhdcp->miscdev.fops = &nvhdcp_fops;
|
|
|
|
e = misc_register(&nvhdcp->miscdev);
|
|
if (e)
|
|
goto free_workqueue;
|
|
|
|
nvhdcp_head[id] = nvhdcp;
|
|
nvhdcp_vdbg("%s(): created misc device %s\n", __func__, nvhdcp->name);
|
|
|
|
return nvhdcp;
|
|
free_workqueue:
|
|
destroy_workqueue(nvhdcp->downstream_wq);
|
|
destroy_workqueue(nvhdcp->fallback_wq);
|
|
i2c_release_client(nvhdcp->client);
|
|
free_nvhdcp:
|
|
kfree(nvhdcp);
|
|
nvhdcp_err("unable to create device.\n");
|
|
return ERR_PTR(e);
|
|
}
|
|
|
|
void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
misc_deregister(&nvhdcp->miscdev);
|
|
tegra_nvhdcp_off(nvhdcp);
|
|
destroy_workqueue(nvhdcp->downstream_wq);
|
|
destroy_workqueue(nvhdcp->fallback_wq);
|
|
i2c_unregister_device(nvhdcp->client);
|
|
nvhdcp_head[nvhdcp->id] = NULL;
|
|
kfree(nvhdcp);
|
|
}
|
|
|
|
#ifdef CONFIG_TEGRA_DEBUG_HDCP
|
|
/* show current maximum number of retries for HDCP authentication */
|
|
static int tegra_nvhdcp_max_retries_dbg_show(struct seq_file *m, void *unused)
|
|
{
|
|
struct tegra_nvhdcp *hdcp = m->private;
|
|
|
|
if (WARN_ON(!hdcp))
|
|
return -EINVAL;
|
|
|
|
seq_printf(m, "hdcp max_retries value: %d\n", hdcp->max_retries);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sw control for hdcp max retries.
|
|
* -1 is the default value which indicates infinite retries
|
|
* If a non-infinite value is desired, set max_retries to a non-negative
|
|
* integer
|
|
* 0 is the minimum number of retries.
|
|
*/
|
|
static ssize_t tegra_nvhdcp_max_retries_dbg_write(struct file *file,
|
|
const char __user *addr,
|
|
size_t len, loff_t *pos)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct tegra_nvhdcp *hdcp = m->private;
|
|
u8 new_max_retries;
|
|
int ret;
|
|
|
|
if (WARN_ON(!hdcp))
|
|
return -EINVAL;
|
|
|
|
ret = kstrtou8_from_user(addr, len, 6, &new_max_retries);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
hdcp->max_retries = new_max_retries;
|
|
|
|
return len;
|
|
}
|
|
|
|
static int tegra_nvhdcp_max_retries_dbg_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
return single_open(file, tegra_nvhdcp_max_retries_dbg_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations tegra_nvhdcp_max_retries_dbg_ops = {
|
|
.open = tegra_nvhdcp_max_retries_dbg_open,
|
|
.read = seq_read,
|
|
.write = tegra_nvhdcp_max_retries_dbg_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
void tegra_nvhdcp_debugfs_init(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
struct dentry *dir, *ret;
|
|
|
|
dir = debugfs_create_dir("tegra_nvhdcp", NULL);
|
|
if (IS_ERR_OR_NULL(dir))
|
|
return;
|
|
|
|
ret = debugfs_create_file("max_retries", 0444, dir,
|
|
nvhdcp, &tegra_nvhdcp_max_retries_dbg_ops);
|
|
if (IS_ERR_OR_NULL(ret))
|
|
goto fail;
|
|
|
|
return;
|
|
fail:
|
|
debugfs_remove_recursive(dir);
|
|
return;
|
|
}
|
|
#else
|
|
void tegra_nvhdcp_debugfs_init(struct tegra_nvhdcp *nvhdcp)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|