438 lines
11 KiB
C
Executable File
438 lines
11 KiB
C
Executable File
/*
|
|
* drivers/soc/samsung/exynos-hdcp/exynos-hdcp.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/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/smc.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <linux/smc.h>
|
|
#include "iia_link/exynos-hdcp2-iia-auth.h"
|
|
#include "exynos-hdcp2-teeif.h"
|
|
#include "iia_link/exynos-hdcp2-iia-selftest.h"
|
|
#include "exynos-hdcp2-encrypt.h"
|
|
#include "exynos-hdcp2-log.h"
|
|
#include "dp_link/exynos-hdcp2-dplink-if.h"
|
|
#include "dp_link/exynos-hdcp2-dplink.h"
|
|
#include "dp_link/exynos-hdcp2-dplink-selftest.h"
|
|
|
|
#define EXYNOS_HDCP_DEV_NAME "hdcp2"
|
|
|
|
struct miscdevice hdcp;
|
|
static DEFINE_MUTEX(hdcp_lock);
|
|
struct hdcp_session_list g_hdcp_session_list;
|
|
enum hdcp_result hdcp_link_ioc_authenticate(void);
|
|
|
|
|
|
static uint32_t inst_num;
|
|
|
|
static long hdcp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int rval;
|
|
|
|
switch (cmd) {
|
|
#if defined(CONFIG_HDCP2_IIA_ENABLE)
|
|
case (uint32_t)HDCP_IOC_SESSION_OPEN:
|
|
{
|
|
struct hdcp_sess_info ss_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&ss_info, (void __user *)arg, sizeof(struct hdcp_sess_info));
|
|
if (rval) {
|
|
hdcp_err("Session open copy from user fail. (%x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_session_open(&ss_info);
|
|
if (rval) {
|
|
hdcp_err("Session open fail. (%x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return rval;
|
|
}
|
|
|
|
rval = copy_to_user((void __user *)arg, &ss_info, sizeof(struct hdcp_sess_info));
|
|
if (rval) {
|
|
hdcp_err("Session open copy to user fail. (%x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_SESSION_CLOSE:
|
|
{
|
|
/* todo: session close */
|
|
struct hdcp_sess_info ss_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&ss_info, (void __user *)arg, sizeof(struct hdcp_sess_info));
|
|
if (rval) {
|
|
hdcp_err("Session close copy from user fail. (%x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_session_close(&ss_info);
|
|
if (rval) {
|
|
hdcp_err("hdcp_session close fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_SESSION_CLOSE_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_LINK_OPEN:
|
|
{
|
|
struct hdcp_link_info lk_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&lk_info, (void __user *)arg, sizeof(struct hdcp_link_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_link open copy from user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_link_open(&lk_info, HDCP_LINK_TYPE_IIA);
|
|
if (rval) {
|
|
hdcp_err("hdcp_link open fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_LINK_OPEN_FAILED;
|
|
}
|
|
|
|
rval = copy_to_user((void __user *)arg, &lk_info, sizeof(struct hdcp_link_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_link open copy to user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_LINK_CLOSE:
|
|
{
|
|
struct hdcp_link_info lk_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
/* find Session node which contain the Link */
|
|
rval = copy_from_user(&lk_info, (void __user *)arg, sizeof(struct hdcp_link_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_link close copy from user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_link_close(&lk_info);
|
|
if (rval) {
|
|
hdcp_err("hdcp_link close fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_LINK_CLOSE_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_LINK_AUTH:
|
|
{
|
|
struct hdcp_msg_info msg_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&msg_info, (void __user *)arg, sizeof(struct hdcp_msg_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_authenticate fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_link_authenticate(&msg_info);
|
|
if (rval) {
|
|
hdcp_err("hdcp_authenticate fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_AUTHENTICATE_FAILED;
|
|
}
|
|
|
|
rval = copy_to_user((void __user *)arg, &msg_info, sizeof(struct hdcp_msg_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_authenticate fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_LINK_ENC:
|
|
{
|
|
/* todo: link close */
|
|
struct hdcp_enc_info enc_info;
|
|
size_t packet_len = 0;
|
|
uint8_t pes_priv[HDCP_PRIVATE_DATA_LEN];
|
|
unsigned long input_phys, output_phys;
|
|
struct hdcp_session_node *ss_node;
|
|
struct hdcp_link_node *lk_node;
|
|
struct hdcp_link_data *lk_data;
|
|
uint8_t *input_virt, *output_virt;
|
|
int ret = HDCP_SUCCESS;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
/* find Session node which contain the Link */
|
|
rval = copy_from_user(&enc_info, (void __user *)arg, sizeof(struct hdcp_enc_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_encrypt copy from user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
/* find Session node which contains the Link */
|
|
ss_node = hdcp_session_list_find(enc_info.id, &g_hdcp_session_list);
|
|
if (!ss_node) {
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_INVALID_INPUT;
|
|
}
|
|
lk_node = hdcp_link_list_find(enc_info.id, &ss_node->ss_data->ln);
|
|
if (!lk_node) {
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_INVALID_INPUT;
|
|
}
|
|
lk_data = lk_node->lk_data;
|
|
|
|
if (!lk_data) {
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_INVALID_INPUT;
|
|
}
|
|
input_phys = (unsigned long)enc_info.input_phys;
|
|
output_phys = (unsigned long)enc_info.output_phys;
|
|
|
|
/* set input counters */
|
|
memset(&(lk_data->tx_ctx.input_ctr), 0x00, HDCP_INPUT_CTR_LEN);
|
|
/* set output counters */
|
|
memset(&(lk_data->tx_ctx.str_ctr), 0x00, HDCP_STR_CTR_LEN);
|
|
|
|
packet_len = (size_t)enc_info.input_len;
|
|
input_virt = (uint8_t *)phys_to_virt(input_phys);
|
|
output_virt = (uint8_t *)phys_to_virt(output_phys);
|
|
|
|
__flush_dcache_area(input_virt, packet_len);
|
|
__flush_dcache_area(output_virt, packet_len);
|
|
|
|
ret = encrypt_packet(pes_priv,
|
|
input_phys, packet_len,
|
|
output_phys, packet_len,
|
|
&(lk_data->tx_ctx));
|
|
|
|
if (ret) {
|
|
hdcp_err("encrypt_packet() is failed with 0x%x\n", ret);
|
|
mutex_unlock(&hdcp_lock);
|
|
return -1;
|
|
}
|
|
|
|
rval = copy_to_user((void __user *)arg, &enc_info, sizeof(struct hdcp_enc_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_encrypt copy to user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
case (uint32_t)HDCP_IOC_STREAM_MANAGE:
|
|
{
|
|
struct hdcp_stream_info stream_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&stream_info, (void __user *)arg, sizeof(struct hdcp_stream_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_link stream manage copy from user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
rval = hdcp_link_stream_manage(&stream_info);
|
|
if (rval) {
|
|
hdcp_err("hdcp_link stream manage fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_LINK_STREAM_FAILED;
|
|
}
|
|
|
|
rval = copy_to_user((void __user *)arg, &stream_info, sizeof(struct hdcp_stream_info));
|
|
if (rval) {
|
|
hdcp_err("hdcp_link stream manage copy to user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_HDCP2_EMULATION_MODE)
|
|
case (uint32_t)HDCP_IOC_EMUL_DPLINK_TX:
|
|
{
|
|
uint32_t emul_cmd;
|
|
|
|
if (copy_from_user(&emul_cmd, (void __user *)arg, sizeof(uint32_t)))
|
|
return -EINVAL;
|
|
|
|
return dplink_emul_handler(emul_cmd);
|
|
}
|
|
#endif
|
|
case (uint32_t)HDCP_IOC_DPLINK_TX_AUTH:
|
|
{
|
|
#if defined(CONFIG_HDCP2_DP_ENABLE)
|
|
rval = dp_hdcp_protocol_self_test();
|
|
if (rval) {
|
|
hdcp_err("DP self_test fail. errno(%d)\n", rval);
|
|
return rval;
|
|
}
|
|
hdcp_err("DP self_test success!!\n");
|
|
#endif
|
|
#if defined(TEST_VECTOR_2)
|
|
/* todo: support test vector 1 */
|
|
rval = iia_hdcp_protocol_self_test();
|
|
if (rval) {
|
|
hdcp_err("IIA self_test failed. errno(%d)\n", rval);
|
|
return rval;
|
|
}
|
|
hdcp_err("IIA self_test success!!\n");
|
|
#endif
|
|
rval = 0;
|
|
return rval;
|
|
}
|
|
case (uint32_t)HDCP_IOC_WRAP_KEY:
|
|
{
|
|
struct hdcp_wrapped_key key_info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
rval = copy_from_user(&key_info, (void __user *)arg, sizeof(struct hdcp_wrapped_key));
|
|
if (rval) {
|
|
hdcp_err("hdcp_wrap_key copy from user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_FROM_USER_FAILED;
|
|
}
|
|
|
|
if (hdcp_wrap_key(&key_info)) {
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_WRAP_FAIL;
|
|
}
|
|
rval = copy_to_user((void __user *)arg, &key_info, sizeof(struct hdcp_wrapped_key));
|
|
if (rval) {
|
|
hdcp_err("hdcp_wrap_key copy to user fail (0x%08x)\n", rval);
|
|
mutex_unlock(&hdcp_lock);
|
|
return HDCP_ERROR_COPY_TO_USER_FAILED;
|
|
}
|
|
|
|
mutex_unlock(&hdcp_lock);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
hdcp_err("HDCP: Invalid IOC num(%d)\n", cmd);
|
|
return -ENOTTY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdcp_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct miscdevice *miscdev = file->private_data;
|
|
struct device *dev = miscdev->this_device;
|
|
struct hdcp_info *info;
|
|
|
|
info = kzalloc(sizeof(struct hdcp_info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
info->dev = dev;
|
|
file->private_data = info;
|
|
|
|
mutex_lock(&hdcp_lock);
|
|
inst_num++;
|
|
/* todo: hdcp device initialize ? */
|
|
mutex_unlock(&hdcp_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdcp_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct hdcp_info *info = file->private_data;
|
|
|
|
/* disable drm if we were the one to turn it on */
|
|
mutex_lock(&hdcp_lock);
|
|
inst_num--;
|
|
/* todo: hdcp device finalize ? */
|
|
mutex_unlock(&hdcp_lock);
|
|
|
|
kfree(info);
|
|
return 0;
|
|
}
|
|
|
|
static int __init hdcp_init(void)
|
|
{
|
|
int ret;
|
|
|
|
hdcp_info("hdcp2 driver init\n");
|
|
|
|
ret = misc_register(&hdcp);
|
|
if (ret) {
|
|
hdcp_err("hdcp can't register misc on minor=%d\n",
|
|
MISC_DYNAMIC_MINOR);
|
|
return ret;
|
|
}
|
|
|
|
/* todo: do initialize sequence */
|
|
hdcp_session_list_init(&g_hdcp_session_list);
|
|
#if defined(CONFIG_HDCP2_DP_ENABLE)
|
|
if (hdcp_dplink_init() < 0) {
|
|
hdcp_err("hdcp_dplink_init fail\n");
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
ret = hdcp_tee_open();
|
|
if (ret) {
|
|
hdcp_err("hdcp_tee_open fail\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit hdcp_exit(void)
|
|
{
|
|
/* todo: do clear sequence */
|
|
|
|
misc_deregister(&hdcp);
|
|
hdcp_session_list_destroy(&g_hdcp_session_list);
|
|
hdcp_tee_close();
|
|
}
|
|
|
|
static const struct file_operations hdcp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = hdcp_open,
|
|
.release = hdcp_release,
|
|
.compat_ioctl = hdcp_ioctl,
|
|
.unlocked_ioctl = hdcp_ioctl,
|
|
};
|
|
|
|
struct miscdevice hdcp = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = EXYNOS_HDCP_DEV_NAME,
|
|
.fops = &hdcp_fops,
|
|
};
|
|
|
|
module_init(hdcp_init);
|
|
module_exit(hdcp_exit);
|