/* * 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 #include #include #include #include #include #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" #include "dp_link/exynos-hdcp2-dplink-inter.h" #include #include #include #include #include #include #define EXYNOS_HDCP_DEV_NAME "hdcp2" static struct miscdevice hdcp; struct device *device_hdcp; static DEFINE_MUTEX(hdcp_lock); enum hdcp_result hdcp_link_ioc_authenticate(void); extern enum dp_state dp_hdcp_state; struct hdcp_ctx { struct delayed_work work; /* debugfs root */ struct dentry *debug_dir; /* seclog can be disabled via debugfs */ bool enabled; unsigned int irq; }; static struct hdcp_ctx h_ctx; 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); return 0; } 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); return 0; } 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); return 0; } 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); return 0; } 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); return 0; } 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); 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); // mark on 5.4 // __flush_dcache_area(input, packet_len); // __flush_dcache_area(output, packet_len); dma_map_single(device_hdcp, (void *)input_virt, packet_len, DMA_TO_DEVICE); dma_map_single(device_hdcp, (void *)output_virt, packet_len, DMA_TO_DEVICE); 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); return 0; } 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); return 0; } #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_EMULATION_MODE) #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 #endif rval = 0; return rval; } #if defined(CONFIG_HDCP2_IIA_ENABLE) 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); return 0; } #endif 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 void exynos_hdcp_worker(struct work_struct *work) { int ret; if (dp_hdcp_state == DP_DISCONNECT) { hdcp_err("dp_disconnected\n"); return; } hdcp_info("Exynos HDCP interrupt occur by LDFW.\n"); ret = hdcp_dplink_auth_check(HDCP_DRM_ON); } static irqreturn_t exynos_hdcp_irq_handler(int irq, void *dev_id) { if (h_ctx.enabled) { if (dp_hdcp_state == DP_HDCP_READY) schedule_delayed_work(&h_ctx.work, msecs_to_jiffies(0)); else schedule_delayed_work(&h_ctx.work, msecs_to_jiffies(2500)); } return IRQ_HANDLED; } static int exynos_hdcp_probe(struct platform_device *pdev) { struct irq_data *hdcp_irqd = NULL; irq_hw_number_t hwirq = 0; int err; h_ctx.irq = irq_of_parse_and_map(pdev->dev.of_node, 0); if (!h_ctx.irq) { dev_err(&pdev->dev, "Fail to get irq from dt\n"); return -EINVAL; } /* Get irq_data for secure log */ hdcp_irqd = irq_get_irq_data(h_ctx.irq); if (!hdcp_irqd) { dev_err(&pdev->dev, "Fail to get irq_data\n"); return -EINVAL; } /* Get hardware interrupt number */ hwirq = irqd_to_hwirq(hdcp_irqd); err = devm_request_irq(&pdev->dev, h_ctx.irq, exynos_hdcp_irq_handler, IRQF_TRIGGER_RISING, pdev->name, NULL); device_hdcp = &pdev->dev; // arch_setup_dma_ops(&pdev->dev, 0x0ULL, 1ULL << 36, NULL, false); pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; dma_set_mask(&pdev->dev, DMA_BIT_MASK(36)); if (err) { dev_err(&pdev->dev, "Fail to request IRQ handler. err(%d) irq(%d)\n", err, h_ctx.irq); return err; } /* Set workqueue for Secure log as bottom half */ INIT_DELAYED_WORK(&h_ctx.work, exynos_hdcp_worker); h_ctx.enabled = true; err = exynos_smc(SMC_HDCP_NOTIFY_INTR_NUM, 0, 0, hwirq); hdcp_info("Exynos HDCP driver probe done! (%d)\n", err); return err; } static const struct of_device_id exynos_hdcp_of_match_table[] = { { .compatible = "samsung,exynos-hdcp", }, { }, }; static struct platform_driver exynos_hdcp_driver = { .probe = exynos_hdcp_probe, .driver = { .name = "exynos-hdcp", .owner = THIS_MODULE, .of_match_table = of_match_ptr(exynos_hdcp_of_match_table), } }; 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; } hdcp_session_list_init(); #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 platform_driver_register(&exynos_hdcp_driver); } static void __exit hdcp_exit(void) { /* todo: do clear sequence */ cancel_delayed_work_sync(&h_ctx.work); misc_deregister(&hdcp); hdcp_session_list_destroy(); hdcp_tee_close(); platform_driver_unregister(&exynos_hdcp_driver); } static const struct file_operations hdcp_fops = { .owner = THIS_MODULE, .open = hdcp_open, .release = hdcp_release, .compat_ioctl = hdcp_ioctl, .unlocked_ioctl = hdcp_ioctl, }; static struct miscdevice hdcp = { .minor = MISC_DYNAMIC_MINOR, .name = EXYNOS_HDCP_DEV_NAME, .fops = &hdcp_fops, }; module_init(hdcp_init); module_exit(hdcp_exit); MODULE_DESCRIPTION("Exynos Secure hdcp driver"); MODULE_AUTHOR(""); MODULE_LICENSE("GPL");