/* * drivers/soc/samsung/exynos-hdcp/dp_link/exynos-hdcp2-dplink.c * * Copyright (c) 2015 Samsung Electronics Co., Ltd. * http://www.samsung.com * * 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 #include #include #include #include #include #include #include #include "../exynos-hdcp2.h" #include "../exynos-hdcp2-log.h" #include "exynos-hdcp2-dplink.h" #include "exynos-hdcp2-dplink-if.h" #include "exynos-hdcp2-dplink-auth.h" #include "exynos-hdcp2-dplink-inter.h" #define HDCP_AUTH_RETRY_COUNT 5 #define RECVID_WAIT_RETRY_COUNT 5 #if defined(CONFIG_HDCP2_EMULATION_MODE) int dplink_emul_handler(int cmd) { /* todo: support hdcp22 emulator */ return 0; } #endif static DEFINE_MUTEX(hdcp_auth_mutex); /* current link data */ static struct hdcp_link_data *lk_data; extern uint8_t rp_ready; extern enum auth_state auth_proc_state; static struct hdcp_sess_info ss_info; static struct hdcp_link_info lk_info; int hdcp_dplink_get_rxinfo(uint8_t *status) { int ret = 0; ret = hdcp_dplink_recv(HDCP22_MSG_RXSTATUS_R, status, sizeof(uint8_t)); hdcp_info("RxStatus: %x\n", *status); return ret; } int do_dplink_auth(struct hdcp_link_info *lk_handle) { struct hdcp_session_node *ss_node; struct hdcp_link_node *lk_node; int ret = HDCP_SUCCESS; int rval = TX_AUTH_SUCCESS; uint32_t retry_recvid = 0; int hdcp_enabled = 0; /* find Session node which contains the Link */ if (!rp_ready) { ss_node = hdcp_session_list_find(lk_handle->ss_id); if (!ss_node) return HDCP_ERROR_INVALID_INPUT; lk_node = hdcp_link_list_find(lk_handle->lk_id, &ss_node->ss_data->ln); if (!lk_node) return HDCP_ERROR_INVALID_INPUT; lk_data = lk_node->lk_data; if (!lk_data) return HDCP_ERROR_INVALID_INPUT; if (lk_data->state != LINK_ST_H1_TX_LOW_VALUE_CONTENT) return HDCP_ERROR_INVALID_STATE; } /** * if Upstream Content Control Function call this API, * it changes state to ST_A0_DETERMINE_RX_HDCP_CAP automatically. * HDCP library do not check CP desire. */ if (!lk_data) { hdcp_info("DP HDCP2.2 Session is not opened.\n"); return HDCP_ERROR_INVALID_INPUT; } if (rp_ready) UPDATE_LINK_STATE(lk_data, LINK_ST_A7_VERIFY_RECEIVER_ID_LIST); else UPDATE_LINK_STATE(lk_data, LINK_ST_A0_DETERMINE_RX_HDCP_CAP); do { switch (lk_data->state) { case LINK_ST_H1_TX_LOW_VALUE_CONTENT: hdcp_dplink_config(DP_HDCP22_DISABLE); return ret; case LINK_ST_A0_DETERMINE_RX_HDCP_CAP: if (dplink_determine_rx_hdcp_cap(lk_data) < 0) { ret = HDCP_ERROR_RX_NOT_HDCP_CAPABLE; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } else UPDATE_LINK_STATE(lk_data, LINK_ST_A1_EXCHANGE_MASTER_KEY); break; case LINK_ST_A1_EXCHANGE_MASTER_KEY: rval = dplink_exchange_master_key(lk_data); if (rval == TX_AUTH_SUCCESS) { UPDATE_LINK_STATE(lk_data, LINK_ST_A2_LOCALITY_CHECK); } else { /* todo: consider connection retry */ ret = HDCP_ERROR_EXCHANGE_KM; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } break; case LINK_ST_A2_LOCALITY_CHECK: rval = dplink_locality_check(lk_data); if (rval == TX_AUTH_SUCCESS) { UPDATE_LINK_STATE(lk_data, LINK_ST_A3_EXCHANGE_SESSION_KEY); } else { /* todo: consider connection retry */ ret = HDCP_ERROR_LOCALITY_CHECK; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } break; case LINK_ST_A3_EXCHANGE_SESSION_KEY: if (dplink_exchange_session_key(lk_data) < 0) { ret = HDCP_ERROR_EXCHANGE_KS; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } else { UPDATE_LINK_STATE(lk_data, LINK_ST_A4_TEST_REPEATER); } break; case LINK_ST_A4_TEST_REPEATER: if (dplink_evaluate_repeater(lk_data) == TRUE) { /* if it is a repeater, verify Rcv ID list */ UPDATE_LINK_STATE(lk_data, LINK_ST_A6_WAIT_RECEIVER_ID_LIST); #if defined(CONFIG_HDCP2_FUNC_TEST_MODE) hdcp_enabled = 1; hdcp_info("it`s func test mode.\n"); #else hdcp_info("It`s repeater link !\n"); hdcp_enabled = 0; #endif } else { /* if it is not a repeater, complete authentication */ UPDATE_LINK_STATE(lk_data, LINK_ST_A5_AUTHENTICATED); hdcp_info("It`s Rx link !\n"); hdcp_enabled = 1; } break; case LINK_ST_A5_AUTHENTICATED: /* HDCP2.2 spec defined 200ms */ msleep(200); if (hdcp_enabled) hdcp_dplink_config(DP_HDCP22_ENABLE); /* Transmitter has completed the authentication protocol */ ret = exynos_smc(SMC_DRM_HDCP_AUTH_INFO, DP_HDCP22_ENABLE, 0, 0); return HDCP_SUCCESS; case LINK_ST_A6_WAIT_RECEIVER_ID_LIST: rval = dplink_wait_for_receiver_id_list(lk_data); if (rval == -TX_AUTH_ERROR_TIME_EXCEED) { if (retry_recvid < RECVID_WAIT_RETRY_COUNT) { /* retry hdcp authentication in case of timeout */ hdcp_dplink_config(DP_HDCP22_DISABLE); UPDATE_LINK_STATE(lk_data, LINK_ST_A0_DETERMINE_RX_HDCP_CAP); hdcp_info("retry authentication. (%d)\n", retry_recvid); retry_recvid++; } else { hdcp_info("exceed retry count. (%d)\n", retry_recvid); ret = HDCP_ERROR_VERIFY_RECEIVER_ID_LIST; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } } else if (rval == HDCP_SUCCESS) { UPDATE_LINK_STATE(lk_data, LINK_ST_A7_VERIFY_RECEIVER_ID_LIST); retry_recvid = 0; } else { /* error on recevier id list wait */ ret = HDCP_ERROR_VERIFY_RECEIVER_ID_LIST; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } break; case LINK_ST_A7_VERIFY_RECEIVER_ID_LIST: if (dplink_verify_receiver_id_list(lk_data) < 0) { ret = HDCP_ERROR_VERIFY_RECEIVER_ID_LIST; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } else { UPDATE_LINK_STATE(lk_data, LINK_ST_A8_SEND_RECEIVER_ID_LIST_ACK); } break; case LINK_ST_A8_SEND_RECEIVER_ID_LIST_ACK: rval = dplink_send_receiver_id_list_ack(lk_data); if (rval < 0) { ret = HDCP_ERROR_VERIFY_RECEIVER_ID_LIST; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } else { UPDATE_LINK_STATE(lk_data, LINK_ST_A9_CONTENT_STREAM_MGT); } break; case LINK_ST_A9_CONTENT_STREAM_MGT: rval = dplink_stream_manage(lk_data); if (rval < 0) { ret = HDCP_ERROR_STREAM_MANAGE; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); } else { UPDATE_LINK_STATE(lk_data, LINK_ST_A5_AUTHENTICATED); auth_proc_state = HDCP_AUTH_PROCESS_DONE; } break; default: ret = HDCP_ERROR_INVALID_STATE; UPDATE_LINK_STATE(lk_data, LINK_ST_H1_TX_LOW_VALUE_CONTENT); break; } } while (1); } /* todo: support multi link & multi session */ int hdcp_dplink_authenticate(void) { int ret; static int retry_cnt; mutex_lock(&hdcp_auth_mutex); auth_proc_state = HDCP_AUTH_PROCESS_IDLE; for (; retry_cnt < HDCP_AUTH_RETRY_COUNT; retry_cnt++) { if (!rp_ready) { hdcp_clear_session(ss_info.ss_id); if (hdcp_session_open(&ss_info)) { mutex_unlock(&hdcp_auth_mutex); return -ENOMEM; } lk_info.ss_id = ss_info.ss_id; if (hdcp_link_open(&lk_info, HDCP_LINK_TYPE_DP)) { hdcp_session_close(&ss_info); mutex_unlock(&hdcp_auth_mutex); return -ENOMEM; } /* if hdcp is enabled, disable it while hdcp authentication */ if (hdcp_dplink_is_enabled_hdcp22()) hdcp_dplink_config(DP_HDCP22_DISABLE); dplink_clear_irqflag_all(); } ret = do_dplink_auth(&lk_info); if (ret == HDCP_SUCCESS) { hdcp_info("Success HDCP authenticate done.\n"); retry_cnt = 0; mutex_unlock(&hdcp_auth_mutex); return 0; } else { /* auth_proc_state flag check */ if (auth_proc_state == HDCP_AUTH_PROCESS_STOP) { hdcp_info("Stop authenticate.\n"); auth_proc_state = HDCP_AUTH_PROCESS_IDLE; break; } /* retry */ dplink_clear_irqflag_all(); hdcp_err("HDCP auth failed. retry(%d)\n", retry_cnt); } } retry_cnt = 0; mutex_unlock(&hdcp_auth_mutex); return -EACCES; } void hdcp_clear_session(uint32_t id) { struct hdcp_sess_info ss; struct hdcp_link_info lk; ss.ss_id = id; lk.ss_id = id; lk.lk_id = id; hdcp_link_close(&lk); hdcp_session_close(&ss); } int hdcp_dplink_stream_manage(void) { hdcp_info("stream manage updated.\n"); /* todo: update stream manage information */ return 0; } MODULE_LICENSE("GPL");