blob: d7d6abf5d13181afa2c52938041773f1d1a23073 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Samsung DisplayPort driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/soc/samsung/exynos-smc.h>
#include <linux/slab.h>
#include <linux/trusty/trusty_ipc.h>
#include <linux/module.h>
#include <asm/cacheflush.h>
#include <linux/dma-mapping.h>
#include <drm/drm_mode.h>
#include "dpcd.h"
#include "teeif.h"
#include "hdcp-log.h"
#define TZ_CON_TIMEOUT 5000
#define TZ_BUF_TIMEOUT 10000
#define TZ_MSG_TIMEOUT 10000
#define HDCP_TA_PORT "com.android.trusty.hdcp.auth"
#define HDCP_V2_3 (5)
#define HDCP_V1 (1)
#define HDCP_NONE (0)
struct hdcp_auth_req {
uint32_t cmd;
int32_t arg;
};
struct hdcp_auth_rsp {
uint32_t cmd;
int32_t err;
int32_t arg;
};
struct hdcp_tz_chan_ctx {
struct tipc_chan *chan;
struct mutex rsp_lock;
struct completion reply_comp;
struct hdcp_auth_rsp rsp;
struct hci_message **msg_ptr;
};
static struct hdcp_tz_chan_ctx hdcp_ta_ctx;
static size_t deduce_num_payload(size_t msg_len)
{
switch (msg_len) {
case (sizeof(struct hdcp_auth_rsp)):
return 1;
case (sizeof(struct hdcp_auth_rsp) + sizeof(struct hci_message)):
return 2;
default:
return 0;
}
}
static struct tipc_msg_buf *tz_srv_handle_msg(void *data,
struct tipc_msg_buf* rxbuf)
{
struct hdcp_tz_chan_ctx *ctx = data;
size_t len;
size_t payload_num;
len = mb_avail_data(rxbuf);
payload_num = deduce_num_payload(len);
if (payload_num == 0) {
hdcp_err("TZ: invalid RSP buffer size (%zd)\n", len);
}
if (payload_num > 0) {
memcpy(&ctx->rsp,
mb_get_data(rxbuf, sizeof(struct hdcp_auth_rsp)),
sizeof(struct hdcp_auth_rsp));
}
if (payload_num > 1 && ctx->msg_ptr) {
memcpy(*ctx->msg_ptr,
mb_get_data(rxbuf, sizeof(struct hci_message)),
sizeof(struct hci_message));
}
complete(&ctx->reply_comp);
return rxbuf;
}
static void tz_srv_handle_event(void *data, int event)
{
struct hdcp_tz_chan_ctx *ctx = data;
complete(&ctx->reply_comp);
}
static const struct tipc_chan_ops tz_srv_ops = {
.handle_msg = tz_srv_handle_msg,
.handle_event = tz_srv_handle_event,
};
void hdcp_tee_init(void)
{
init_completion(&hdcp_ta_ctx.reply_comp);
mutex_init(&hdcp_ta_ctx.rsp_lock);
hdcp_ta_ctx.msg_ptr = NULL;
}
int hdcp_tee_open(void)
{
int ret = 0;
struct tipc_chan *chan;
if (hdcp_ta_ctx.chan) {
hdcp_debug("HCI is already connected\n");
return 0;
}
chan = tipc_create_channel(NULL, &tz_srv_ops, &hdcp_ta_ctx);
if (IS_ERR(chan)) {
hdcp_err("TZ: failed (%ld) to create chan\n", PTR_ERR(chan));
return PTR_ERR(chan);
}
reinit_completion(&hdcp_ta_ctx.reply_comp);
ret = tipc_chan_connect(chan, HDCP_TA_PORT);
if (ret < 0) {
hdcp_err("TZ: failed (%d) to connect\n", ret);
tipc_chan_destroy(chan);
return ret;
}
hdcp_ta_ctx.chan = chan;
ret = wait_for_completion_timeout(&hdcp_ta_ctx.reply_comp,
msecs_to_jiffies(TZ_CON_TIMEOUT));
if (ret <= 0) {
ret = (!ret) ? -ETIMEDOUT : ret;
hdcp_err("TZ: failed (%d) to wait for connect\n", ret);
hdcp_tee_close();
return ret;
}
return 0;
}
int hdcp_tee_close(void)
{
if (!hdcp_ta_ctx.chan) {
hdcp_info("HCI is already disconnected\n");
return 0;
}
tipc_chan_shutdown(hdcp_ta_ctx.chan);
tipc_chan_destroy(hdcp_ta_ctx.chan);
hdcp_ta_ctx.chan = NULL;
return 0;
}
static int hdcp_tee_comm_xchg_internal(uint32_t cmd, int32_t arg,
int32_t *rsp, struct hci_message *hci)
{
int ret;
struct tipc_msg_buf *txbuf;
struct hdcp_auth_req auth_req;
ret = hdcp_tee_open();
if (ret)
return ret;
txbuf = tipc_chan_get_txbuf_timeout(hdcp_ta_ctx.chan, TZ_BUF_TIMEOUT);
if (IS_ERR(txbuf)) {
hdcp_err("TZ: failed (%ld) to get txbuf\n", PTR_ERR(txbuf));
return PTR_ERR(txbuf);
}
auth_req.cmd = cmd;
auth_req.arg = arg;
memcpy(mb_put_data(txbuf, sizeof(struct hdcp_auth_req)),
&auth_req, sizeof(struct hdcp_auth_req));
if (hci) {
memcpy(mb_put_data(txbuf, sizeof(struct hci_message)),
hci, sizeof(struct hci_message));
hdcp_ta_ctx.msg_ptr = &hci;
} else {
hdcp_ta_ctx.msg_ptr = NULL;
}
reinit_completion(&hdcp_ta_ctx.reply_comp);
ret = tipc_chan_queue_msg(hdcp_ta_ctx.chan, txbuf);
if (ret < 0) {
hdcp_err("TZ: failed(%d) to queue msg\n", ret);
tipc_chan_put_txbuf(hdcp_ta_ctx.chan, txbuf);
hdcp_tee_close();
return ret;
}
ret = wait_for_completion_timeout(&hdcp_ta_ctx.reply_comp,
msecs_to_jiffies(TZ_MSG_TIMEOUT));
if (ret <= 0) {
ret = (!ret) ? -ETIMEDOUT : ret;
hdcp_err("TZ: failed (%d) to wait for reply\n", ret);
hdcp_tee_close();
return ret;
}
if (hdcp_ta_ctx.rsp.cmd != (cmd | HDCP_CMD_AUTH_RESP)) {
hdcp_err("TZ: hdcp had an unexpected rsp cmd (%x vs %x)",
hdcp_ta_ctx.rsp.cmd, cmd | HDCP_CMD_AUTH_RESP);
return -EIO;
}
if (hdcp_ta_ctx.rsp.err) {
hdcp_err("TZ: hdcp had an unexpected rsp err (%d)",
hdcp_ta_ctx.rsp.err);
return -EIO;
}
if (rsp)
*rsp = hdcp_ta_ctx.rsp.arg;
return 0;
}
static int hdcp_tee_comm_xchg(uint32_t cmd, int32_t arg, int32_t *rsp,
struct hci_message *hci) {
int ret;
int retries = 2;
mutex_lock(&hdcp_ta_ctx.rsp_lock);
while (retries) {
retries--;
ret = hdcp_tee_comm_xchg_internal(cmd, arg, rsp, hci);
if (!ret) {
mutex_unlock(&hdcp_ta_ctx.rsp_lock);
return 0;
}
}
mutex_unlock(&hdcp_ta_ctx.rsp_lock);
return ret;
}
int hdcp_tee_send_cmd(uint32_t cmd) {
return hdcp_tee_comm_xchg(cmd, 0, NULL, NULL);
}
int hdcp_tee_enable_enc_22(void) {
int ret = hdcp_tee_comm_xchg(HDCP_CMD_ENCRYPTION_SET, HDCP_V2_3, NULL,
NULL);
if (ret)
return ret;
hdcp_dplink_update_cp(DRM_MODE_CONTENT_PROTECTION_ENABLED);
return 0;
}
int hdcp_tee_enable_enc_13(void) {
int ret = hdcp_tee_comm_xchg(HDCP_CMD_ENCRYPTION_SET, HDCP_V1, NULL,
NULL);
if (ret)
return ret;
hdcp_dplink_update_cp(DRM_MODE_CONTENT_PROTECTION_ENABLED);
return 0;
}
int hdcp_tee_disable_enc(void) {
hdcp_dplink_update_cp(DRM_MODE_CONTENT_PROTECTION_DESIRED);
return hdcp_tee_comm_xchg(HDCP_CMD_ENCRYPTION_SET, HDCP_NONE, NULL, NULL);
}
int hdcp_tee_check_protection(int* version) {
return hdcp_tee_comm_xchg(HDCP_CMD_ENCRYPTION_GET, 0, version, NULL);
}
int hdcp_tee_set_test_mode(bool enable) {
return hdcp_tee_comm_xchg(HDCP_CMD_SET_TEST_MODE, enable, NULL, NULL);
}
int hdcp_tee_connect_info(int connect_info) {
return hdcp_tee_comm_xchg(HDCP_CMD_CONNECT_INFO, connect_info, NULL, NULL);
}
static int hdcp_tee_comm(struct hci_message *hci) {
return hdcp_tee_comm_xchg(HDCP_CMD_PROTOCOL, 0, NULL, hci);
}
int teei_gen_rtx(uint32_t lk_type,
uint8_t *rtx, size_t rtx_len,
uint8_t *caps, uint32_t caps_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_RTX;
hci->genrtx.lk_type = lk_type;
hci->genrtx.len = rtx_len;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
/* check returned message from SWD */
if (rtx && rtx_len)
memcpy(rtx, hci->genrtx.rtx, rtx_len);
if (caps && caps_len)
memcpy(caps, hci->genrtx.tx_caps, caps_len);
return ret;
}
int teei_verify_cert(uint8_t *cert, size_t cert_len,
uint8_t *rrx, size_t rrx_len,
uint8_t *rx_caps, size_t rx_caps_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_VERIFY_CERT;
hci->vfcert.len = cert_len;
memcpy(hci->vfcert.cert, cert, cert_len);
if (rrx && rrx_len)
memcpy(hci->vfcert.rrx, rrx, rrx_len);
if (rx_caps && rx_caps_len)
memcpy(hci->vfcert.rx_caps, rx_caps, rx_caps_len);
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
/* return verification result */
return ret;
}
int teei_generate_master_key(uint32_t lk_type, uint8_t *emkey, size_t emkey_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_MKEY;
hci->genmkey.lk_type = lk_type;
hci->genmkey.emkey_len = emkey_len;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
/* copy encrypted mkey & wrapped mkey to hdcp ctx */
memcpy(emkey, hci->genmkey.emkey, hci->genmkey.emkey_len);
/* check returned message from SWD */
return 0;
}
int teei_compare_ake_hmac(uint8_t *rx_hmac, size_t rx_hmac_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_COMPARE_AKE_HMAC;
hci->comphmac.rx_hmac_len = rx_hmac_len;
memcpy(hci->comphmac.rx_hmac, rx_hmac, rx_hmac_len);
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
return ret;
}
int teei_set_pairing_info(uint8_t *ekh_mkey, size_t ekh_mkey_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_SET_PAIRING_INFO;
memcpy(hci->setpairing.ekh_mkey, ekh_mkey, ekh_mkey_len);
/* send command to swd */
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
return ret;
}
int teei_get_pairing_info(uint8_t *ekh_mkey, size_t ekh_mkey_len,
uint8_t *m, size_t m_len,
int *found)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GET_PAIRING_INFO;
/* send command to swd */
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
memcpy(ekh_mkey, hci->getpairing.ekh_mkey, ekh_mkey_len);
memcpy(m, hci->getpairing.m, m_len);
*found = hci->getpairing.found ? 1 : 0;
return ret;
}
int teei_gen_rn(uint8_t *out, size_t len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_RN;
hci->genrn.len = len;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
/* check returned message from SWD */
memcpy(out, hci->genrn.rn, len);
return ret;
}
int teei_compare_lc_hmac(uint8_t *rx_hmac, size_t rx_hmac_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_COMPARE_LC_HMAC;
hci->complchmac.rx_hmac_len = rx_hmac_len;
memcpy(hci->complchmac.rx_hmac, rx_hmac, rx_hmac_len);
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
return ret;
}
int teei_generate_riv(uint8_t *out, size_t len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_RIV;
hci->genriv.len = len;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
memcpy(out, hci->genriv.riv, len);
/* todo: check returned message from SWD */
return ret;
}
int teei_generate_skey(uint32_t lk_type,
uint8_t *eskey, size_t eskey_len,
int share_skey)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_SKEY;
hci->genskey.lk_type = lk_type;
hci->genskey.eskey_len = eskey_len;
hci->genskey.share_skey = share_skey;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
/* copy encrypted mkey & wrapped mkey to hdcp ctx */
memcpy(eskey, hci->genskey.eskey, hci->genskey.eskey_len);
/* todo: check returned message from SWD */
return 0;
}
int teei_set_rcvlist_info(uint8_t *rx_info,
uint8_t *seq_num_v,
uint8_t *v_prime,
uint8_t *rcvid_list,
uint8_t *v,
uint8_t *valid)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
hci->cmd_id = HDCP_TEEI_SET_RCV_ID_LIST;
memcpy(hci->setrcvlist.rcvid_lst, rcvid_list, HDCP_RP_RCVID_LIST_LEN);
memcpy(hci->setrcvlist.v_prime, v_prime, HDCP_RP_HMAC_V_LEN / 2);
/* Only used DP */
if (rx_info != NULL && seq_num_v != NULL) {
memcpy(hci->setrcvlist.rx_info, rx_info, HDCP_RP_RX_INFO_LEN);
memcpy(hci->setrcvlist.seq_num_v, seq_num_v, HDCP_RP_SEQ_NUM_V_LEN);
}
ret = hdcp_tee_comm(hci);
if (ret != 0) {
*valid = 0;
return ret;
}
memcpy(v, hci->setrcvlist.v, HDCP_RP_HMAC_V_LEN / 2);
*valid = 1;
return 0;
}
int teei_gen_stream_manage(uint16_t stream_num,
uint8_t *streamid,
uint8_t *seq_num_m,
uint8_t *k,
uint8_t *streamid_type)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
/* todo: input check */
/* Update TCI buffer */
hci->cmd_id = HDCP_TEEI_GEN_STREAM_MANAGE;
hci->genstrminfo.stream_num = stream_num;
memcpy(hci->genstrminfo.streamid, streamid, sizeof(uint8_t) * stream_num);
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
memcpy(seq_num_m, hci->genstrminfo.seq_num_m, HDCP_RP_SEQ_NUM_M_LEN);
memcpy(k, hci->genstrminfo.k, HDCP_RP_K_LEN);
memcpy(streamid_type, hci->genstrminfo.streamid_type, HDCP_RP_MAX_STREAMID_TYPE_LEN);
/* check returned message from SWD */
/* return verification result */
return ret;
}
int teei_verify_m_prime(uint8_t *m_prime, uint8_t *input, size_t input_len)
{
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
hci->cmd_id = HDCP_TEEI_VERIFY_M_PRIME;
memcpy(hci->verifymprime.m_prime, m_prime, HDCP_RP_HMAC_M_LEN);
if (input && input_len < sizeof(hci->verifymprime.strmsg)) {
memcpy(hci->verifymprime.strmsg, input, input_len);
hci->verifymprime.str_len = input_len;
}
ret = hdcp_tee_comm(hci);
return ret;
}
int teei_ksv_exchange(uint64_t bksv, uint32_t is_repeater, uint64_t *aksv,
uint64_t *an) {
int ret = 0;
struct hci_message msg;
struct hci_message *hci = &msg;
hci->cmd_id = HDCP_TEEI_KSV_EXCHANGE;
hci->ksvexchange.bksv = bksv;
hci->ksvexchange.is_repeater = is_repeater;
if ((ret = hdcp_tee_comm(hci)) < 0)
return ret;
*aksv = hci->ksvexchange.aksv;
*an = hci->ksvexchange.an;
return 0;
}
int teei_verify_r_prime(uint16_t rprime) {
struct hci_message msg;
struct hci_message *hci = &msg;
hci->cmd_id = HDCP_TEEI_VERIFY_R_PRIME;
hci->verifyrprime.r_prime = rprime;
return hdcp_tee_comm(hci);
}
int teei_verify_v_prime(uint16_t binfo, uint8_t *ksv, uint32_t ksv_len,
uint8_t *vprime) {
struct hci_message msg;
struct hci_message *hci = &msg;
hci->cmd_id = HDCP_TEEI_VERIFY_V_PRIME;
hci->verifyvprime.ksv_len = ksv_len;
hci->verifyvprime.binfo = binfo;
memcpy(hci->verifyvprime.ksv, ksv, ksv_len);
memcpy(hci->verifyvprime.v_prime, vprime, HDCP_SHA1_SIZE);
return hdcp_tee_comm(hci);
}