2446 lines
57 KiB
C
2446 lines
57 KiB
C
/*
|
|
* dphdcp.c: dp hdcp driver.
|
|
*
|
|
* Copyright (c) 2015-2020, 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/module.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/tsec.h>
|
|
#include <soc/tegra/kfuse.h>
|
|
#include <soc/tegra/fuse.h>
|
|
|
|
#include "dc.h"
|
|
#include "dphdcp.h"
|
|
#include "dp.h"
|
|
#include "dpaux.h"
|
|
#include "edid.h"
|
|
#include "sor.h"
|
|
#include "sor_regs.h"
|
|
#include "dpaux_regs.h"
|
|
#include "tsec_drv.h"
|
|
#include "tsec/tsec_methods.h"
|
|
#include "nvhdcp_hdcp22_methods.h"
|
|
#include "tsec/tsec.h"
|
|
|
|
#if (defined(CONFIG_TRUSTY))
|
|
#include <linux/trusty/trusty_ipc.h>
|
|
#endif
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(wq_worker);
|
|
|
|
/* Bcaps register bits */
|
|
#define BCAPS_REPEATER (1 << 1)
|
|
#define BCAPS_HDCP_CAPABLE (1 << 0)
|
|
|
|
/* Bstatus register bits */
|
|
#define BSTATUS_REAUTH_REQ (1 << 3)
|
|
#define BSTATUS_LINK_INTEG_FAIL (1 << 2)
|
|
#define BSTATUS_R0_PRIME_SET (1 << 1)
|
|
#define BSTATUS_READY (1 << 0)
|
|
|
|
/* Binfo register bits */
|
|
#define BINFO_MAX_DEVS_EXCEEDED (1 << 7)
|
|
#define BINFO_MAX_CASCADE_EXCEEDED (1 << 11)
|
|
|
|
/* for hdcp 2.2 */
|
|
#define HDCP22_PROTOCOL 1
|
|
#define HDCP1X_PROTOCOL 0
|
|
#define HDCP_DEBUG 1
|
|
#define HDCP_READY 1
|
|
#define HDCP_REAUTH 2
|
|
#define HDCP_READY_SET (1 << 0)
|
|
#define HDCP_HPRIME_AVAIL (1 << 1)
|
|
#define HDCP_PAIRING_AVAIL (1 << 2)
|
|
#define HDCP_REAUTH_MASK (1 << 3)
|
|
#define HDCP_LINK_INTEG_FAIL (1 << 4)
|
|
|
|
#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_AUTH_CMD 0x5
|
|
|
|
#define HDCP_TA_CTRL_ENABLE 1
|
|
#define HDCP_TA_CTRL_DISABLE 0
|
|
|
|
#define HDCP_CMD_OFFSET 1
|
|
#define HDCP_CMD_BYTE_OFFSET 8
|
|
|
|
|
|
#define MAX_AUX_SIZE 15
|
|
|
|
#define SIZE_ONE_BYTE 1
|
|
#define SIZE_TWO_BYTES 2
|
|
#define SIZE_FIVE_BYTES 5
|
|
#define SIZE_EIGHT_BYTES 8
|
|
|
|
#define KEY_CTRL_RETRIES 101
|
|
#define HDCP_CTRL_RETRIES 13
|
|
#define SRAM_CLR_RETRIES 6
|
|
#define RX_VALIDATE_RETRIES 3
|
|
#define VPRIME_RETRIES 3
|
|
#define KSV_RETRIES 10
|
|
#define MAX_BYTES_READ 15
|
|
#define REPEATER_READY_RETRY 51
|
|
|
|
#define HDCP_KEY_LOAD 0x100
|
|
#define KFUSE_MASK 0x10
|
|
|
|
#define HDCP11_SRM_PATH "vendor/etc/hdcpsrm/hdcp1x.srm"
|
|
|
|
#define CP_IRQ_OFFSET (1 << 2)
|
|
#define CP_IRQ_RESET 0x4
|
|
|
|
/* logging */
|
|
#ifdef VERBOSE_DEBUG
|
|
#define dphdcp_vdbg(...) \
|
|
pr_debug("dphdcp: " __VA_ARGS__)
|
|
#else
|
|
#define dphdcp_vdbg(...) \
|
|
({ \
|
|
if (0) \
|
|
pr_debug("dphdcp: " __VA_ARGS__); \
|
|
0; \
|
|
})
|
|
#endif
|
|
#define dphdcp_debug(...) \
|
|
pr_debug("dphdcp: " __VA_ARGS__)
|
|
#define dphdcp_err(...) \
|
|
pr_err("dphdcp: Error: " __VA_ARGS__)
|
|
#define dphdcp_info(...) \
|
|
pr_info("dphdcp: " __VA_ARGS__)
|
|
|
|
#define HDCP_PORT_NAME "com.nvidia.tos.13f616f9-8572-4a6f-a1f104aa9b05f9ff"
|
|
|
|
static bool repeater_flag;
|
|
static bool vprime_check_done;
|
|
static struct tegra_dphdcp **dphdcp_head;
|
|
|
|
static int tegra_dphdcp_read(struct tegra_dc_dp_data *dp, u32 cmd,
|
|
u8 *data_ptr, u32 size, u32 *aux_status)
|
|
{
|
|
u32 status = 0;
|
|
u32 cursize = 0;
|
|
int ret = 0;
|
|
struct tegra_dc_dpaux_data *dpaux = NULL;
|
|
|
|
if (!dp || !data_ptr || !aux_status) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
|
|
return -EIO;
|
|
|
|
dpaux = dp->dpaux;
|
|
cursize = size;
|
|
mutex_lock(&dpaux->lock);
|
|
tegra_dpaux_get(dp->dpaux);
|
|
ret = tegra_dc_dpaux_read_chunk_locked(dpaux, DPAUX_DP_AUXCTL_CMD_AUXRD,
|
|
cmd, data_ptr, &cursize, &status);
|
|
tegra_dpaux_put(dp->dpaux);
|
|
mutex_unlock(&dpaux->lock);
|
|
if (ret)
|
|
dev_err(&dp->dc->ndev->dev,
|
|
"dp: Failed to read data. CMD 0x%x, Status 0x%x\n",
|
|
cmd, status);
|
|
*aux_status = status;
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_dphdcp_write(struct tegra_dc_dp_data *dp, u32 cmd,
|
|
u8 *data, u32 size)
|
|
{
|
|
u32 status = 0;
|
|
u32 cursize = 0;
|
|
int ret;
|
|
struct tegra_dc_dpaux_data *dpaux = NULL;
|
|
|
|
if (!dp || !data) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP)
|
|
return -EIO;
|
|
|
|
dpaux = dp->dpaux;
|
|
cursize = size;
|
|
mutex_lock(&dpaux->lock);
|
|
tegra_dpaux_get(dp->dpaux);
|
|
ret = tegra_dc_dpaux_write_chunk_locked(dpaux,
|
|
DPAUX_DP_AUXCTL_CMD_AUXWR, cmd, data,
|
|
&cursize, &status);
|
|
tegra_dpaux_put(dp->dpaux);
|
|
mutex_unlock(&dpaux->lock);
|
|
if (ret)
|
|
dev_err(&dp->dc->ndev->dev,
|
|
"dp: Failed to write data. CMD 0x%x, Status 0x%x\n",
|
|
cmd, status);
|
|
return ret;
|
|
}
|
|
|
|
/* read 5 bytes of data */
|
|
static int tegra_dphdcp_read40(struct tegra_dc_dp_data *dp, u32 cmd,
|
|
u64 *data)
|
|
{
|
|
u8 buf[SIZE_FIVE_BYTES];
|
|
int i;
|
|
u64 n;
|
|
int e;
|
|
u32 status;
|
|
|
|
if (!dp || !data) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
e = tegra_dphdcp_read(dp, cmd, buf, sizeof(buf), &status);
|
|
if (e)
|
|
return e;
|
|
|
|
/* assign the value read from aux to data */
|
|
for (i = 0, n = 0; i < 5; i++) {
|
|
n <<= 8;
|
|
n |= buf[4 - i];
|
|
}
|
|
if (data)
|
|
*data = n;
|
|
return 0;
|
|
}
|
|
|
|
/* read 2 bytes of data */
|
|
static int tegra_dphdcp_read16(struct tegra_dc_dp_data *dp, u32 cmd,
|
|
u64 *data)
|
|
{
|
|
u8 buf[SIZE_TWO_BYTES];
|
|
int e;
|
|
u32 status;
|
|
|
|
if (!dp || !data) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
e = tegra_dphdcp_read(dp, cmd, buf, sizeof(buf), &status);
|
|
if (e)
|
|
return e;
|
|
|
|
if (data)
|
|
*data = buf[0] | (u16)buf[1] << 8;
|
|
return 0;
|
|
}
|
|
|
|
/* write 8 bytes of data */
|
|
static int tegra_dphdcp_write64(struct tegra_dc_dp_data *dp, u32 reg,
|
|
u64 *data)
|
|
{
|
|
char buf[SIZE_EIGHT_BYTES];
|
|
|
|
if (!dp || !data) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(buf, (char *)data, sizeof(buf));
|
|
return tegra_dphdcp_write(dp, reg, buf, sizeof(buf));
|
|
}
|
|
|
|
/* write 1 byte of data */
|
|
static int tegra_dphdcp_write8(struct tegra_dc_dp_data *dp, u32 reg,
|
|
u8 data)
|
|
{
|
|
char buf[SIZE_ONE_BYTE];
|
|
u8 cur_data;
|
|
|
|
if (!dp) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cur_data = data;
|
|
|
|
memcpy(buf, (char *)&cur_data, sizeof(buf));
|
|
return tegra_dphdcp_write(dp, reg, buf, sizeof(buf));
|
|
}
|
|
|
|
/* write 5 bytes of data */
|
|
static int tegra_dphdcp_write40(struct tegra_dc_dp_data *dp, u32 reg,
|
|
u64 *data)
|
|
{
|
|
char buf[SIZE_FIVE_BYTES];
|
|
|
|
if (!dp || !data) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(buf, data, sizeof(buf));
|
|
return tegra_dphdcp_write(dp, reg, buf, sizeof(buf));
|
|
}
|
|
|
|
/*
|
|
* wait for bits in mask to be set to value in NV_SOR_KEY_CTRL
|
|
* waits upto 100 ms
|
|
*/
|
|
static int wait_key_ctrl(struct tegra_dc_sor_data *sor, u32 mask, u32 value)
|
|
{
|
|
int retries = KEY_CTRL_RETRIES;
|
|
u32 ctrl;
|
|
|
|
if (!sor) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
do {
|
|
usleep_range(1, 2);
|
|
ctrl = tegra_sor_readl_ext(sor, NV_SOR_KEY_CTRL);
|
|
if (((ctrl ^ value) & mask) == 0)
|
|
break;
|
|
} while (--retries);
|
|
if (!retries) {
|
|
dphdcp_err("key ctrl read timeout (mask=0x%x)\n", mask);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* set or clear RUN_YES */
|
|
static void hdcp_ctrl_run(struct tegra_dc_sor_data *sor, bool v)
|
|
{
|
|
u32 ctrl;
|
|
|
|
if (!sor) {
|
|
dphdcp_err("Null params sent\n");
|
|
return;
|
|
}
|
|
|
|
if (v) {
|
|
ctrl = tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_CTRL);
|
|
ctrl |= HDCP_RUN_YES;
|
|
} else {
|
|
ctrl = 0;
|
|
}
|
|
|
|
tegra_sor_writel_ext(sor, NV_SOR_DP_HDCP_CTRL, ctrl);
|
|
}
|
|
|
|
/*
|
|
* wait for any bits in mask to be set in NV_SOR_DP_HDCP_CTRL
|
|
* sleeps up to 120 ms
|
|
*/
|
|
static int wait_hdcp_ctrl(struct tegra_dc_sor_data *sor, u32 mask, u32 *v)
|
|
{
|
|
int retries = HDCP_CTRL_RETRIES;
|
|
u32 ctrl;
|
|
|
|
if (!sor) {
|
|
dphdcp_err("Null params sent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
do {
|
|
ctrl = tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_CTRL);
|
|
if ((ctrl & mask)) {
|
|
if (v)
|
|
*v = ctrl;
|
|
break;
|
|
}
|
|
if (retries > 1)
|
|
usleep_range(10, 15);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
dphdcp_err("ctrl read timeout (mask=0x%x)\n", mask);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* check if the KSV values returned are valid,
|
|
* i.e a combination of 20 ones and 20 zeroes
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* 64-bit link encryption session random number */
|
|
static inline u64 get_an(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u64 r;
|
|
|
|
if (!sor) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = (u64)tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_AN_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_AN_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 40-bit transmitter's key selection vector */
|
|
static inline u64 get_aksv(struct tegra_dc_sor_data *sor)
|
|
{
|
|
u64 r;
|
|
|
|
if (!sor) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = (u64)tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_AKSV_MSB) << 32;
|
|
r |= tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_AKSV_LSB);
|
|
return r;
|
|
}
|
|
|
|
/* 40-bit receiver's key selection vector */
|
|
static inline void set_bksv(struct tegra_dc_sor_data *sor, u64 b_ksv,
|
|
bool repeater)
|
|
{
|
|
if (sor) {
|
|
if (repeater)
|
|
b_ksv |= (u64)REPEATER << 32;
|
|
tegra_sor_writel_ext(sor, NV_SOR_DP_HDCP_BKSV_LSB, (u32)b_ksv);
|
|
tegra_sor_writel_ext(sor, NV_SOR_DP_HDCP_BKSV_MSB, b_ksv >> 32);
|
|
}
|
|
}
|
|
|
|
static int get_bcaps(struct tegra_dc_dp_data *dp, u8 *b_caps)
|
|
{
|
|
u32 status;
|
|
|
|
if (!dp || !b_caps) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_dphdcp_read(dp, NV_DPCD_HDCP_BCAPS_OFFSET,
|
|
b_caps, 1, &status);
|
|
}
|
|
|
|
static int get_bstatus(struct tegra_dc_dp_data *dp, u8 *bstatus)
|
|
{
|
|
u32 status;
|
|
|
|
if (!dp || !bstatus) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_dphdcp_read(dp, NV_DPCD_HDCP_BSTATUS_OFFSET,
|
|
bstatus, 1, &status);
|
|
}
|
|
|
|
static int get_irq_status(struct tegra_dc_dp_data *dp, u8 *irq_status)
|
|
{
|
|
u32 status;
|
|
|
|
if (!dp || !irq_status) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return tegra_dphdcp_read(dp, NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR,
|
|
irq_status, 1, &status);
|
|
}
|
|
|
|
static inline bool dphdcp_is_plugged(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
rmb();
|
|
if (dphdcp)
|
|
return dphdcp->plugged;
|
|
return false;
|
|
}
|
|
|
|
static inline bool dphdcp_set_plugged(struct tegra_dphdcp *dphdcp,
|
|
bool plugged)
|
|
{
|
|
if (dphdcp) {
|
|
dphdcp->plugged = plugged;
|
|
wmb();
|
|
}
|
|
return plugged;
|
|
}
|
|
|
|
static int load_kfuse(struct tegra_dc_dp_data *dp)
|
|
{
|
|
u32 ctrl;
|
|
u32 tmp;
|
|
int retries;
|
|
int e;
|
|
int i;
|
|
unsigned buf[KFUSE_DATA_SZ/4];
|
|
struct tegra_dc_sor_data *sor;
|
|
|
|
if (!dp) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sor = dp->sor;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
/* load kfuse */
|
|
dphdcp_vdbg("loading kfuse\n");
|
|
/* copy load kfuse into buffer - only needed for early Tegra parts */
|
|
e = tegra_kfuse_read(buf, sizeof(buf));
|
|
if (e) {
|
|
dphdcp_err("Kfuse read failure\n");
|
|
return e;
|
|
}
|
|
|
|
/* write the kfuse to the DP SRAM */
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_CTRL, 1);
|
|
|
|
/* issue a reload */
|
|
ctrl = tegra_sor_readl_ext(sor, NV_SOR_KEY_CTRL);
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_CTRL, ctrl | PKEY_RELOAD_TRIGGER
|
|
| LOCAL_KEYS);
|
|
e = wait_key_ctrl(sor, PKEY_LOADED, PKEY_LOADED);
|
|
if (e) {
|
|
dphdcp_err("key reload timeout\n");
|
|
return e;
|
|
}
|
|
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_SKEY_INDEX, 0);
|
|
|
|
/* wait for SRAM to be cleared */
|
|
retries = SRAM_CLR_RETRIES;
|
|
do {
|
|
tmp = tegra_sor_readl_ext(sor, NV_SOR_KEY_DEBUG0);
|
|
if ((tmp & 1) == 0)
|
|
break;
|
|
if (retries > 1)
|
|
mdelay(1);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
dphdcp_err("key SRAM clear timeout\n");
|
|
return -EIO;
|
|
}
|
|
|
|
for (i = 0; i < KFUSE_DATA_SZ / 4; i += 4) {
|
|
|
|
/* load 128-bits*/
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_HDCP_KEY_0, buf[i]);
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_HDCP_KEY_1, buf[i+1]);
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_HDCP_KEY_2, buf[i+2]);
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_HDCP_KEY_3, buf[i+3]);
|
|
|
|
/* trigger LOAD_HDCP_KEY */
|
|
tegra_sor_writel_ext(sor,
|
|
NV_SOR_KEY_HDCP_KEY_TRIG, HDCP_KEY_LOAD);
|
|
tmp = LOCAL_KEYS | WRITE16;
|
|
if (i)
|
|
tmp |= AUTOINC;
|
|
tegra_sor_writel_ext(sor, NV_SOR_KEY_CTRL, tmp);
|
|
|
|
/* wait for WRITE16 to complete */
|
|
e = wait_key_ctrl(sor, KFUSE_MASK, 0); /* WRITE16 */
|
|
if (e) {
|
|
dphdcp_err("key write timeout\n");
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* check the 16 bit link integrity value */
|
|
static inline u64 get_transmitter_ro_prime(struct tegra_dc_dp_data *dp)
|
|
{
|
|
struct tegra_dc_sor_data *sor;
|
|
|
|
if (!dp) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sor = dp->sor;
|
|
return tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_RI);
|
|
}
|
|
|
|
/* R0' prime value generated from the receiver */
|
|
static inline int get_receiver_ro_prime(struct tegra_dc_dp_data *dp, u64 *r)
|
|
{
|
|
if (!dp || !r) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
return tegra_dphdcp_read16(dp, NV_DPCD_HDCP_RPRIME_OFFSET, r);
|
|
}
|
|
|
|
static int validate_rx(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
int retries = RX_VALIDATE_RETRIES;
|
|
u64 rx = 0, tx = 0;
|
|
int e;
|
|
struct tegra_dc_dp_data *dp;
|
|
|
|
if (!dphdcp) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dp = dphdcp->dp;
|
|
|
|
/* try 3 times for possible link errors */
|
|
do {
|
|
tx = get_transmitter_ro_prime(dp);
|
|
e = get_receiver_ro_prime(dp, &rx);
|
|
} while (--retries && rx != tx);
|
|
dphdcp_vdbg("rx=0x%016llx tx=0x%016llx\n", rx, tx);
|
|
|
|
if (rx != tx)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
/* get V' 160-bit SHA-1 hash from repeater */
|
|
static int get_vprime(struct tegra_dc_dp_data *dp, u8 *v_prime)
|
|
{
|
|
int e, i;
|
|
u32 status;
|
|
|
|
if (!dp || !v_prime) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < 20; i += 4) {
|
|
e = tegra_dphdcp_read(dp,
|
|
NV_DPCP_HDCP_SHA_H0_OFFSET+i, v_prime+i, 4, &status);
|
|
if (e) {
|
|
dphdcp_err("Error reading V'\n");
|
|
return e;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_ksvfifo(struct tegra_dc_dp_data *dp,
|
|
unsigned num_bksv_list, u64 *ksv_list)
|
|
{
|
|
u8 *buf = NULL;
|
|
u8 *orig_buf = NULL;
|
|
int e;
|
|
unsigned int dp_retries = KSV_RETRIES;
|
|
u32 status;
|
|
size_t buf_len = num_bksv_list * SIZE_FIVE_BYTES;
|
|
|
|
|
|
if (!ksv_list || !dp || 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;
|
|
|
|
orig_buf = buf;
|
|
while (buf_len > MAX_BYTES_READ) {
|
|
aux_read:
|
|
e = tegra_dphdcp_read(dp,
|
|
NV_DPCD_HDCP_KSV_FIFO_OFFSET, buf, MAX_BYTES_READ, &status);
|
|
if (e) {
|
|
dphdcp_err("Error reading KSV\n");
|
|
kfree(orig_buf);
|
|
return e;
|
|
}
|
|
if ((status & DPAUX_DP_AUXSTAT_REPLYTYPE_I2CDEFER) ||
|
|
(status & DPAUX_DP_AUXSTAT_REPLYTYPE_DEFER)) {
|
|
if (--dp_retries)
|
|
goto aux_read;
|
|
}
|
|
buf_len -= MAX_BYTES_READ;
|
|
buf += MAX_BYTES_READ;
|
|
}
|
|
|
|
if (buf_len)
|
|
e = tegra_dphdcp_read(dp,
|
|
NV_DPCD_HDCP_KSV_FIFO_OFFSET, buf, buf_len, &status);
|
|
if (e) {
|
|
dphdcp_err("Error reading KSV\n");
|
|
kfree(orig_buf);
|
|
return e;
|
|
}
|
|
memcpy(ksv_list, orig_buf, num_bksv_list*5);
|
|
kfree(orig_buf);
|
|
return 0;
|
|
}
|
|
|
|
/* validate srm signature */
|
|
static int get_srm_signature(struct hdcp_context_t *hdcp_context,
|
|
char *nonce, uint64_t *pkt, void *ta_ctx)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!hdcp_context || !nonce || !pkt || !ta_ctx) {
|
|
dphdcp_err("Null params sent!\n");
|
|
return err;
|
|
}
|
|
/* generate nonce in the ucode */
|
|
err = tsec_hdcp_generate_nonce(hdcp_context, nonce);
|
|
if (err) {
|
|
dphdcp_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) = HDCP_1x;
|
|
err = te_launch_trusted_oper(pkt, PKT_SIZE, HDCP_CMD_GEN_CMAC, ta_ctx);
|
|
if (err)
|
|
dphdcp_err("te launch operation failed with error %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
/* SRM revocation check for receiver */
|
|
static int srm_revocation_check(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
struct hdcp_context_t *hdcp_context =
|
|
kmalloc(sizeof(struct hdcp_context_t), GFP_KERNEL);
|
|
int e = 0;
|
|
unsigned char nonce[HDCP_NONCE_SIZE];
|
|
|
|
uint64_t *pkt = kzalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
if (!pkt || !hdcp_context)
|
|
goto exit;
|
|
|
|
e = tsec_hdcp_context_creation(hdcp_context, DISPLAY_TYPE_DP,
|
|
dphdcp->dp->sor->ctrl_num);
|
|
if (e) {
|
|
dphdcp_err("hdcp context create/init failed\n");
|
|
goto exit;
|
|
}
|
|
|
|
e = tsec_hdcp_create_session(hdcp_context, DISPLAY_TYPE_DP,
|
|
dphdcp->dp->sor->ctrl_num);
|
|
if (e) {
|
|
dphdcp_err("error in session creation\n");
|
|
goto exit;
|
|
}
|
|
e = get_srm_signature(hdcp_context, nonce, pkt, dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
e = tsec_hdcp_revocation_check(hdcp_context,
|
|
(unsigned char *)(pkt + HDCP_CMAC_OFFSET),
|
|
*((unsigned int *)(pkt + HDCP_TSEC_ADDR_OFFSET)),
|
|
TEGRA_NVHDCP_PORT_DP, HDCP_1x);
|
|
|
|
if (e)
|
|
dphdcp_err("hdcp revocation check failed with err: %x\n", e);
|
|
exit:
|
|
tsec_hdcp_free_context(hdcp_context);
|
|
kfree(hdcp_context);
|
|
kfree(pkt);
|
|
return e;
|
|
}
|
|
|
|
/* vprime verification for repeater */
|
|
static int tsec_hdcp_dp_verify_vprime(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
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_DP,
|
|
dphdcp->dp->sor->ctrl_num);
|
|
if (e) {
|
|
dphdcp_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, dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("Error getting srm signature!\n");
|
|
goto exit;
|
|
}
|
|
|
|
memset(&verify_vprime_param, 0x0,
|
|
sizeof(struct hdcp_verify_vprime_param));
|
|
|
|
/* convert 64 bit values to 40 bit */
|
|
p = buf;
|
|
for (i = 0; i < dphdcp->num_bksv_list; i++) {
|
|
p[0] = (u8)(dphdcp->bksv_list[i] & 0xff);
|
|
p[1] = (u8)((dphdcp->bksv_list[i]>>8) & 0xff);
|
|
p[2] = (u8)((dphdcp->bksv_list[i]>>16) & 0xff);
|
|
p[3] = (u8)((dphdcp->bksv_list[i]>>24) & 0xff);
|
|
p[4] = (u8)((dphdcp->bksv_list[i]>>32) & 0xff);
|
|
p += 5;
|
|
}
|
|
|
|
memcpy((void *)verify_vprime_param.vprime, dphdcp->v_prime,
|
|
HDCP_SIZE_VPRIME_1X_8);
|
|
verify_vprime_param.port = TEGRA_NVHDCP_PORT_DP; /* hdcp 1.x */
|
|
verify_vprime_param.bstatus = dphdcp->binfo;
|
|
e = tsec_hdcp1x_verify_vprime(verify_vprime_param, hdcp_context,
|
|
buf, dphdcp->num_bksv_list, pkt);
|
|
|
|
exit:
|
|
tsec_hdcp_free_context(hdcp_context);
|
|
kfree(pkt);
|
|
kfree(hdcp_context);
|
|
return e;
|
|
}
|
|
|
|
static int get_repeater_info(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
int e = 0;
|
|
unsigned int retries;
|
|
int vcheck_tries = VPRIME_RETRIES;
|
|
u8 bstatus;
|
|
u64 binfo;
|
|
u8 irq;
|
|
int err = 0;
|
|
struct tegra_dc_dp_data *dp = dphdcp->dp;
|
|
|
|
dphdcp_vdbg("repeater found:fetching repeater info\n");
|
|
/* wait up to 5 seconds for READY on repeater */
|
|
retries = REPEATER_READY_RETRY;
|
|
do {
|
|
mutex_lock(&dphdcp->lock);
|
|
e = dphdcp_is_plugged(dphdcp);
|
|
mutex_unlock(&dphdcp->lock);
|
|
if (!e) {
|
|
dphdcp_err("disconnect while waiting for repeater\n");
|
|
return -EIO;
|
|
}
|
|
/* wait till receiver computes V' */
|
|
e = get_bstatus(dp, &bstatus);
|
|
if (!e && (bstatus & BSTATUS_READY)) {
|
|
dphdcp_vdbg("Bstatus READY from repeater\n");
|
|
break;
|
|
}
|
|
if (retries > 1)
|
|
msleep(100);
|
|
} while (--retries);
|
|
if (!retries) {
|
|
dphdcp_err("repeater Bstatus read timeout\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
/* READY is set so the CP_IRQ interrupt should go high */
|
|
e = get_irq_status(dp, &irq);
|
|
if (e) {
|
|
dphdcp_err("irq register read failure!\n");
|
|
return e;
|
|
}
|
|
if (irq & CP_IRQ_OFFSET)
|
|
dphdcp_vdbg("CP_IRQ interrupt set high\n");
|
|
|
|
/* verify V' thrice to check for link failures */
|
|
do {
|
|
memset(dphdcp->bksv_list, 0, sizeof(dphdcp->bksv_list));
|
|
memset(dphdcp->v_prime, 0, sizeof(dphdcp->v_prime));
|
|
/* read Binfo register */
|
|
e = tegra_dphdcp_read16(dp, NV_DPCD_HDCP_BINFO_OFFSET,
|
|
&binfo);
|
|
if (e) {
|
|
dphdcp_err("Binfo read failure!\n");
|
|
return e;
|
|
}
|
|
msleep(100);
|
|
|
|
/* clear the irq register, this will be needed to find out
|
|
* if a spurious interrupt is generated. The DEVICE_SERVICE_IRQ
|
|
* register is clearable read only and can be cleared by writing
|
|
* 1 to the respective bit
|
|
*/
|
|
e = tegra_dphdcp_write8(dp, NV_DPCD_DEVICE_SERVICE_IRQ_VECTOR,
|
|
CP_IRQ_RESET);
|
|
/* wait for the CP_IRQ bit to be cleared */
|
|
msleep(100);
|
|
|
|
e = get_irq_status(dp, &irq);
|
|
if (e) {
|
|
dphdcp_err("irq register read failure!\n");
|
|
return e;
|
|
}
|
|
dphdcp_vdbg("read irq after clearing: %x\n", irq);
|
|
|
|
if (binfo & BINFO_MAX_DEVS_EXCEEDED) {
|
|
dphdcp_err("repeater:max devices (0x%016llx)\n", binfo);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (binfo & BINFO_MAX_CASCADE_EXCEEDED) {
|
|
dphdcp_err("repeater:max cascade (0x%16llx)\n", binfo);
|
|
return -EINVAL;
|
|
}
|
|
dphdcp->binfo = binfo;
|
|
dphdcp->num_bksv_list = binfo & 0x7f;
|
|
dphdcp_vdbg("Binfo 0x%16llx (devices: %d)\n",
|
|
binfo, dphdcp->num_bksv_list);
|
|
|
|
/* do not read KSV FIFO when device count is zero */
|
|
if (dphdcp->num_bksv_list != 0) {
|
|
e = get_ksvfifo(dp, dphdcp->num_bksv_list,
|
|
dphdcp->bksv_list);
|
|
if (e) {
|
|
dphdcp_err("KSVFIFO read (err %d)\n", e);
|
|
return e;
|
|
}
|
|
}
|
|
/* verify V' three times to check for link failures */
|
|
e = get_vprime(dp, dphdcp->v_prime);
|
|
if (e)
|
|
dphdcp_err("repeater Vprime read failure!\n");
|
|
err = tsec_hdcp_dp_verify_vprime(dphdcp);
|
|
if (err)
|
|
dphdcp_err("vprime verification failed\n");
|
|
|
|
/* read CP_IRQ interrupt to check for spurious interrupts.
|
|
* This needs to be done only when we are authenticating
|
|
* on the DP engine. On the HDMI engine, we will not take
|
|
* any action for a spurious interrupt
|
|
*/
|
|
if (!repeater_flag) {
|
|
e = get_irq_status(dp, &irq);
|
|
if (e) {
|
|
dphdcp_err("irq register read failure!\n");
|
|
return e;
|
|
}
|
|
get_bstatus(dp, &bstatus);
|
|
/* For a spurious CP_IRQ interrupt, the CP_IRQ bit
|
|
* will be high and the bstatus register bits will be
|
|
* de-asserted. Check for both these conditions
|
|
* to ensure if the interrupt generated is a
|
|
* spurious one
|
|
*/
|
|
if ((irq & CP_IRQ_OFFSET) && (bstatus == 0)) {
|
|
dphdcp_vdbg("Spurious interrupt set\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
} while (--vcheck_tries && err);
|
|
if (err)
|
|
return -EINVAL;
|
|
vprime_check_done = true;
|
|
return 0;
|
|
}
|
|
|
|
static void dphdcp_downstream_worker(struct work_struct *work)
|
|
{
|
|
int e = 0;
|
|
u8 b_caps = 0;
|
|
u8 bstatus = 0;
|
|
u32 tmp = 0;
|
|
u32 res = 0;
|
|
|
|
struct tegra_dphdcp *dphdcp =
|
|
container_of(to_delayed_work(work), struct tegra_dphdcp, work);
|
|
struct tegra_dc_dp_data *dp = dphdcp->dp;
|
|
struct tegra_dc *dc = dp->dc;
|
|
struct tegra_dc_sor_data *sor = dp->sor;
|
|
int hdcp_ta_ret; /* track returns from TA */
|
|
uint32_t ta_cmd = HDCP_AUTH_CMD;
|
|
bool enc = false;
|
|
|
|
uint64_t *pkt = kmalloc(PKT_SIZE, GFP_KERNEL);
|
|
|
|
/* If memory unavailable */
|
|
if (!pkt) {
|
|
dphdcp_err("Memory allocation failed\n");
|
|
e = -ENOMEM;
|
|
goto failure;
|
|
}
|
|
dphdcp_vdbg("%s():started thread %s\n", __func__, dphdcp->name);
|
|
tegra_dc_io_start(dc);
|
|
mutex_lock(&dphdcp->lock);
|
|
if (dphdcp->state == STATE_OFF) {
|
|
dphdcp_err("dphdcp failure - giving up\n");
|
|
goto err;
|
|
}
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
/* check plug state to terminate early in case flush_workqueue() */
|
|
if (!dphdcp_is_plugged(dphdcp)) {
|
|
dphdcp_err("worker started while unplugged!\n");
|
|
goto lost_dp;
|
|
}
|
|
dphdcp_vdbg("%s():hpd=%d\n", __func__, dphdcp->plugged);
|
|
|
|
dphdcp->a_ksv = 0;
|
|
dphdcp->b_ksv = 0;
|
|
dphdcp->a_n = 0;
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
/* read bcaps from receiver */
|
|
e = get_bcaps(dp, &b_caps);
|
|
mutex_lock(&dphdcp->lock);
|
|
if (e) {
|
|
dphdcp_err("Error reading bcaps\n");
|
|
goto failure;
|
|
}
|
|
|
|
dphdcp_vdbg("read Bcaps = 0x%02x\n", b_caps);
|
|
/* check if receiver is hdcp capable */
|
|
if (b_caps & BCAPS_HDCP_CAPABLE)
|
|
dphdcp_vdbg("receiver is hdcp capable\n");
|
|
else {
|
|
dphdcp_err("receiver is not hdcp capable\n");
|
|
goto failure;
|
|
}
|
|
dphdcp->ta_ctx = NULL;
|
|
e = te_open_trusted_session(HDCP_PORT_NAME, &dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("open session failed\n");
|
|
goto failure;
|
|
}
|
|
repeater_auth:
|
|
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_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = 0;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = b_caps & BCAPS_REPEATER;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 5*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd,
|
|
dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te_launch_op failed with error %d\n", e);
|
|
goto failure;
|
|
} else {
|
|
dphdcp_vdbg("Loading kfuse\n");
|
|
e = load_kfuse(dp);
|
|
if (e) {
|
|
dphdcp_err("te_launch_op failed with error %d\n", e);
|
|
goto failure;
|
|
} else {
|
|
dphdcp_vdbg("Loading kfuse\n");
|
|
e = load_kfuse(dp);
|
|
if (e) {
|
|
dphdcp_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_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = HDCP_TA_CTRL_ENABLE;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd,
|
|
dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te_launch_op failed with error %d\n", e);
|
|
goto failure;
|
|
} else {
|
|
dphdcp_vdbg("wait AN_VALID ...\n");
|
|
hdcp_ta_ret = *pkt;
|
|
dphdcp_vdbg("An returned %x\n", e);
|
|
if (hdcp_ta_ret) {
|
|
dphdcp_err("An key generation timeout\n");
|
|
goto failure;
|
|
}
|
|
/* check SROM return */
|
|
hdcp_ta_ret = *(pkt + HDCP_CMD_BYTE_OFFSET);
|
|
if (hdcp_ta_ret) {
|
|
dphdcp_err("SROM error\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
|
|
msleep(25);
|
|
*pkt = HDCP_TA_CMD_AKSV;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd,
|
|
dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te_launch_op failed with error %d\n", e);
|
|
goto failure;
|
|
|
|
} else {
|
|
hdcp_ta_ret = (u64)*pkt;
|
|
dphdcp->a_ksv = (u64)*(pkt + 1*HDCP_CMD_BYTE_OFFSET);
|
|
dphdcp->a_n = (u64)*(pkt + 2*HDCP_CMD_BYTE_OFFSET);
|
|
dphdcp_vdbg("Aksv is 0x%016llx\n", dphdcp->a_ksv);
|
|
dphdcp_vdbg("An is 0x%016llx\n", dphdcp->a_n);
|
|
/* check if verification of Aksv failed */
|
|
if (hdcp_ta_ret) {
|
|
dphdcp_err("Aksv verify failure\n");
|
|
goto disable;
|
|
}
|
|
} }
|
|
} else {
|
|
set_bksv(sor, 0, (b_caps & BCAPS_REPEATER));
|
|
e = load_kfuse(dp);
|
|
if (e) {
|
|
dphdcp_err("error loading kfuse\n");
|
|
goto failure;
|
|
}
|
|
|
|
usleep_range(20000, 25000);
|
|
hdcp_ctrl_run(sor, 1);
|
|
|
|
dphdcp_vdbg("waiting for An_valid\n");
|
|
|
|
/* wait for hardware to generate HDCP values */
|
|
e = wait_hdcp_ctrl(sor, AN_VALID | SROM_ERR, &res);
|
|
if (e) {
|
|
dphdcp_err("An key generation timeout\n");
|
|
goto failure;
|
|
}
|
|
if (res & SROM_ERR) {
|
|
dphdcp_err("SROM error\n");
|
|
goto failure;
|
|
}
|
|
|
|
msleep(25);
|
|
|
|
dphdcp->a_ksv = get_aksv(sor);
|
|
dphdcp->a_n = get_an(sor);
|
|
|
|
dphdcp_vdbg("aksv is 0x%016llx\n", dphdcp->a_ksv);
|
|
dphdcp_vdbg("an is 0x%016llx\n", dphdcp->a_n);
|
|
|
|
if (verify_ksv(dphdcp->a_ksv)) {
|
|
dphdcp_err("Aksv verify failure! (0x%016llx)\n",
|
|
dphdcp->a_ksv);
|
|
goto disable;
|
|
}
|
|
}
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
/* write An to receiver */
|
|
e = tegra_dphdcp_write64(dp, NV_DPCD_HDCP_AN_OFFSET, &dphdcp->a_n);
|
|
if (e) {
|
|
dphdcp_err("An write failure\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
dphdcp_vdbg("wrote An = 0x%016llx\n", dphdcp->a_n);
|
|
|
|
/* write Aksv to receiver */
|
|
e = tegra_dphdcp_write40(dp, NV_DPCD_HDCP_AKSV_OFFSET,
|
|
&dphdcp->a_ksv);
|
|
if (e) {
|
|
dphdcp_err("Aksv write failure\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
dphdcp_vdbg("wrote Aksv = 0x%010llx\n", dphdcp->a_ksv);
|
|
|
|
mutex_lock(&dphdcp->lock);
|
|
/* handle if connection lost in the middle of authentication */
|
|
if (!dphdcp_is_plugged(dphdcp))
|
|
goto lost_dp;
|
|
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
/* get bksv from receiver */
|
|
e = tegra_dphdcp_read40(dp, NV_DPCD_HDCP_BKSV_OFFSET,
|
|
&dphdcp->b_ksv);
|
|
mutex_lock(&dphdcp->lock);
|
|
if (e) {
|
|
dphdcp_err("Bksv read failure\n");
|
|
goto failure;
|
|
}
|
|
dphdcp_vdbg("read Bksv from device: 0x%016llx\n", dphdcp->b_ksv);
|
|
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_BKSV;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = dphdcp->b_ksv;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = b_caps & BCAPS_REPEATER;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 5*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te launch operation failed with error: %d\n", e);
|
|
goto failure;
|
|
} else {
|
|
/* check if Bksv verification was successful */
|
|
hdcp_ta_ret = (int)*pkt;
|
|
if (hdcp_ta_ret) {
|
|
dphdcp_err("Bksv verify failure\n");
|
|
goto failure;
|
|
} else {
|
|
dphdcp_vdbg("loaded Bksv into controller\n");
|
|
/* check if R0 read was successful */
|
|
hdcp_ta_ret = (int)*(pkt);
|
|
if (hdcp_ta_ret) {
|
|
dphdcp_err("R0 read failure\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* verify the bksv to be if it has a correct combination of
|
|
* 1's and 0's
|
|
*/
|
|
if (verify_ksv(dphdcp->b_ksv)) {
|
|
dphdcp_err("Bksv verify failure! (0x%016llx)\n",
|
|
dphdcp->b_ksv);
|
|
goto failure;
|
|
}
|
|
|
|
set_bksv(sor, dphdcp->b_ksv, (b_caps & BCAPS_REPEATER));
|
|
dphdcp_vdbg("Loaded Bksv into controller\n");
|
|
|
|
/* check if the computations of Km, Ks, M0 and R0 are over */
|
|
e = wait_hdcp_ctrl(sor, R0_VALID, NULL);
|
|
if (e) {
|
|
dphdcp_err("R0 read failure\n");
|
|
goto failure;
|
|
}
|
|
|
|
dphdcp_vdbg("R0 valid\n");
|
|
}
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
msleep(100); /* cannot read R0' within 100 ms of writing AKSV */
|
|
|
|
/*
|
|
* after part 1 of authentication protocol, check for
|
|
* link integrity
|
|
* TODO: add support for both single and multi stream mode
|
|
*/
|
|
if (!repeater_flag) {
|
|
e = validate_rx(dphdcp);
|
|
if (e) {
|
|
dphdcp_err("could not validate receiver\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_ENC;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = b_caps;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE/4, ta_cmd,
|
|
dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("launch oper failed with error: %d\n", e);
|
|
goto failure;
|
|
}
|
|
enc = true;
|
|
} else {
|
|
tmp = tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_CTRL);
|
|
tmp |= CRYPT_ENABLED;
|
|
tegra_sor_writel_ext(sor, NV_SOR_DP_HDCP_CTRL, tmp);
|
|
}
|
|
dphdcp_vdbg("CRYPT enabled\n");
|
|
}
|
|
msleep(100);
|
|
e = get_bstatus(dp, &bstatus);
|
|
if (!e && (bstatus & BSTATUS_LINK_INTEG_FAIL)) {
|
|
dphdcp_err("link integrity failure\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
/* revocation check for receiver. For repeater, is it
|
|
* handled in verify V'
|
|
*/
|
|
if (!(b_caps & BCAPS_REPEATER)) {
|
|
e = srm_revocation_check(dphdcp);
|
|
if (e) {
|
|
dphdcp_err("SRM revocation check failed\n");
|
|
goto failure;
|
|
}
|
|
}
|
|
/*
|
|
* part 2 of authentication protocol, if receiver is
|
|
* a repeater
|
|
*/
|
|
if ((b_caps & BCAPS_REPEATER) && !vprime_check_done) {
|
|
e = get_repeater_info(dphdcp);
|
|
if (e) {
|
|
dphdcp_err("get repeater info failed\n");
|
|
/* some latency before we transition to the
|
|
* HDMI engine
|
|
*/
|
|
msleep(100);
|
|
repeater_flag = true;
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
/* continue last part of authentication as
|
|
* a DP receiver, ie. second stage of
|
|
* authentication will not be performed
|
|
*/
|
|
repeater_flag = false;
|
|
mutex_lock(&dphdcp->lock);
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
*pkt = HDCP_TA_CMD_CTRL;
|
|
*(pkt + 1*HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = HDCP_TA_CTRL_DISABLE;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd, dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te_launch_oper failed with err: %d\n", e);
|
|
goto failure;
|
|
}
|
|
} else {
|
|
tmp = tegra_sor_readl_ext(sor, NV_SOR_DP_HDCP_CTRL);
|
|
tmp |= CRYPT_ENABLED;
|
|
tegra_sor_writel_ext(sor, NV_SOR_DP_HDCP_CTRL, tmp);
|
|
}
|
|
goto repeater_auth;
|
|
}
|
|
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp->state = STATE_LINK_VERIFY;
|
|
dphdcp_info("link verified!\n");
|
|
|
|
while (1) {
|
|
if (!dphdcp_is_plugged(dphdcp))
|
|
goto lost_dp;
|
|
|
|
if (dphdcp->state != STATE_LINK_VERIFY)
|
|
goto failure;
|
|
|
|
mutex_unlock(&dphdcp->lock);
|
|
/* check for link integrity failure */
|
|
e = get_bstatus(dp, &bstatus);
|
|
if (!e && (bstatus & BSTATUS_LINK_INTEG_FAIL)) {
|
|
dphdcp_err("link integrity failure\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
wait_event_interruptible_timeout(wq_worker,
|
|
!dphdcp_is_plugged(dphdcp), msecs_to_jiffies(200));
|
|
tegra_dc_io_start(dc);
|
|
mutex_lock(&dphdcp->lock);
|
|
|
|
}
|
|
|
|
failure:
|
|
dphdcp->fail_count++;
|
|
if (dphdcp->fail_count > dphdcp->max_retries)
|
|
dphdcp_err("dphdcp failure- too many failures, giving up\n");
|
|
else {
|
|
dphdcp_err("dphdcp failure- renegotiating in 1 second\n");
|
|
if (!dphdcp_is_plugged(dphdcp))
|
|
goto lost_dp;
|
|
|
|
queue_delayed_work(dphdcp->downstream_wq, &dphdcp->work,
|
|
msecs_to_jiffies(1000));
|
|
}
|
|
|
|
/* Failed because of lack of memory */
|
|
if (e == -ENOMEM) {
|
|
kfree(pkt);
|
|
return;
|
|
}
|
|
lost_dp:
|
|
dphdcp_info("lost dp connection\n");
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
if (tegra_dc_is_nvdisplay()) {
|
|
if (pkt) {
|
|
*pkt = HDCP_TA_CMD_CTRL;
|
|
*(pkt + HDCP_CMD_OFFSET) = TEGRA_NVHDCP_PORT_DP;
|
|
*(pkt + 2*HDCP_CMD_OFFSET) = HDCP_TA_CTRL_DISABLE;
|
|
*(pkt + 3*HDCP_CMD_OFFSET) = repeater_flag;
|
|
*(pkt + 4*HDCP_CMD_OFFSET) = dphdcp->dp->sor->ctrl_num;
|
|
}
|
|
/* a launch operation makes sense only if a valid context exists
|
|
* already
|
|
*/
|
|
if (dphdcp->ta_ctx) {
|
|
e = te_launch_trusted_oper(pkt, PKT_SIZE, ta_cmd,
|
|
dphdcp->ta_ctx);
|
|
if (e) {
|
|
dphdcp_err("te_launch_oper failed with error:"
|
|
"%d\n", e);
|
|
goto failure;
|
|
}
|
|
}
|
|
} else {
|
|
hdcp_ctrl_run(sor, 0);
|
|
}
|
|
|
|
err:
|
|
mutex_unlock(&dphdcp->lock);
|
|
kfree(pkt);
|
|
if (dphdcp->ta_ctx) {
|
|
te_close_trusted_session(dphdcp->ta_ctx);
|
|
dphdcp->ta_ctx = NULL;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
return;
|
|
disable:
|
|
dphdcp->state = STATE_OFF;
|
|
kfree(pkt);
|
|
if (dphdcp->ta_ctx) {
|
|
te_close_trusted_session(dphdcp->ta_ctx);
|
|
dphdcp->ta_ctx = NULL;
|
|
}
|
|
dphdcp_set_plugged(dphdcp, false);
|
|
mutex_unlock(&dphdcp->lock);
|
|
tegra_dc_io_end(dc);
|
|
}
|
|
|
|
#ifdef DPHDCP22
|
|
/* HDCP 2.2 over display port */
|
|
|
|
/* write N bytes of data over AUX channel */
|
|
static int tegra_dphdcp_write_n(struct tegra_dc_dp_data *dp, u32 reg,
|
|
u64 *data, u8 size)
|
|
{
|
|
u8 *buf = NULL;
|
|
u8 *orig_buf = NULL;
|
|
int e;
|
|
|
|
buf = kmalloc(size, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
orig_buf = buf;
|
|
memcpy(buf, data, size);
|
|
if (size <= MAX_AUX_SIZE) {
|
|
e = tegra_dphdcp_write(dp, reg, buf, size);
|
|
if (e) {
|
|
dphdcp_err("Error writing over AUX\n");
|
|
goto error;
|
|
}
|
|
} else {
|
|
while (size > MAX_AUX_SIZE) {
|
|
e = tegra_dphdcp_write(dp, reg, buf, MAX_AUX_SIZE);
|
|
if (e) {
|
|
dphdcp_err("Error writing over AUX\n");
|
|
goto error;
|
|
}
|
|
size -= MAX_AUX_SIZE;
|
|
buf += MAX_AUX_SIZE;
|
|
}
|
|
if (size) {
|
|
e = tegra_dphdcp_write(dp, reg, buf, size);
|
|
if (e) {
|
|
dphdcp_err("Error writing over AUX\n");
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
error:
|
|
kfree(orig_buf);
|
|
return e;
|
|
}
|
|
|
|
/* read N bytes of data over AUX channel */
|
|
static int tegra_dphdcp_read_n(struct tegra_dc_dp_data *dp, u32 cmd,
|
|
u64 *data, int size)
|
|
{
|
|
u8 *buf;
|
|
int e;
|
|
u8 *orig_buf;
|
|
u32 status;
|
|
|
|
buf = kmalloc(size, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
orig_buf = buf;
|
|
if (size <= MAX_AUX_SIZE) {
|
|
e = tegra_dphdcp_read(dp, cmd, buf, size, &status);
|
|
if (e) {
|
|
dphdcp_err("Error reading over AUX\n");
|
|
goto error;
|
|
}
|
|
} else {
|
|
while (size > MAX_AUX_SIZE) {
|
|
e = tegra_dphdcp_read(dp, cmd, buf,
|
|
MAX_AUX_SIZE, &status);
|
|
if (e) {
|
|
dphdcp_err("Error reading over AUX\n");
|
|
goto error;
|
|
}
|
|
size -= MAX_AUX_SIZE;
|
|
buf += MAX_AUX_SIZE;
|
|
}
|
|
if (size) {
|
|
e = tegra_dphdcp_write(dp, cmd, buf, MAX_AUX_SIZE);
|
|
if (e) {
|
|
dphdcp_err("Error reading over AUX\n");
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
/* copy the content to data */
|
|
memcpy(data, orig_buf, size);
|
|
|
|
error:
|
|
kfree(orig_buf);
|
|
return e;
|
|
}
|
|
|
|
static int get_rxstatus(struct tegra_dc_dp_data *dp, u8 *rxstatus)
|
|
{
|
|
u32 status;
|
|
|
|
return tegra_dphdcp_read(dp, NV_DPCD_HDCP_RXSTATUS,
|
|
rxstatus, 1, &status);
|
|
}
|
|
|
|
static int dphdcp_ake_init_rtx_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
/* write 8 bytes of rtx */
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_RTX_OFFSET, buf, 8);
|
|
}
|
|
|
|
static int dphdcp_ake_init_txcaps_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
/* write 3 bytes of txcaps */
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_TXCAPS_OFFSET, buf, 3);
|
|
}
|
|
|
|
static int dphdcp_ake_cert_recv_rx(struct tegra_dc_dp_data *dp, u8 *buf)
|
|
{
|
|
/* read 522 bytes of receiver certificate */
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_CERT_RX_OFFSET,
|
|
(u64 *)buf, 522);
|
|
}
|
|
|
|
static int dphdcp_ake_cert_recv_rrx(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
/* write 8 bytes of pseudo random number */
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_CERT_RRX_OFFSET,
|
|
buf, 8);
|
|
}
|
|
|
|
static int dphdcp_ake_cert_recv_rxcaps(struct tegra_dc_dp_data *dp, u16 *buf)
|
|
{
|
|
/* write 3 bytes of receiver capability */
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_CERT_RXCAPS_OFFSET,
|
|
(u64 *)buf, 3);
|
|
}
|
|
|
|
static int dphdcp_ake_no_stored_km_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_EKM_NOSTORED, buf, 128);
|
|
}
|
|
|
|
static int dphdcp_ake_hprime_receive(struct tegra_dc_dp_data *dp, u32 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_HPRIME,
|
|
(u64 *)buf, 32);
|
|
}
|
|
|
|
static int dphdcp_lc_init_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_RN, buf, 8);
|
|
}
|
|
|
|
static int dphdcp_ake_pairing_info_receive(struct tegra_dc_dp_data *dp,
|
|
u64 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_LPRIME,
|
|
buf, 16);
|
|
}
|
|
|
|
static int dphdcp_lc_lprime_receive(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_LPRIME,
|
|
buf, 32);
|
|
}
|
|
|
|
static int dphdcp_ske_eks_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_EKS, buf, 16);
|
|
}
|
|
|
|
static int dphdcp_ske_riv_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_RIV, buf, 8);
|
|
}
|
|
|
|
static int dphdcp_rxinfo_recv(struct tegra_dc_dp_data *dp, u16 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_RXINFO, (u64 *)buf, 2);
|
|
}
|
|
|
|
static int dphdcp_seqnum_recv(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_SEQNUM_V, buf, 3);
|
|
}
|
|
|
|
static int dphdcp_vprime_recv(struct tegra_dc_dp_data *dp, u16 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_VPRIME, (u64 *)buf, 16);
|
|
}
|
|
|
|
static int dphdcp_recvrid_list_recv(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_RX_ID_LIST, buf, 635);
|
|
}
|
|
|
|
static int dphdcp_rptr_ack_send(struct tegra_dc_dp_data *dp, u64 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_V, buf, 16);
|
|
}
|
|
|
|
static int dphdcp_rptr_seqnum_send(struct tegra_dc_dp_data *dp, u8 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_SEQ_NUM_M,
|
|
(u64 *)buf, 3);
|
|
}
|
|
|
|
static int dphdcp_rptr_k_send(struct tegra_dc_dp_data *dp, u16 *buf)
|
|
{
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_K,
|
|
(u64 *)&buf, 2);
|
|
}
|
|
|
|
static int dphdcp_rptr_strmid_type_send(struct tegra_dc_dp_data *dp, u16 *buf)
|
|
{
|
|
/* max streams: 1 */
|
|
return tegra_dphdcp_write_n(dp, NV_DPCD_HDCP_STRMID_TYPE,
|
|
(u64 *)buf, 2);
|
|
}
|
|
|
|
static int dphdcp_rptr_stream_ready_recv(struct tegra_dc_dp_data *dp, u8 *buf)
|
|
{
|
|
return tegra_dphdcp_read_n(dp, NV_DPCD_HDCP_MPRIME, (u64 *)buf, 32);
|
|
}
|
|
|
|
/* poll for status until timeout */
|
|
static int dphdcp_poll(struct tegra_dc_dp_data *dp, int timeout, int status)
|
|
{
|
|
int e;
|
|
s64 start_time;
|
|
s64 end_time;
|
|
struct timespec tm;
|
|
u8 val;
|
|
u32 aux_stat;
|
|
|
|
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 >= timeout*1000)
|
|
return -ETIMEDOUT;
|
|
e = tegra_dphdcp_read(dp,
|
|
NV_DPCD_HDCP_RXSTATUS, &val, 1, &aux_stat);
|
|
if (e) {
|
|
dphdcp_err("dphdcp_poll_ready failed\n");
|
|
goto exit;
|
|
}
|
|
if (status == HDCP_READY) {
|
|
if (val)
|
|
break;
|
|
} else if (status == HDCP_REAUTH) {
|
|
if (cpu_to_be16(val) & HDCP_REAUTH_MASK)
|
|
break;
|
|
}
|
|
}
|
|
e = 0;
|
|
exit:
|
|
return e;
|
|
}
|
|
|
|
static int dphdcp_poll_ready(struct tegra_dc_dp_data *dp,
|
|
int timeout)
|
|
{
|
|
int e;
|
|
|
|
e = dphdcp_poll(dp, timeout, HDCP_READY);
|
|
return e;
|
|
}
|
|
|
|
static int tsec_hdcp_authentication(struct tegra_dc_dp_data *dp,
|
|
struct hdcp_context_t *hdcp_context)
|
|
{
|
|
int err = 0;
|
|
u8 version = 0x02;
|
|
u16 caps = 0;
|
|
u16 txcaps = 0x0;
|
|
u64 txval;
|
|
u32 *buf;
|
|
|
|
err = tsec_hdcp_readcaps(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_init(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
/* rtx populated in hdcp create session */
|
|
err = tsec_hdcp_create_session(hdcp_context, DISPLAY_TYPE_DP,
|
|
dphdcp->dp->sor->ctrl_num);
|
|
if (err)
|
|
goto exit;
|
|
err = tsec_hdcp_exchange_info(hdcp_context,
|
|
HDCP_EXCHANGE_INFO_GET_TMTR_INFO, &version, &caps);
|
|
if (err)
|
|
goto exit;
|
|
hdcp_context->msg.txcaps_version = version;
|
|
hdcp_context->msg.txcaps_capmask = txcaps;
|
|
/* initiate authentication with 64 bit pseudo random # */
|
|
err = dphdcp_ake_init_rtx_send(dp, &hdcp_context->msg.rtx);
|
|
if (err)
|
|
goto exit;
|
|
txval = (version << 16) | txcaps;
|
|
/* write the txcaps register */
|
|
err = dphdcp_ake_init_txcaps_send(dp, &txval);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* wait for 100 ms before reading the RX */
|
|
err = dphdcp_poll_ready(dp, 100);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* process upon CP_IRQ interrupt */
|
|
/* read the certificate from the rx */
|
|
err = dphdcp_ake_cert_recv_rx(dp, hdcp_context->msg.cert_rx);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_ake_cert_recv_rrx(dp, &hdcp_context->msg.rrx);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_ake_cert_recv_rxcaps(dp,
|
|
&hdcp_context->msg.rxcaps_capmask);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* verify received certificate */
|
|
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;
|
|
|
|
err = dphdcp_ake_no_stored_km_send(dp, (u64 *)hdcp_context->msg.ekm);
|
|
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;
|
|
|
|
/* device in revocation list ? */
|
|
err = tsec_hdcp_revocation_check(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
/* H' should be ready within 1 sec */
|
|
err = dphdcp_poll_ready(dp, 1000);
|
|
if (err)
|
|
goto exit;
|
|
buf = (u32 *)hdcp_context->msg.hprime;
|
|
err = dphdcp_ake_hprime_receive(dp, buf);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_verify_hprime(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
/* wait for AKE pairing message to be ready */
|
|
err = dphdcp_poll_ready(dp, 200);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_ake_pairing_info_receive(dp,
|
|
(u64 *)hdcp_context->msg.ekhkm);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_encrypt_pairing_info(hdcp_context);
|
|
if (err)
|
|
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;
|
|
|
|
err = dphdcp_lc_init_send(dp, &hdcp_context->msg.rn);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_poll_ready(dp, 7);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_lc_lprime_receive(dp, (u64 *)hdcp_context->msg.lprime);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_verify_lprime(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_ske_init(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_ske_eks_send(dp, (u64 *)hdcp_context->msg.eks);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_ske_riv_send(dp, (u64 *)hdcp_context->msg.riv);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* check if the receiver is a repeater */
|
|
if (hdcp_context->msg.rxcaps_capmask & HDCP_22_REPEATER) {
|
|
dp->dphdcp->repeater = 1;
|
|
err = dphdcp_poll_ready(dp, 3000);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* read receiver id list */
|
|
err = dphdcp_rxinfo_recv(dp, &hdcp_context->msg.rxinfo);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_seqnum_recv(dp, (u64 *)hdcp_context->msg.seq_num);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_vprime_recv(dp, &hdcp_context->msg.rxinfo);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_recvrid_list_recv(dp,
|
|
(u64 *)&hdcp_context->msg.rxinfo);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_verify_vprime(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* send ack for repeater auth */
|
|
err = dphdcp_rptr_ack_send(dp, (u64 *)hdcp_context->msg.v);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = tsec_hdcp_rptr_stream_manage(hdcp_context);
|
|
if (err)
|
|
goto exit;
|
|
/* One stream */
|
|
hdcp_context->msg.k = 0x0100;
|
|
/* STREAM_ID and Type are 0 */
|
|
hdcp_context->msg.streamid_type[0] = 0x0000;
|
|
/* stream management information */
|
|
err = dphdcp_rptr_seqnum_send(dp,
|
|
hdcp_context->msg.seq_num_m);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_rptr_k_send(dp,
|
|
&hdcp_context->msg.k);
|
|
if (err)
|
|
goto exit;
|
|
|
|
err = dphdcp_rptr_strmid_type_send(dp,
|
|
hdcp_context->msg.streamid_type);
|
|
if (err)
|
|
goto exit;
|
|
|
|
/* repeater auth stream ready information */
|
|
err = dphdcp_rptr_stream_ready_recv(dp,
|
|
hdcp_context->msg.mprime);
|
|
if (err)
|
|
goto exit;
|
|
|
|
}
|
|
dphdcp_info("HDCP authentication successful\n");
|
|
exit:
|
|
if (err)
|
|
dphdcp_err("HDCP authentication failed with err %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
static void dphdcp2_downstream_worker(struct work_struct *work)
|
|
{
|
|
struct tegra_dphdcp *dphdcp =
|
|
container_of(to_delayed_work(work),
|
|
struct tegra_dphdcp, work);
|
|
struct tegra_dc_dp_data *dp = dphdcp->dp;
|
|
struct hdcp_context_t hdcp_context;
|
|
struct tegra_dc *dc = dp->dc;
|
|
int e;
|
|
u8 rxstatus;
|
|
|
|
e = tsec_hdcp_create_context(&hdcp_context);
|
|
if (e)
|
|
goto err;
|
|
|
|
dphdcp_vdbg("%s():started thread %s\n", __func__, dphdcp->name);
|
|
tegra_dc_io_start(dc);
|
|
|
|
mutex_lock(&dphdcp->lock);
|
|
if (dphdcp->state == STATE_OFF) {
|
|
dphdcp_err("dphdcp failure: giving up\n");
|
|
goto err;
|
|
}
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
/* check plug state to terminate early in case of flush_workqueue */
|
|
if (!dphdcp_is_plugged(dphdcp)) {
|
|
dphdcp_err("worker started in unplugged state\n");
|
|
goto lost_dp;
|
|
}
|
|
dphdcp_vdbg("%s():hpd=%d\n", __func__, dphdcp->plugged);
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
if (tsec_hdcp_authentication(dp, &hdcp_context)) {
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp->state = STATE_LINK_VERIFY;
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
e = tsec_hdcp_session_ctrl(&hdcp_context,
|
|
HDCP_SESSION_CTRL_FLAG_ACTIVATE);
|
|
if (e) {
|
|
dphdcp_err("tsec hdcp session ctrl failed\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
dphdcp_info("HDCP 2.2 CRYPT enabled\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
while (1) {
|
|
if (!dphdcp_is_plugged(dphdcp))
|
|
goto lost_dp;
|
|
|
|
if (dphdcp->state != STATE_LINK_VERIFY)
|
|
goto failure;
|
|
|
|
mutex_unlock(&dphdcp->lock);
|
|
|
|
/* link integrity check */
|
|
e = get_rxstatus(dp, &rxstatus);
|
|
if (!e && (rxstatus & HDCP_LINK_INTEG_FAIL)) {
|
|
dphdcp_err("link integrity failure\n");
|
|
mutex_lock(&dphdcp->lock);
|
|
goto failure;
|
|
}
|
|
tegra_dc_io_end(dc);
|
|
wait_event_interruptible_timeout(wq_worker,
|
|
!dphdcp_is_plugged(dphdcp), msecs_to_jiffies(200));
|
|
tegra_dc_io_start(dc);
|
|
mutex_lock(&dphdcp->lock);
|
|
}
|
|
|
|
failure:
|
|
dphdcp->fail_count++;
|
|
if (dphdcp->fail_count > dphdcp->max_retries) {
|
|
dphdcp_err("dphdcp failure - too many failures, giving up\n");
|
|
} else {
|
|
dphdcp_err("dphdcp failure - renegotiating in 1 sec\n");
|
|
if (!dphdcp_is_plugged(dphdcp))
|
|
goto lost_dp;
|
|
|
|
queue_delayed_work(dphdcp->downstream_wq, &dphdcp->work,
|
|
msecs_to_jiffies(1000));
|
|
}
|
|
|
|
lost_dp:
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
err:
|
|
mutex_unlock(&dphdcp->lock);
|
|
tegra_dc_io_end(dc);
|
|
e = tsec_hdcp_free_context(&hdcp_context);
|
|
}
|
|
#endif
|
|
|
|
static int tegra_dphdcp_on(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
u8 hdcp2version = 0;
|
|
int e;
|
|
u32 status;
|
|
struct tegra_dc_dp_data *dp = dphdcp->dp;
|
|
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
if (dphdcp_is_plugged(dphdcp) &&
|
|
atomic_read(&dphdcp->policy) !=
|
|
TEGRA_DC_HDCP_POLICY_ALWAYS_OFF) {
|
|
dphdcp->fail_count = 0;
|
|
e = tegra_dphdcp_read(dp, HDCP_HDCP2_VERSION,
|
|
&hdcp2version, 1, &status);
|
|
if (e)
|
|
return -EIO;
|
|
|
|
dphdcp_vdbg("read back version:%x\n", hdcp2version);
|
|
if (hdcp2version & HDCP_HDCP2_VERSION_HDCP22_YES) {
|
|
#ifdef DPHDCP22
|
|
INIT_DELAYED_WORK(&dphdcp->work,
|
|
dphdcp2_downstream_worker);
|
|
#endif
|
|
dphdcp->hdcp22 = HDCP22_PROTOCOL;
|
|
} else {
|
|
INIT_DELAYED_WORK(&dphdcp->work,
|
|
dphdcp_downstream_worker);
|
|
dphdcp->hdcp22 = HDCP1X_PROTOCOL;
|
|
}
|
|
|
|
queue_delayed_work(dphdcp->downstream_wq, &dphdcp->work,
|
|
msecs_to_jiffies(100));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dphdcp_off(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp->state = STATE_OFF;
|
|
dphdcp_set_plugged(dphdcp, false);
|
|
mutex_unlock(&dphdcp->lock);
|
|
wake_up_interruptible(&wq_worker);
|
|
cancel_delayed_work_sync(&dphdcp->work);
|
|
return 0;
|
|
}
|
|
|
|
static int get_dphdcp_state(struct tegra_dphdcp *dphdcp,
|
|
struct tegra_nvhdcp_packet *pkt)
|
|
{
|
|
int i;
|
|
|
|
mutex_lock(&dphdcp->lock);
|
|
if (dphdcp->state != STATE_LINK_VERIFY) {
|
|
memset(pkt, 0, sizeof(struct tegra_nvhdcp_packet));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_LINK_FAILED;
|
|
} else {
|
|
pkt->num_bksv_list = dphdcp->num_bksv_list;
|
|
for (i = 0; i < pkt->num_bksv_list; i++)
|
|
pkt->bksv_list[i] = dphdcp->bksv_list[i];
|
|
pkt->binfo = dphdcp->binfo;
|
|
pkt->b_ksv = dphdcp->b_ksv;
|
|
memcpy(pkt->v_prime, dphdcp->v_prime, sizeof(dphdcp->v_prime));
|
|
pkt->packet_results = TEGRA_NVHDCP_RESULT_SUCCESS;
|
|
pkt->hdcp22 = dphdcp->hdcp22;
|
|
pkt->port = TEGRA_NVHDCP_PORT_DP;
|
|
}
|
|
pkt->sor = dphdcp->dp->sor->ctrl_num;
|
|
mutex_unlock(&dphdcp->lock);
|
|
return 0;
|
|
}
|
|
|
|
int tegra_dphdcp_set_policy(struct tegra_dphdcp *dphdcp, int pol)
|
|
{
|
|
if (pol == TEGRA_DC_HDCP_POLICY_ALWAYS_ON) {
|
|
dphdcp_info("using \"always on\" policy.\n");
|
|
if (atomic_xchg(&dphdcp->policy, pol) != pol) {
|
|
/* policy changed, start working */
|
|
tegra_dphdcp_on(dphdcp);
|
|
}
|
|
} else if (pol == TEGRA_DC_HDCP_POLICY_ALWAYS_OFF) {
|
|
dphdcp_info("using \"always off\" policy.\n");
|
|
if (atomic_xchg(&dphdcp->policy, pol) != pol) {
|
|
/* policy changed, stop working */
|
|
tegra_dphdcp_off(dphdcp);
|
|
}
|
|
} else {
|
|
/* unsupported policy */
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_dphdcp_renegotiate(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp->state = STATE_RENEGOTIATE;
|
|
mutex_unlock(&dphdcp->lock);
|
|
tegra_dphdcp_on(dphdcp);
|
|
return 0;
|
|
}
|
|
|
|
void tegra_dphdcp_set_plug(struct tegra_dphdcp *dphdcp, bool hpd)
|
|
{
|
|
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 >= 0x5 have SFK */
|
|
if (ft_info < FUSE_START_SFK) {
|
|
dphdcp_err("Device does not have SFK!");
|
|
return;
|
|
}
|
|
}
|
|
/* ensure all previous values are reset on hotplug */
|
|
vprime_check_done = false;
|
|
repeater_flag = false;
|
|
dphdcp_debug("DP hotplug detected (hpd = %d)\n", hpd);
|
|
if (hpd) {
|
|
dphdcp_set_plugged(dphdcp, true);
|
|
tegra_dphdcp_on(dphdcp);
|
|
} else {
|
|
tegra_dphdcp_off(dphdcp);
|
|
}
|
|
}
|
|
|
|
static long dphdcp_dev_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct tegra_dphdcp *dphdcp;
|
|
struct tegra_nvhdcp_packet *pkt;
|
|
struct tegra_dc_dp_data *dp;
|
|
int e = -ENOTTY;
|
|
|
|
if (!filp)
|
|
return -EINVAL;
|
|
|
|
dphdcp = filp->private_data;
|
|
dp = dphdcp->dp;
|
|
|
|
switch (cmd) {
|
|
case TEGRAIO_NVHDCP_ON:
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp_set_plugged(dphdcp, dp->enabled);
|
|
mutex_unlock(&dphdcp->lock);
|
|
return tegra_dphdcp_on(dphdcp);
|
|
|
|
case TEGRAIO_NVHDCP_OFF:
|
|
return tegra_dphdcp_off(dphdcp);
|
|
|
|
case TEGRAIO_NVHDCP_SET_POLICY:
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp_set_plugged(dphdcp, dp->enabled);
|
|
mutex_unlock(&dphdcp->lock);
|
|
return tegra_dphdcp_set_policy(dphdcp, arg);
|
|
|
|
case TEGRAIO_NVHDCP_RENEGOTIATE:
|
|
mutex_lock(&dphdcp->lock);
|
|
dphdcp_set_plugged(dphdcp, dp->enabled);
|
|
mutex_unlock(&dphdcp->lock);
|
|
e = tegra_dphdcp_renegotiate(dphdcp);
|
|
break;
|
|
|
|
case TEGRAIO_NVHDCP_HDCP_STATE:
|
|
pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
|
|
if (!pkt) {
|
|
kfree(pkt);
|
|
return -ENOMEM;
|
|
}
|
|
e = get_dphdcp_state(dphdcp, pkt);
|
|
if (copy_to_user((void __user *)arg, pkt, sizeof(*pkt))) {
|
|
kfree(pkt);
|
|
return -EFAULT;
|
|
}
|
|
kfree(pkt);
|
|
return e;
|
|
|
|
}
|
|
return e;
|
|
}
|
|
|
|
static int dphdcp_dev_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct miscdevice *miscdev = filp->private_data;
|
|
struct tegra_dphdcp *dphdcp =
|
|
container_of(miscdev, struct tegra_dphdcp, miscdev);
|
|
filp->private_data = dphdcp;
|
|
return 0;
|
|
}
|
|
|
|
static int dphdcp_dev_release(struct inode *inode, struct file *filp)
|
|
{
|
|
filp->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations dphdcp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = dphdcp_dev_ioctl,
|
|
.open = dphdcp_dev_open,
|
|
.release = dphdcp_dev_release,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = dphdcp_dev_ioctl,
|
|
#endif
|
|
};
|
|
/* we only support one AP right now, so should only call this once. */
|
|
struct tegra_dphdcp *tegra_dphdcp_create(struct tegra_dc_dp_data *dp,
|
|
int id, int bus)
|
|
{
|
|
struct tegra_dphdcp *dphdcp;
|
|
int e;
|
|
int num_heads;
|
|
|
|
num_heads = tegra_dc_get_numof_dispheads();
|
|
|
|
if (id >= num_heads) {
|
|
dphdcp_err("head id greater than what's available!");
|
|
return ERR_PTR(-EMFILE);
|
|
}
|
|
|
|
/* ensure memory allocated once */
|
|
if (!dphdcp_head) {
|
|
dphdcp_head =
|
|
kzalloc(sizeof(void *) * num_heads, GFP_KERNEL);
|
|
if (!dphdcp_head)
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
dphdcp = dphdcp_head[id];
|
|
/* do not allow multiple node creation */
|
|
if (dphdcp)
|
|
return ERR_PTR(-EMFILE);
|
|
|
|
dphdcp = kzalloc(sizeof(*dphdcp), GFP_KERNEL);
|
|
if (!dphdcp)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
dphdcp->id = id;
|
|
snprintf(dphdcp->name, sizeof(dphdcp->name), "nvhdcp%u", id);
|
|
dphdcp->dp = dp;
|
|
mutex_init(&dphdcp->lock);
|
|
|
|
strlcpy(dphdcp->info.type, dphdcp->name, sizeof(dphdcp->info.type));
|
|
dphdcp->bus = bus;
|
|
dphdcp->fail_count = 0;
|
|
dphdcp->max_retries = HDCP_MAX_RETRIES;
|
|
dphdcp->hpd = 0;
|
|
atomic_set(&dphdcp->policy, dp->dc->pdata->default_out->hdcp_policy);
|
|
|
|
dphdcp->state = STATE_UNAUTHENTICATED;
|
|
|
|
dphdcp->downstream_wq = create_singlethread_workqueue(dphdcp->name);
|
|
|
|
dphdcp->miscdev.minor = MISC_DYNAMIC_MINOR;
|
|
dphdcp->miscdev.name = dphdcp->name;
|
|
dphdcp->miscdev.fops = &dphdcp_fops;
|
|
/* register miscellaneous device */
|
|
e = misc_register(&dphdcp->miscdev);
|
|
if (e) {
|
|
dphdcp_err("cannot register\n");
|
|
goto free_workqueue;
|
|
}
|
|
|
|
dphdcp_head[id] = dphdcp;
|
|
dphdcp_vdbg("%s(): created misc device %s\n", __func__, dphdcp->name);
|
|
|
|
return dphdcp;
|
|
|
|
free_workqueue:
|
|
destroy_workqueue(dphdcp->downstream_wq);
|
|
return ERR_PTR(e);
|
|
}
|
|
|
|
void tegra_dphdcp_destroy(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
misc_deregister(&dphdcp->miscdev);
|
|
tegra_dphdcp_off(dphdcp);
|
|
destroy_workqueue(dphdcp->downstream_wq);
|
|
dphdcp_head[dphdcp->id] = NULL;
|
|
kfree(dphdcp);
|
|
}
|
|
|
|
#ifdef CONFIG_TEGRA_DEBUG_DP_HDCP
|
|
/* show current maximum number of retries for HDCP DP authentication */
|
|
static int tegra_dp_max_retries_dbg_show(struct seq_file *m, void *unused)
|
|
{
|
|
struct tegra_dphdcp *dphdcp = m->private;
|
|
|
|
if (WARN_ON(!dphdcp))
|
|
return -EINVAL;
|
|
|
|
seq_printf(m, "hdcp max_retries value: %d\n", dphdcp->max_retries);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* show current hotplug state */
|
|
static int tegra_dp_hotplug_dbg_show(struct seq_file *m, void *unused)
|
|
{
|
|
struct tegra_dphdcp *dphdcp = m->private;
|
|
|
|
if (WARN_ON(!dphdcp))
|
|
return -EINVAL;
|
|
|
|
seq_printf(m, "hotplug value set to: %d\n", dphdcp->hpd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* sw control for hdcp max retries.
|
|
* 5 is the normal number of max retries.
|
|
* 1 is the minimum number of retries.
|
|
* 5 is the maximum number of retries.
|
|
* sw should keep the number of retries to 5 until unless a change is required
|
|
*/
|
|
static ssize_t tegra_dp_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_dphdcp *dphdcp = m->private;
|
|
u8 new_max_retries;
|
|
int ret;
|
|
|
|
if (WARN_ON(!dphdcp))
|
|
return -EINVAL;
|
|
|
|
ret = kstrtou8_from_user(addr, len, 6, &new_max_retries);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (new_max_retries >= HDCP_MIN_RETRIES &&
|
|
new_max_retries <= HDCP_MAX_RETRIES)
|
|
dphdcp->max_retries = new_max_retries;
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* sw control for hotplug on and off
|
|
* 1: turn hotplug on
|
|
* 0: turn hotplug off
|
|
*/
|
|
static ssize_t tegra_dp_hotplug_dbg_write(struct file *file,
|
|
const char __user *addr,
|
|
size_t len, loff_t *pos)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct tegra_dphdcp *dphdcp = m->private;
|
|
u8 new_hpd;
|
|
int ret;
|
|
|
|
if (WARN_ON(!dphdcp))
|
|
return -EINVAL;
|
|
|
|
ret = kstrtou8_from_user(addr, len, 6, &new_hpd);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (new_hpd > 0) {
|
|
/* start downstream_worker */
|
|
dphdcp_set_plugged(dphdcp, true);
|
|
tegra_dphdcp_on(dphdcp);
|
|
dphdcp->hpd = new_hpd;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int tegra_dp_max_retries_dbg_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
if (!inode || !file)
|
|
return -EINVAL;
|
|
|
|
return single_open(file, tegra_dp_max_retries_dbg_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static int tegra_dp_hotplug_dbg_open(struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
if (!inode || !file)
|
|
return -EINVAL;
|
|
|
|
return single_open(file, tegra_dp_hotplug_dbg_show,
|
|
inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations tegra_dp_max_retries_dbg_ops = {
|
|
.open = tegra_dp_max_retries_dbg_open,
|
|
.read = seq_read,
|
|
.write = tegra_dp_max_retries_dbg_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static const struct file_operations tegra_dp_hotplug_dbg_ops = {
|
|
.open = tegra_dp_hotplug_dbg_open,
|
|
.read = seq_read,
|
|
.write = tegra_dp_hotplug_dbg_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
void tegra_dphdcp_debugfs_init(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
struct dentry *dir, *ret;
|
|
|
|
if (!dphdcp)
|
|
goto fail;
|
|
|
|
dir = debugfs_create_dir("tegra_dphdcp", NULL);
|
|
|
|
ret = debugfs_create_file("max_retries", 0444, dir,
|
|
dphdcp, &tegra_dp_max_retries_dbg_ops);
|
|
if (IS_ERR_OR_NULL(ret))
|
|
goto fail;
|
|
|
|
ret = debugfs_create_file("hotplug", 0444, dir,
|
|
dphdcp, &tegra_dp_hotplug_dbg_ops);
|
|
if (IS_ERR_OR_NULL(ret))
|
|
goto fail;
|
|
|
|
fail:
|
|
debugfs_remove_recursive(dir);
|
|
}
|
|
#else
|
|
void tegra_dphdcp_debugfs_init(struct tegra_dphdcp *dphdcp)
|
|
{
|
|
}
|
|
#endif
|