blob: d7c2284ae304600ac700b28648cfc4a65efc2b64 [file] [log] [blame]
/*
* 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");