| /* |
| * hdcp_top.c |
| * |
| * HDCP interface DSS driver setting for TI's OMAP4 family of processor. |
| * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ |
| * Authors: Fabrice Olivero |
| * Fabrice Olivero <f-olivero@ti.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. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include <linux/wait.h> |
| #include <linux/sched.h> |
| #include <linux/mm.h> |
| #include <linux/completion.h> |
| #include <linux/miscdevice.h> |
| #include <linux/firmware.h> |
| #include "../../hdmi_ti_4xxx_ip.h" |
| #include "../dss/dss.h" |
| #include "hdcp.h" |
| #include "../../hdmi_ti_4xxx_ip_ddc.h" |
| |
| struct hdcp hdcp; |
| struct hdcp_sha_in sha_input; |
| |
| /* State machine / workqueue */ |
| static void hdcp_wq_disable(void); |
| static void hdcp_wq_start_authentication(void); |
| static void hdcp_wq_check_r0(void); |
| static void hdcp_wq_step2_authentication(void); |
| static void hdcp_wq_authentication_failure(void); |
| static void hdcp_work_queue(struct work_struct *work); |
| static struct delayed_work *hdcp_submit_work(int event, int delay); |
| static void hdcp_cancel_work(struct delayed_work **work); |
| |
| /* Callbacks */ |
| static void hdcp_start_frame_cb(void); |
| static void hdcp_irq_cb(int hpd_low); |
| |
| /* Control */ |
| static long hdcp_enable_ctl(void __user *argp); |
| static long hdcp_disable_ctl(void); |
| static long hdcp_query_status_ctl(void __user *argp); |
| static long hdcp_encrypt_key_ctl(void __user *argp); |
| |
| /* Driver */ |
| static int __init hdcp_init(void); |
| static void __exit hdcp_exit(void); |
| |
| struct completion hdcp_comp; |
| static DECLARE_WAIT_QUEUE_HEAD(hdcp_up_wait_queue); |
| static DECLARE_WAIT_QUEUE_HEAD(hdcp_down_wait_queue); |
| |
| #define DSS_POWER |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_request_dss |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_request_dss(void) |
| { |
| #ifdef DSS_POWER |
| hdcp.dss_state = dss_runtime_get(); |
| #endif |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_user_space_task |
| *----------------------------------------------------------------------------- |
| */ |
| int hdcp_user_space_task(int flags) |
| { |
| int ret; |
| |
| DBG("Wait for user space task %x\n", flags); |
| hdcp.hdcp_up_event = flags & 0xFF; |
| hdcp.hdcp_down_event = flags & 0xFF; |
| wake_up_interruptible(&hdcp_up_wait_queue); |
| wait_event_interruptible(hdcp_down_wait_queue, |
| (hdcp.hdcp_down_event & 0xFF) == 0); |
| ret = (hdcp.hdcp_down_event & 0xFF00) >> 8; |
| |
| DBG("User space task done %x\n", hdcp.hdcp_down_event); |
| hdcp.hdcp_down_event = 0; |
| |
| return ret; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_release_dss |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_release_dss(void) |
| { |
| #ifdef DSS_POWER |
| if (hdcp.dss_state == 0) |
| dss_runtime_put(); |
| #endif |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wq_disable |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_wq_disable(void) |
| { |
| printk(KERN_INFO "HDCP: disabled\n"); |
| |
| hdcp_cancel_work(&hdcp.pending_wq_event); |
| hdcp_lib_disable(); |
| ddc.pending_disable = 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wq_start_authentication |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_wq_start_authentication(void) |
| { |
| int status = HDCP_OK; |
| |
| hdcp.hdcp_state = HDCP_AUTHENTICATION_START; |
| |
| if (hdcp.print_messages) |
| printk(KERN_INFO "HDCP: authentication start\n"); |
| |
| /* Step 1 part 1 (until R0 calc delay) */ |
| status = hdcp_lib_step1_start(); |
| |
| if (status == -HDCP_AKSV_ERROR) { |
| hdcp_wq_authentication_failure(); |
| } else if (status == -HDCP_CANCELLED_AUTH) { |
| DBG("Authentication step 1 cancelled."); |
| return; |
| } else if (status != HDCP_OK) { |
| hdcp_wq_authentication_failure(); |
| } else { |
| hdcp.hdcp_state = HDCP_WAIT_R0_DELAY; |
| hdcp.auth_state = HDCP_STATE_AUTH_1ST_STEP; |
| hdcp.pending_wq_event = hdcp_submit_work(HDCP_R0_EXP_EVENT, |
| HDCP_R0_DELAY); |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wq_check_r0 |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_wq_check_r0(void) |
| { |
| int status = hdcp_lib_step1_r0_check(); |
| |
| if (status == -HDCP_CANCELLED_AUTH) { |
| DBG("Authentication step 1/R0 cancelled."); |
| return; |
| } else if (status < 0) |
| hdcp_wq_authentication_failure(); |
| else { |
| hdcp.fail_cnt = 0; |
| hdcp.print_messages = 1; |
| if (hdcp_lib_check_repeater_bit_in_tx()) { |
| /* Repeater */ |
| printk(KERN_INFO "HDCP: authentication step 1 " |
| "successful - Repeater\n"); |
| |
| hdcp.hdcp_state = HDCP_WAIT_KSV_LIST; |
| hdcp.auth_state = HDCP_STATE_AUTH_2ND_STEP; |
| |
| hdcp.pending_wq_event = |
| hdcp_submit_work(HDCP_KSV_TIMEOUT_EVENT, |
| HDCP_KSV_TIMEOUT_DELAY); |
| } else { |
| /* Receiver */ |
| printk(KERN_INFO "HDCP: authentication step 1 " |
| "successful - Receiver\n"); |
| |
| hdcp.av_mute_needed = 1; |
| hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; |
| hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; |
| |
| /* Restore retry counter */ |
| if (hdcp.en_ctrl->nb_retry == 0) |
| hdcp.retry_cnt = HDCP_INFINITE_REAUTH; |
| else |
| hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; |
| } |
| } |
| } |
| |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wq_step2_authentication |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_wq_step2_authentication(void) |
| { |
| int status = HDCP_OK; |
| |
| /* KSV list timeout is running and should be canceled */ |
| hdcp_cancel_work(&hdcp.pending_wq_event); |
| |
| status = hdcp_lib_step2(); |
| |
| if (status == -HDCP_CANCELLED_AUTH) { |
| DBG("Authentication step 2 cancelled."); |
| return; |
| } else if (status < 0) |
| hdcp_wq_authentication_failure(); |
| else { |
| printk(KERN_INFO "HDCP: (Repeater) authentication step 2 " |
| "successful\n"); |
| |
| hdcp.av_mute_needed = 1; |
| hdcp.hdcp_state = HDCP_LINK_INTEGRITY_CHECK; |
| hdcp.auth_state = HDCP_STATE_AUTH_3RD_STEP; |
| |
| /* Restore retry counter */ |
| if (hdcp.en_ctrl->nb_retry == 0) |
| hdcp.retry_cnt = HDCP_INFINITE_REAUTH; |
| else |
| hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wq_authentication_failure |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_wq_authentication_failure(void) |
| { |
| if (hdcp.hdmi_state == HDMI_STOPPED) { |
| hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; |
| return; |
| } |
| |
| hdcp_lib_auto_ri_check(false); |
| hdcp_lib_auto_bcaps_rdy_check(false); |
| if (hdcp.av_mute_needed) |
| hdcp_lib_set_av_mute(AV_MUTE_SET); |
| hdcp_lib_set_encryption(HDCP_ENC_OFF); |
| |
| hdcp_cancel_work(&hdcp.pending_wq_event); |
| |
| hdcp_lib_disable(); |
| ddc.pending_disable = 0; |
| |
| if (hdcp.retry_cnt && (hdcp.hdmi_state != HDMI_STOPPED)) { |
| if (hdcp.retry_cnt < HDCP_INFINITE_REAUTH) { |
| hdcp.retry_cnt--; |
| printk(KERN_INFO "HDCP: authentication failed - " |
| "retrying, attempts=%d\n", |
| hdcp.retry_cnt); |
| } else { |
| hdcp.fail_cnt++; |
| if (hdcp.print_messages) { |
| if (hdcp.fail_cnt < HDCP_MAX_FAIL_MESSAGES) |
| printk(KERN_INFO "HDCP: authentication " |
| "failed - retrying\n"); |
| else { |
| hdcp.print_messages = 0; |
| printk(KERN_INFO "HDCP: authentication " |
| "failed %d consecutive times\n", |
| hdcp.fail_cnt); |
| printk(KERN_INFO "HDCP: will keep " |
| "trying but silencing logs " |
| "until hotplug or success\n"); |
| } |
| } |
| } |
| |
| hdcp.hdcp_state = HDCP_AUTHENTICATION_START; |
| hdcp.auth_state = HDCP_STATE_AUTH_FAIL_RESTARTING; |
| |
| hdcp.pending_wq_event = hdcp_submit_work(HDCP_AUTH_REATT_EVENT, |
| HDCP_REAUTH_DELAY); |
| } else { |
| printk(KERN_INFO "HDCP: authentication failed - " |
| "HDCP disabled\n"); |
| hdcp.hdcp_state = HDCP_ENABLE_PENDING; |
| hdcp.auth_state = HDCP_STATE_AUTH_FAILURE; |
| } |
| |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_work_queue |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_work_queue(struct work_struct *work) |
| { |
| struct hdcp_delayed_work *hdcp_w = |
| container_of(work, struct hdcp_delayed_work, work.work); |
| int event = hdcp_w->event; |
| |
| mutex_lock(&hdcp.lock); |
| |
| hdcp_request_dss(); |
| |
| DBG("hdcp_work_queue() - START - %u hdmi=%d hdcp=%d auth=%d evt= %x %d" |
| " hdcp_ctrl=%02x", |
| jiffies_to_msecs(jiffies), |
| hdcp.hdmi_state, |
| hdcp.hdcp_state, |
| hdcp.auth_state, |
| (event & 0xFF00) >> 8, |
| event & 0xFF, |
| RD_REG_32(hdcp.hdmi_wp_base_addr + HDMI_IP_CORE_SYSTEM, |
| HDMI_IP_CORE_SYSTEM__HDCP_CTRL)); |
| |
| /* Clear pending_wq_event |
| * In case a delayed work is scheduled from the state machine |
| * "pending_wq_event" is used to memorize pointer on the event to be |
| * able to cancel any pending work in case HDCP is disabled |
| */ |
| if (event & HDCP_WORKQUEUE_SRC) |
| hdcp.pending_wq_event = 0; |
| |
| /* First handle HDMI state */ |
| if (event == HDCP_START_FRAME_EVENT) { |
| hdcp.pending_start = 0; |
| hdcp.hdmi_state = HDMI_STARTED; |
| } |
| /**********************/ |
| /* HDCP state machine */ |
| /**********************/ |
| switch (hdcp.hdcp_state) { |
| |
| /* State */ |
| /*********/ |
| case HDCP_DISABLED: |
| /* HDCP enable control or re-authentication event */ |
| if (event == HDCP_ENABLE_CTL) { |
| if (hdcp.en_ctrl->nb_retry == 0) |
| hdcp.retry_cnt = HDCP_INFINITE_REAUTH; |
| else |
| hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; |
| |
| if (hdcp.hdmi_state == HDMI_STARTED) |
| hdcp_wq_start_authentication(); |
| else |
| hdcp.hdcp_state = HDCP_ENABLE_PENDING; |
| } |
| |
| break; |
| |
| /* State */ |
| /*********/ |
| case HDCP_ENABLE_PENDING: |
| /* HDMI start frame event */ |
| if (event == HDCP_START_FRAME_EVENT) |
| hdcp_wq_start_authentication(); |
| |
| break; |
| |
| /* State */ |
| /*********/ |
| case HDCP_AUTHENTICATION_START: |
| /* Re-authentication */ |
| if (event == HDCP_AUTH_REATT_EVENT) |
| hdcp_wq_start_authentication(); |
| |
| break; |
| |
| /* State */ |
| /*********/ |
| case HDCP_WAIT_R0_DELAY: |
| /* R0 timer elapsed */ |
| if (event == HDCP_R0_EXP_EVENT) |
| hdcp_wq_check_r0(); |
| |
| break; |
| |
| /* State */ |
| /*********/ |
| case HDCP_WAIT_KSV_LIST: |
| /* Ri failure */ |
| if (event == HDCP_RI_FAIL_EVENT) { |
| printk(KERN_INFO "HDCP: Ri check failure\n"); |
| |
| hdcp_wq_authentication_failure(); |
| } |
| /* KSV list ready event */ |
| else if (event == HDCP_KSV_LIST_RDY_EVENT) |
| hdcp_wq_step2_authentication(); |
| /* Timeout */ |
| else if (event == HDCP_KSV_TIMEOUT_EVENT) { |
| printk(KERN_INFO "HDCP: BCAPS polling timeout\n"); |
| hdcp_wq_authentication_failure(); |
| } |
| break; |
| |
| /* State */ |
| /*********/ |
| case HDCP_LINK_INTEGRITY_CHECK: |
| /* Ri failure */ |
| if (event == HDCP_RI_FAIL_EVENT) { |
| printk(KERN_INFO "HDCP: Ri check failure\n"); |
| hdcp_wq_authentication_failure(); |
| } |
| break; |
| |
| default: |
| printk(KERN_WARNING "HDCP: error - unknow HDCP state\n"); |
| break; |
| } |
| |
| kfree(hdcp_w); |
| hdcp_w = 0; |
| if (event == HDCP_START_FRAME_EVENT) |
| hdcp.pending_start = 0; |
| if (event == HDCP_KSV_LIST_RDY_EVENT || |
| event == HDCP_R0_EXP_EVENT) { |
| hdcp.pending_wq_event = 0; |
| } |
| |
| DBG("hdcp_work_queue() - END - %u hdmi=%d hdcp=%d auth=%d evt=%x %d ", |
| jiffies_to_msecs(jiffies), |
| hdcp.hdmi_state, |
| hdcp.hdcp_state, |
| hdcp.auth_state, |
| (event & 0xFF00) >> 8, |
| event & 0xFF); |
| |
| hdcp_release_dss(); |
| mutex_unlock(&hdcp.lock); |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_submit_work |
| *----------------------------------------------------------------------------- |
| */ |
| static struct delayed_work *hdcp_submit_work(int event, int delay) |
| { |
| struct hdcp_delayed_work *work; |
| |
| work = kmalloc(sizeof(struct hdcp_delayed_work), GFP_ATOMIC); |
| |
| if (work) { |
| INIT_DELAYED_WORK(&work->work, hdcp_work_queue); |
| work->event = event; |
| queue_delayed_work(hdcp.workqueue, |
| &work->work, |
| msecs_to_jiffies(delay)); |
| } else { |
| printk(KERN_WARNING "HDCP: Cannot allocate memory to " |
| "create work\n"); |
| return 0; |
| } |
| |
| return &work->work; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_cancel_work |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_cancel_work(struct delayed_work **work) |
| { |
| int ret = 0; |
| |
| if (*work) { |
| ret = cancel_delayed_work(*work); |
| if (ret != 1) { |
| ret = cancel_work_sync(&((*work)->work)); |
| printk(KERN_INFO "Canceling work failed - " |
| "cancel_work_sync done %d\n", ret); |
| } |
| kfree(*work); |
| *work = 0; |
| } |
| } |
| |
| |
| /****************************************************************************** |
| * HDCP callbacks |
| *****************************************************************************/ |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_3des_cb |
| *----------------------------------------------------------------------------- |
| */ |
| static bool hdcp_3des_cb(void) |
| { |
| DBG("hdcp_3des_cb() %u", jiffies_to_msecs(jiffies)); |
| |
| if (!hdcp.hdcp_keys_loaded) { |
| printk(KERN_ERR "%s: hdcp_keys not loaded = %d", |
| __func__, hdcp.hdcp_keys_loaded); |
| return false; |
| } |
| |
| /* Load 3DES key */ |
| if (hdcp_3des_load_key(hdcp.en_ctrl->key) != HDCP_OK) { |
| printk(KERN_ERR "Error Loading HDCP keys\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_start_frame_cb |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_start_frame_cb(void) |
| { |
| DBG("hdcp_start_frame_cb() %u", jiffies_to_msecs(jiffies)); |
| |
| if (!hdcp.hdcp_keys_loaded) { |
| DBG("%s: hdcp_keys not loaded = %d", |
| __func__, hdcp.hdcp_keys_loaded); |
| return; |
| } |
| |
| /* Cancel any pending work */ |
| if (hdcp.pending_start) |
| hdcp_cancel_work(&hdcp.pending_start); |
| if (hdcp.pending_wq_event) |
| hdcp_cancel_work(&hdcp.pending_wq_event); |
| |
| hdcp.hpd_low = 0; |
| ddc.pending_disable = 0; |
| hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; |
| hdcp.fail_cnt = 0; |
| hdcp.av_mute_needed = 0; |
| hdcp.print_messages = 1; |
| |
| hdcp.hdmi_state = HDMI_STOPPED; |
| hdcp.hdcp_state = HDCP_ENABLE_PENDING; |
| hdcp.auth_state = HDCP_STATE_DISABLED; |
| |
| hdcp.pending_start = hdcp_submit_work(HDCP_START_FRAME_EVENT, |
| HDCP_ENABLE_DELAY); |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_irq_cb |
| *----------------------------------------------------------------------------- |
| */ |
| static void hdcp_irq_cb(int status) |
| { |
| DBG("hdcp_irq_cb() status=%x", status); |
| |
| if (!hdcp.hdcp_keys_loaded) { |
| DBG("%s: hdcp_keys not loaded = %d", |
| __func__, hdcp.hdcp_keys_loaded); |
| return; |
| } |
| |
| /* Disable auto Ri/BCAPS immediately */ |
| if (((status & HDMI_RI_ERR) || |
| (status & HDMI_BCAP) || |
| (status & HDMI_HPD_LOW)) && |
| (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { |
| hdcp_lib_auto_ri_check(false); |
| hdcp_lib_auto_bcaps_rdy_check(false); |
| } |
| |
| /* Work queue execution not required if HDCP is disabled */ |
| /* TODO: ignore interrupts if they are masked (cannnot access UMASK |
| * here so should use global variable |
| */ |
| if ((hdcp.hdcp_state != HDCP_DISABLED) && |
| (hdcp.hdcp_state != HDCP_ENABLE_PENDING)) { |
| if (status & HDMI_HPD_LOW) { |
| hdcp_lib_set_encryption(HDCP_ENC_OFF); |
| ddc_abort(); |
| } |
| |
| if (status & HDMI_RI_ERR) { |
| hdcp_lib_set_av_mute(AV_MUTE_SET); |
| hdcp_lib_set_encryption(HDCP_ENC_OFF); |
| hdcp_submit_work(HDCP_RI_FAIL_EVENT, 0); |
| } |
| /* RI error takes precedence over BCAP */ |
| else if (status & HDMI_BCAP) |
| hdcp_submit_work(HDCP_KSV_LIST_RDY_EVENT, 0); |
| } |
| |
| if (status & HDMI_HPD_LOW) { |
| ddc.pending_disable = 1; /* Used to exit on-going HDCP |
| * work */ |
| hdcp.hpd_low = 0; /* Used to cancel HDCP works */ |
| hdcp_lib_disable(); |
| ddc.pending_disable = 0; |
| /* In case of HDCP_STOP_FRAME_EVENT, HDCP stop |
| * frame callback is blocked and waiting for |
| * HDCP driver to finish accessing the HW |
| * before returning |
| * Reason is to avoid HDMI driver to shutdown |
| * DSS/HDMI power before HDCP work is finished |
| */ |
| hdcp.hdmi_state = HDMI_STOPPED; |
| hdcp.hdcp_state = HDCP_ENABLE_PENDING; |
| hdcp.auth_state = HDCP_STATE_DISABLED; |
| } |
| } |
| |
| /****************************************************************************** |
| * HDCP control from ioctl |
| *****************************************************************************/ |
| |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_enable_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_enable_ctl(void __user *argp) |
| { |
| DBG("hdcp_ioctl() - ENABLE %u", jiffies_to_msecs(jiffies)); |
| |
| if (hdcp.en_ctrl == 0) { |
| hdcp.en_ctrl = |
| kmalloc(sizeof(struct hdcp_enable_control), |
| GFP_KERNEL); |
| |
| if (hdcp.en_ctrl == 0) { |
| printk(KERN_WARNING |
| "HDCP: Cannot allocate memory for HDCP" |
| " enable control struct\n"); |
| return -EFAULT; |
| } |
| } |
| |
| if (copy_from_user(hdcp.en_ctrl, argp, |
| sizeof(struct hdcp_enable_control))) { |
| printk(KERN_WARNING "HDCP: Error copying from user space " |
| "- enable ioctl\n"); |
| return -EFAULT; |
| } |
| |
| /* Post event to workqueue */ |
| if (hdcp_submit_work(HDCP_ENABLE_CTL, 0) == 0) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_disable_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_disable_ctl(void) |
| { |
| DBG("hdcp_ioctl() - DISABLE %u", jiffies_to_msecs(jiffies)); |
| |
| hdcp_cancel_work(&hdcp.pending_start); |
| hdcp_cancel_work(&hdcp.pending_wq_event); |
| |
| ddc.pending_disable = 1; |
| /* Post event to workqueue */ |
| if (hdcp_submit_work(HDCP_DISABLE_CTL, 0) == 0) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_query_status_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_query_status_ctl(void __user *argp) |
| { |
| uint32_t *status = (uint32_t *)argp; |
| |
| DBG("hdcp_ioctl() - QUERY %u", jiffies_to_msecs(jiffies)); |
| |
| *status = hdcp.auth_state; |
| |
| return 0; |
| } |
| |
| static int hdcp_wait_re_entrance; |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_wait_event_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_wait_event_ctl(void __user *argp) |
| { |
| struct hdcp_wait_control ctrl; |
| |
| DBG("hdcp_ioctl() - WAIT %u %d", jiffies_to_msecs(jiffies), |
| hdcp.hdcp_up_event); |
| |
| if (copy_from_user(&ctrl, argp, |
| sizeof(struct hdcp_wait_control))) { |
| printk(KERN_WARNING "HDCP: Error copying from user space" |
| " - wait ioctl"); |
| return -EFAULT; |
| } |
| |
| if (hdcp_wait_re_entrance == 0) { |
| hdcp_wait_re_entrance = 1; |
| wait_event_interruptible(hdcp_up_wait_queue, |
| (hdcp.hdcp_up_event & 0xFF) != 0); |
| |
| ctrl.event = hdcp.hdcp_up_event; |
| |
| if ((ctrl.event & 0xFF) == HDCP_EVENT_STEP2) { |
| if (copy_to_user(ctrl.data, &sha_input, |
| sizeof(struct hdcp_sha_in))) { |
| printk(KERN_WARNING "HDCP: Error copying to " |
| "user space - wait ioctl"); |
| return -EFAULT; |
| } |
| } |
| |
| hdcp.hdcp_up_event = 0; |
| hdcp_wait_re_entrance = 0; |
| } else |
| ctrl.event = HDCP_EVENT_EXIT; |
| |
| /* Store output data to output pointer */ |
| if (copy_to_user(argp, &ctrl, |
| sizeof(struct hdcp_wait_control))) { |
| printk(KERN_WARNING "HDCP: Error copying to user space -" |
| " wait ioctl"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_done_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_done_ctl(void __user *argp) |
| { |
| uint32_t *status = (uint32_t *)argp; |
| |
| DBG("hdcp_ioctl() - DONE %u %d", jiffies_to_msecs(jiffies), *status); |
| |
| hdcp.hdcp_down_event &= ~(*status & 0xFF); |
| hdcp.hdcp_down_event |= *status & 0xFF00; |
| |
| wake_up_interruptible(&hdcp_down_wait_queue); |
| |
| return 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_encrypt_key_ctl |
| *----------------------------------------------------------------------------- |
| */ |
| static long hdcp_encrypt_key_ctl(void __user *argp) |
| { |
| struct hdcp_encrypt_control *ctrl; |
| uint32_t *out_key; |
| |
| DBG("hdcp_ioctl() - ENCRYPT KEY %u", jiffies_to_msecs(jiffies)); |
| |
| mutex_lock(&hdcp.lock); |
| |
| if (hdcp.hdcp_state != HDCP_DISABLED) { |
| printk(KERN_INFO "HDCP: Cannot encrypt keys while HDCP " |
| "is enabled\n"); |
| mutex_unlock(&hdcp.lock); |
| return -EFAULT; |
| } |
| |
| hdcp.hdcp_state = HDCP_KEY_ENCRYPTION_ONGOING; |
| |
| /* Encryption happens in ioctl / user context */ |
| ctrl = kmalloc(sizeof(struct hdcp_encrypt_control), |
| GFP_KERNEL); |
| |
| if (ctrl == 0) { |
| printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP" |
| " encryption control struct\n"); |
| mutex_unlock(&hdcp.lock); |
| return -EFAULT; |
| } |
| |
| out_key = kmalloc(sizeof(uint32_t) * |
| DESHDCP_KEY_SIZE, GFP_KERNEL); |
| |
| if (out_key == 0) { |
| printk(KERN_WARNING "HDCP: Cannot allocate memory for HDCP " |
| "encryption output key\n"); |
| kfree(ctrl); |
| mutex_unlock(&hdcp.lock); |
| return -EFAULT; |
| } |
| |
| if (copy_from_user(ctrl, argp, |
| sizeof(struct hdcp_encrypt_control))) { |
| printk(KERN_WARNING "HDCP: Error copying from user space" |
| " - encrypt ioctl\n"); |
| kfree(ctrl); |
| kfree(out_key); |
| mutex_unlock(&hdcp.lock); |
| return -EFAULT; |
| } |
| |
| hdcp_request_dss(); |
| |
| /* Call encrypt function */ |
| hdcp_3des_encrypt_key(ctrl, out_key); |
| |
| hdcp_release_dss(); |
| |
| hdcp.hdcp_state = HDCP_DISABLED; |
| mutex_unlock(&hdcp.lock); |
| |
| /* Store output data to output pointer */ |
| if (copy_to_user(ctrl->out_key, out_key, |
| sizeof(uint32_t)*DESHDCP_KEY_SIZE)) { |
| printk(KERN_WARNING "HDCP: Error copying to user space -" |
| " encrypt ioctl\n"); |
| kfree(ctrl); |
| kfree(out_key); |
| return -EFAULT; |
| } |
| |
| kfree(ctrl); |
| kfree(out_key); |
| return 0; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_ioctl |
| *----------------------------------------------------------------------------- |
| */ |
| long hdcp_ioctl(struct file *fd, unsigned int cmd, unsigned long arg) |
| { |
| void __user *argp = (void __user *)arg; |
| |
| switch (cmd) { |
| case HDCP_ENABLE: |
| return hdcp_enable_ctl(argp); |
| |
| case HDCP_DISABLE: |
| return hdcp_disable_ctl(); |
| |
| case HDCP_ENCRYPT_KEY: |
| return hdcp_encrypt_key_ctl(argp); |
| |
| case HDCP_QUERY_STATUS: |
| return hdcp_query_status_ctl(argp); |
| |
| case HDCP_WAIT_EVENT: |
| return hdcp_wait_event_ctl(argp); |
| |
| case HDCP_DONE: |
| return hdcp_done_ctl(argp); |
| |
| default: |
| return -ENOTTY; |
| } /* End switch */ |
| } |
| |
| |
| /****************************************************************************** |
| * HDCP driver init/exit |
| *****************************************************************************/ |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_mmap |
| *----------------------------------------------------------------------------- |
| */ |
| static int hdcp_mmap(struct file *filp, struct vm_area_struct *vma) |
| { |
| int status; |
| |
| DBG("hdcp_mmap() %lx %lx %lx\n", vma->vm_start, vma->vm_pgoff, |
| vma->vm_end - vma->vm_start); |
| |
| vma->vm_flags |= VM_IO | VM_RESERVED; |
| vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| |
| status = remap_pfn_range(vma, vma->vm_start, |
| HDMI_WP >> PAGE_SHIFT, |
| vma->vm_end - vma->vm_start, |
| vma->vm_page_prot); |
| if (status) { |
| DBG("mmap error %d\n", status); |
| return -EAGAIN; |
| } |
| |
| DBG("mmap succesfull\n"); |
| return 0; |
| } |
| |
| static struct file_operations hdcp_fops = { |
| .owner = THIS_MODULE, |
| .mmap = hdcp_mmap, |
| .unlocked_ioctl = hdcp_ioctl, |
| }; |
| |
| struct miscdevice mdev; |
| |
| static void hdcp_load_keys_cb(const struct firmware *fw, void *context) |
| { |
| struct hdcp_enable_control *en_ctrl; |
| |
| if (!fw) { |
| pr_err("HDCP: failed to load keys\n"); |
| goto out; |
| } |
| |
| if (fw->size != sizeof(en_ctrl->key)) { |
| pr_err("HDCP: encrypted key file wrong size %d\n", fw->size); |
| goto out; |
| } |
| |
| en_ctrl = kmalloc(sizeof(*en_ctrl), GFP_KERNEL); |
| if (!en_ctrl) { |
| pr_err("HDCP: can't allocated space for keys\n"); |
| goto out; |
| } |
| |
| memcpy(en_ctrl->key, fw->data, sizeof(en_ctrl->key)); |
| en_ctrl->nb_retry = HDCP_INFINITE_REAUTH; |
| |
| hdcp.en_ctrl = en_ctrl; |
| hdcp.retry_cnt = hdcp.en_ctrl->nb_retry; |
| hdcp.hdcp_state = HDCP_ENABLE_PENDING; |
| hdcp.hdcp_keys_loaded = true; |
| pr_info("HDCP: loaded keys\n"); |
| |
| out: |
| /* check hpd state and call handler if hpd is asserted */ |
| if (hdmi_get_current_hpd()) |
| hdmi_panel_hpd_handler(); |
| } |
| |
| static int hdcp_load_keys(void) |
| { |
| int ret; |
| |
| ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, |
| "hdcp.keys", mdev.this_device, GFP_KERNEL, |
| &hdcp, hdcp_load_keys_cb); |
| if (ret < 0) { |
| pr_err("HDCP: request_firmware_nowait failed: %d\n", ret); |
| hdcp.hdcp_keys_loaded = false; |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_init |
| *----------------------------------------------------------------------------- |
| */ |
| static int __init hdcp_init(void) |
| { |
| DBG("hdcp_init() %u", jiffies_to_msecs(jiffies)); |
| |
| /* Map HDMI WP address */ |
| hdcp.hdmi_wp_base_addr = ioremap(HDMI_WP, 0x1000); |
| |
| if (!hdcp.hdmi_wp_base_addr) { |
| printk(KERN_ERR "HDCP: HDMI WP IOremap error\n"); |
| return -EFAULT; |
| } |
| |
| /* Map DESHDCP in kernel address space */ |
| hdcp.deshdcp_base_addr = ioremap(DSS_SS_FROM_L3__DESHDCP, 0x34); |
| |
| if (!hdcp.deshdcp_base_addr) { |
| printk(KERN_ERR "HDCP: DESHDCP IOremap error\n"); |
| goto err_map_deshdcp; |
| } |
| |
| mutex_init(&hdcp.lock); |
| |
| mdev.minor = MISC_DYNAMIC_MINOR; |
| mdev.name = "hdcp"; |
| mdev.mode = 0666; |
| mdev.fops = &hdcp_fops; |
| |
| if (misc_register(&mdev)) { |
| printk(KERN_ERR "HDCP: Could not add character driver\n"); |
| goto err_register; |
| } |
| |
| mutex_lock(&hdcp.lock); |
| |
| /* Variable init */ |
| hdcp.en_ctrl = 0; |
| hdcp.hdcp_state = HDCP_DISABLED; |
| hdcp.pending_start = 0; |
| hdcp.pending_wq_event = 0; |
| hdcp.retry_cnt = 0; |
| hdcp.auth_state = HDCP_STATE_DISABLED; |
| ddc.pending_disable = 0; |
| hdcp.hdcp_up_event = 0; |
| hdcp.hdcp_down_event = 0; |
| hdcp_wait_re_entrance = 0; |
| hdcp.hpd_low = 0; |
| |
| spin_lock_init(&hdcp.spinlock); |
| |
| init_completion(&hdcp_comp); |
| |
| hdcp.workqueue = create_singlethread_workqueue("hdcp"); |
| if (hdcp.workqueue == NULL) |
| goto err_add_driver; |
| |
| hdcp_request_dss(); |
| |
| /* Register HDCP callbacks to HDMI library */ |
| if (omapdss_hdmi_register_hdcp_callbacks(&hdcp_start_frame_cb, |
| &hdcp_irq_cb, |
| &hdcp_3des_cb)) |
| hdcp.hdmi_state = HDMI_STARTED; |
| else |
| hdcp.hdmi_state = HDMI_STOPPED; |
| |
| hdcp_release_dss(); |
| |
| mutex_unlock(&hdcp.lock); |
| |
| hdcp_load_keys(); |
| |
| return 0; |
| |
| err_add_driver: |
| misc_deregister(&mdev); |
| |
| err_register: |
| mutex_destroy(&hdcp.lock); |
| |
| iounmap(hdcp.deshdcp_base_addr); |
| |
| err_map_deshdcp: |
| iounmap(hdcp.hdmi_wp_base_addr); |
| |
| return -EFAULT; |
| } |
| |
| /*----------------------------------------------------------------------------- |
| * Function: hdcp_exit |
| *----------------------------------------------------------------------------- |
| */ |
| static void __exit hdcp_exit(void) |
| { |
| DBG("hdcp_exit() %u", jiffies_to_msecs(jiffies)); |
| |
| mutex_lock(&hdcp.lock); |
| |
| kfree(hdcp.en_ctrl); |
| |
| hdcp_request_dss(); |
| |
| /* Un-register HDCP callbacks to HDMI library */ |
| omapdss_hdmi_register_hdcp_callbacks(0, 0, 0); |
| |
| hdcp_release_dss(); |
| |
| misc_deregister(&mdev); |
| |
| /* Unmap HDMI WP / DESHDCP */ |
| iounmap(hdcp.hdmi_wp_base_addr); |
| iounmap(hdcp.deshdcp_base_addr); |
| |
| destroy_workqueue(hdcp.workqueue); |
| |
| mutex_unlock(&hdcp.lock); |
| |
| mutex_destroy(&hdcp.lock); |
| } |
| |
| /*----------------------------------------------------------------------------- |
| *----------------------------------------------------------------------------- |
| */ |
| module_init(hdcp_init); |
| module_exit(hdcp_exit); |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("OMAP HDCP kernel module"); |
| MODULE_AUTHOR("Fabrice Olivero"); |