| /* |
| * drivers/soc/samsung/exynos-hdcp/exynos-hdcp2-teeif.c |
| * |
| * Copyright (c) 2016 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 <linux/smc.h> |
| #include <linux/slab.h> |
| #include <asm/cacheflush.h> |
| |
| #include "exynos-hdcp2-teeif.h" |
| #include "exynos-hdcp2.h" |
| #include "exynos-hdcp2-log.h" |
| |
| extern void __inval_cache_range(const void *start, const void *end); |
| |
| static struct hci_ctx hctx = { |
| .msg = NULL, |
| .state = HCI_DISCONNECTED |
| }; |
| |
| int hdcp_tee_open(void) |
| { |
| int ret = 0; |
| u64 phys_addr = 0; |
| |
| if (hctx.state == HCI_CONNECTED) { |
| hdcp_info("HCI is already connected\n"); |
| return 0; |
| } |
| |
| /* Allocate WSM for HDCP commnad interface */ |
| hctx.msg = (struct hci_message *)kzalloc(sizeof(struct hci_message), GFP_KERNEL); |
| if (!hctx.msg) { |
| hdcp_err("alloc wsm for HDCP SWd is failed\n"); |
| return -ENOMEM; |
| } |
| |
| /* send WSM address to SWd */ |
| phys_addr = virt_to_phys((void *)hctx.msg); |
| ret = exynos_smc(SMC_HDCP_INIT, phys_addr, sizeof(struct hci_message), 0); |
| if (ret) { |
| hdcp_err("Fail to set up connection with SWd. ret(%d)\n", ret); |
| kfree(hctx.msg); |
| hctx.msg = NULL; |
| return -ECONNREFUSED; |
| } |
| |
| hctx.state = HCI_CONNECTED; |
| |
| return 0; |
| } |
| |
| int hdcp_tee_close() |
| { |
| int ret; |
| |
| if (hctx.state == HCI_DISCONNECTED) { |
| hdcp_info("HCI is already disconnected\n"); |
| return 0; |
| } |
| |
| if (hctx.msg) { |
| kfree(hctx.msg); |
| hctx.msg = NULL; |
| } |
| |
| /* send terminate command to SWd */ |
| ret = exynos_smc(SMC_HDCP_TERMINATE, 0, 0, 0); |
| if (ret) { |
| hdcp_err("HDCP: Fail to set up connection with SWd. ret(%d)\n", ret); |
| return -EFAULT; |
| } |
| |
| hctx.state = HCI_DISCONNECTED; |
| |
| return 0; |
| } |
| |
| int hdcp_tee_comm(struct hci_message *hci) |
| { |
| int ret; |
| |
| if (!hci) |
| return -EINVAL; |
| |
| /** |
| * kernel & TEE does not share cache. |
| * So, cache should be flushed before sending data to TEE |
| * and invalidate after come back to kernel |
| */ |
| __flush_dcache_area((void *)hci, sizeof(struct hci_message)); |
| ret = exynos_smc(SMC_HDCP_PROT_MSG, 0, 0, 0); |
| __inval_cache_range((void *)hci, (void *)((uint8_t *)hci + sizeof(struct hci_message))); |
| |
| if (ret) { |
| hdcp_info("SWd returned(%d)\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| 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 *hci = hctx.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_get_txinfo(uint8_t *version, size_t version_len, |
| uint8_t *caps_mask, uint32_t caps_mask_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| |
| /* todo: input check */ |
| |
| /* Update TCI buffer */ |
| hci->cmd_id = HDCP_TEEI_GET_TXINFO; |
| |
| /* send command to swd */ |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| /* check returned message from SWD */ |
| memcpy(version, hci->gettxinfo.version, version_len); |
| memcpy(caps_mask, hci->gettxinfo.caps_mask, caps_mask_len); |
| |
| return ret; |
| } |
| |
| int teei_set_rxinfo(uint8_t *version, size_t version_len, |
| uint8_t *caps_mask, uint32_t caps_mask_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| |
| /* todo: input check */ |
| |
| /* Update TCI buffer */ |
| hci->cmd_id = HDCP_TEEI_SET_RXINFO; |
| memcpy(hci->setrxinfo.version, version, version_len); |
| memcpy(hci->setrxinfo.caps_mask, caps_mask, caps_mask_len); |
| |
| /* send command to swd */ |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| 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 *hci = hctx.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 *hci = hctx.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_set_rrx(uint8_t *rrx, size_t rrx_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| |
| /* todo: input check */ |
| |
| /* Update TCI buffer */ |
| hci->cmd_id = HDCP_TEEI_SET_RRX; |
| memcpy(hci->setrrx.rrx, rrx, rrx_len); |
| |
| /* send command to swd */ |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| return ret; |
| } |
| |
| int teei_compare_ake_hmac(uint8_t *rx_hmac, size_t rx_hmac_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.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 *hci = hctx.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 ret = 0; |
| struct hci_message *hci = hctx.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); |
| |
| return ret; |
| } |
| |
| int teei_gen_rn(uint8_t *out, size_t len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.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_gen_lc_hmac(uint32_t lk_type, uint8_t *lsb16_hmac) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| /* todo: input check */ |
| |
| /* Update TCI buffer */ |
| hci->cmd_id = HDCP_TEEI_GEN_LC_HMAC; |
| hci->genlchmac.lk_type = lk_type; |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| /* Send LSB 16bytes to Rx */ |
| if (lsb16_hmac) |
| memcpy(lsb16_hmac, hci->genlchmac.lsb16_hmac, (HDCP_HMAC_SHA256_LEN / 2)); |
| |
| /* todo: check returned message from SWD */ |
| |
| return 0; |
| } |
| |
| int teei_compare_lc_hmac(uint8_t *rx_hmac, size_t rx_hmac_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.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 *hci = hctx.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 *hci = hctx.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_encrypt_packet(uint8_t *input, size_t input_len, |
| uint8_t *output, size_t output_len, |
| uint8_t *str_ctr, size_t str_ctr_len, |
| uint8_t *input_ctr, size_t input_ctr_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| /* todo: input check */ |
| |
| /* Update TCI buffer */ |
| hci->cmd_id = HDCP_TEEI_ENC_PACKET; |
| hci->encpacket.input_len = input_len; |
| hci->encpacket.output_len = output_len; |
| hci->encpacket.input_addr = (uint64_t)input; |
| hci->encpacket.output_addr = (uint64_t)output; |
| memcpy(hci->encpacket.str_ctr, str_ctr, str_ctr_len); |
| memcpy(hci->encpacket.input_ctr, input_ctr, input_ctr_len); |
| |
| hdcp_debug("teeif command(%x)\n", hci->cmd_id); |
| hdcp_debug("input(%llx), output(%llx)\n", hci->encpacket.input_addr, hci->encpacket.output_addr); |
| |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| 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 *hci = hctx.msg; |
| /* todo: input check */ |
| |
| hci->cmd_id = HDCP_TEEI_SET_RCV_ID_LIST; |
| |
| if (rcvid_list) |
| memcpy(hci->setrcvlist.rcvid_lst, rcvid_list, HDCP_RP_RCVID_LIST_LEN); |
| else |
| return TX_AUTH_ERROR_WRONG_MSG; |
| |
| if (v_prime) |
| memcpy(hci->setrcvlist.v_prime, v_prime, HDCP_RP_HMAC_V_LEN / 2); |
| else |
| return TX_AUTH_ERROR_WRONG_MSG; |
| |
| /* 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); |
| } |
| |
| if ((ret = hdcp_tee_comm(hci)) < 0) { |
| *valid = 1; |
| return ret; |
| } |
| memcpy(v, hci->setrcvlist.v, HDCP_RP_HMAC_V_LEN / 2); |
| *valid = 0; |
| |
| 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 *hci = hctx.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_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 *hci = hctx.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; |
| } |
| |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| return ret; |
| } |
| |
| int teei_wrapped_key(uint8_t *key, uint32_t wrapped, uint32_t key_len) |
| { |
| int ret = 0; |
| struct hci_message *hci = hctx.msg; |
| |
| hci->cmd_id = HDCP_TEEI_WRAP_KEY; |
| |
| if (key_len < HDCP_WRAP_AUTH_TAG || key_len > HDCP_WRAP_MAX_SIZE - HDCP_WRAP_AUTH_TAG) { |
| hdcp_err("(un)wraaping key size is wrong. 0x%x\n", key_len); |
| return HDCP_ERROR_WRONG_SIZE; |
| } |
| |
| if (wrapped == UNWRAP) |
| memcpy(hci->wrap_key.enc_key, key, key_len + HDCP_WRAP_AUTH_TAG); |
| else |
| memcpy(hci->wrap_key.key, key, HDCP_WRAP_KEY); |
| |
| hci->wrap_key.wrapped = wrapped; |
| hci->wrap_key.key_len = key_len; |
| |
| if ((ret = hdcp_tee_comm(hci)) < 0) |
| return ret; |
| |
| if (hci->wrap_key.wrapped == WRAP) { |
| memcpy(key, hci->wrap_key.enc_key, key_len + HDCP_WRAP_AUTH_TAG); |
| } |
| |
| return ret; |
| } |