1706 lines
43 KiB
C
1706 lines
43 KiB
C
/*
|
|
|
|
This file is provided under a dual BSD/GPLv2 license. When using or
|
|
redistributing this file, you may do so under either license.
|
|
|
|
GPL LICENSE SUMMARY
|
|
|
|
Copyright(c) 2011 Intel Corporation. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License 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, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
|
The full GNU General Public License is included in this distribution
|
|
in the file called LICENSE.GPL.
|
|
|
|
Contact Information:
|
|
|
|
Intel Corporation
|
|
2200 Mission College Blvd.
|
|
Santa Clara, CA 95054
|
|
|
|
BSD LICENSE
|
|
|
|
Copyright(c) 2011 Intel Corporation. All rights reserved.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in
|
|
the documentation and/or other materials provided with the
|
|
distribution.
|
|
* Neither the name of Intel Corporation nor the names of its
|
|
contributors may be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/types.h>
|
|
#include "hdcp_rx_defs.h"
|
|
#include "otm_hdmi.h"
|
|
#include "otm_hdmi_types.h"
|
|
#include "ipil_hdcp_api.h"
|
|
#include "psb_powermgmt.h"
|
|
#include "ipil_hdmi.h"
|
|
#include "hdcp_api.h"
|
|
|
|
#define OTM_HDCP_DEBUG_MODULE
|
|
|
|
#ifdef OTM_HDCP_DEBUG_MODULE
|
|
bool module_disable_hdcp = false;
|
|
EXPORT_SYMBOL_GPL(module_disable_hdcp);
|
|
bool module_force_ri_mismatch = false;
|
|
EXPORT_SYMBOL_GPL(module_force_ri_mismatch);
|
|
#endif
|
|
|
|
enum {
|
|
HDCP_ENABLE = 1,
|
|
HDCP_RESET,
|
|
HDCP_RI_CHECK,
|
|
HDCP_REPEATER_CHECK,
|
|
HDCP_REPEATER_WDT_EXPIRED,
|
|
HDCP_SET_POWER_SAVE_STATUS,
|
|
HDCP_SET_HPD_STATUS,
|
|
HDCP_SET_DPMS_STATUS
|
|
} hdcp_task_msg_en;
|
|
|
|
struct hdcp_wq_struct_t {
|
|
struct delayed_work dwork;
|
|
int msg;
|
|
void *msg_data;
|
|
};
|
|
|
|
/* = = = = = = = = = = = = = = = = == = = = = = = = = = = = = = = = = = = = = */
|
|
/*! \brief Our local context.
|
|
*/
|
|
struct hdcp_context_t {
|
|
int can_authenticate;
|
|
/*!< indicates HDCP Authentication currently allowed
|
|
*/
|
|
bool is_required;
|
|
bool is_phase1_enabled;
|
|
bool is_phase2_enabled;
|
|
bool is_phase3_valid;
|
|
bool previous_phase1_status;
|
|
bool suspend;
|
|
bool hpd;
|
|
bool previous_hpd;
|
|
bool display_power_on;
|
|
bool auto_retry;
|
|
bool wdt_expired;
|
|
bool sink_query /* used to unconditionally read sink bksv */;
|
|
bool force_reset;
|
|
unsigned int auth_id;
|
|
/*!< id that indicates current
|
|
* authentication attempt
|
|
*/
|
|
unsigned int ri_check_interval;
|
|
/*!< phase 3 ri-check interval based on mode */
|
|
unsigned int ri_check_interval_upper;
|
|
/*!< upper bound of ri-check interval */
|
|
unsigned int ri_check_interval_lower;
|
|
/*!< lower bound of ri-check interval */
|
|
unsigned int video_refresh_interval;
|
|
/*!< time interval (msec) of video refresh. */
|
|
unsigned int prev_ri_frm_cnt_status;
|
|
/*!< Ri frame count in HDCP status register when
|
|
* doing previous Ri check. */
|
|
unsigned int current_srm_ver;
|
|
/*!< currently used SRM version (if vrl is not null)
|
|
*/
|
|
uint64_t *vrl;
|
|
/*!< pointer to our VRL, formatted as array of KSVs
|
|
* as hdcp__u64_t's
|
|
*/
|
|
unsigned int vrl_count;
|
|
/*!< total number of KSVs in our VRL */
|
|
int (*ddc_read_write)(bool, uint8_t, uint8_t, uint8_t *, int);
|
|
/*!< Pointer to callback function for DDC Read Write */
|
|
struct workqueue_struct *hdcp_wq;
|
|
/*!< single-thread workqueue handling HDCP events */
|
|
unsigned int ri_retry;
|
|
/*!< time delay (msec) to re-try Ri check */
|
|
unsigned int hdcp_delay;
|
|
/*!< time delay (msec) to wait for TMDS to be ready */
|
|
bool hdmi; /* HDMI or DVI*/
|
|
|
|
uint8_t bksv[HDCP_KSV_SIZE];
|
|
};
|
|
|
|
/* Global instance of local context */
|
|
static struct hdcp_context_t *hdcp_context;
|
|
|
|
/* HDCP Main Event Handler */
|
|
static void hdcp_task_event_handler(struct work_struct *work);
|
|
|
|
/**
|
|
* Description: this function sends a message to the hdcp workqueue to be
|
|
* processed with a delay
|
|
*
|
|
* @msg message type
|
|
* @msg_data any additional data accompanying the message
|
|
* @delay amount of delay for before the message gets processed
|
|
*
|
|
* Returns: true if message was successfully queued else false
|
|
*/
|
|
static bool wq_send_message_delayed(int msg,
|
|
void *msg_data,
|
|
unsigned long delay)
|
|
{
|
|
struct hdcp_wq_struct_t *hwq = NULL;
|
|
hwq = kmalloc(sizeof(struct hdcp_wq_struct_t), GFP_KERNEL);
|
|
if (hwq == NULL) {
|
|
if (msg_data)
|
|
kfree(msg_data);
|
|
return false;
|
|
}
|
|
|
|
hwq->msg = msg;
|
|
hwq->msg_data = msg_data;
|
|
|
|
INIT_DELAYED_WORK(&hwq->dwork, hdcp_task_event_handler);
|
|
|
|
if (queue_delayed_work(hdcp_context->hdcp_wq, &hwq->dwork,
|
|
(unsigned long)(msecs_to_jiffies(delay))) != 0)
|
|
return true;
|
|
else
|
|
pr_debug("hdcp: failed to add message to delayed wq\n");
|
|
|
|
if (msg_data)
|
|
kfree(msg_data);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function sends a message to the hdcp workqueue to be
|
|
* processed without delay
|
|
*
|
|
* @msg message type
|
|
* @msg_data any additional data accompanying the message
|
|
*
|
|
* Returns: true if message was successfully queued else false
|
|
*/
|
|
static bool wq_send_message(int msg, void *msg_data)
|
|
{
|
|
return wq_send_message_delayed(msg, msg_data, 0);
|
|
}
|
|
|
|
/**
|
|
* Description: this function verifies all conditions to enable hdcp
|
|
*
|
|
* Returns: true if hdcp can be enabled else false
|
|
*/
|
|
static bool hdcp_enable_condition_ready(void)
|
|
{
|
|
if (hdcp_context != NULL &&
|
|
hdcp_context->can_authenticate == true &&
|
|
hdcp_context->is_required == true &&
|
|
hdcp_context->suspend == false &&
|
|
hdcp_context->hpd == true &&
|
|
hdcp_context->display_power_on == true)
|
|
return true;
|
|
|
|
if (hdcp_context == NULL) {
|
|
pr_err("hdcp: hdcp_context is NULL\n");
|
|
} else {
|
|
pr_err("hdcp: condition not ready, required %d, hpd %d\n",
|
|
hdcp_context->is_required, hdcp_context->hpd);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads data from downstream i2c hdcp device
|
|
*
|
|
* @offset offset address on hdcp device
|
|
* @buffer buffer to store data
|
|
* @size size of buffer to be read
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static int hdcp_ddc_read(uint8_t offset, uint8_t *buffer, int size)
|
|
{
|
|
if (hdcp_enable_condition_ready() == true ||
|
|
(hdcp_context->sink_query == true &&
|
|
offset == HDCP_RX_BKSV_ADDR))
|
|
return hdcp_context->ddc_read_write(true,
|
|
HDCP_PRIMARY_I2C_ADDR, offset, buffer, size);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function writes data to downstream i2c hdcp device
|
|
*
|
|
* @offset offset address on hdcp device
|
|
* @buffer data to be written
|
|
* @size size of data to be written
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static int hdcp_ddc_write(uint8_t offset, uint8_t *buffer, int size)
|
|
{
|
|
if (hdcp_enable_condition_ready() == true)
|
|
return hdcp_context->ddc_read_write(false,
|
|
HDCP_PRIMARY_I2C_ADDR, offset, buffer, size);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function converts ksv byte buffer into 64 bit number
|
|
*
|
|
* @ksv ksv value
|
|
* @size size of the ksv
|
|
*
|
|
* Returns: 64bit value of the ksv
|
|
*/
|
|
static uint64_t hdcp_ksv_64val_conv(uint8_t *ksv, uint32_t size)
|
|
{
|
|
int i = 0;
|
|
uint64_t ksv64 = 0;
|
|
if (ksv != NULL && size == HDCP_KSV_SIZE) {
|
|
for (i = 0; i < 5; i++)
|
|
ksv64 |= (ksv[i] << (i*8));
|
|
}
|
|
return ksv64;
|
|
}
|
|
|
|
/**
|
|
* Description: this function validates a ksv value
|
|
* 1. 20 1's & 20 o's
|
|
* 2. SRM check: check for revoked keys
|
|
*
|
|
* @ksv ksv value
|
|
* @size size of the ksv
|
|
*
|
|
* Returns: true if valid else false
|
|
*/
|
|
static bool hdcp_validate_ksv(uint8_t *ksv, uint32_t size)
|
|
{
|
|
int i = 0, count = 0;
|
|
uint8_t temp = 0;
|
|
uint64_t ksv64 = hdcp_ksv_64val_conv(ksv, size);
|
|
bool ret = false;
|
|
if (ksv != NULL && size == HDCP_KSV_SIZE) {
|
|
count = 0;
|
|
for (i = 0; i < 5; i++) {
|
|
temp = ksv[i];
|
|
while (temp) {
|
|
temp &= (temp-1);
|
|
count++;
|
|
}
|
|
}
|
|
if (count == HDCP_KSV_HAMMING_WT)
|
|
ret = true;
|
|
}
|
|
if (ret) {
|
|
/* SRM Check ? */
|
|
if (hdcp_context->vrl != NULL) {
|
|
const uint64_t *vrl = hdcp_context->vrl;
|
|
for (i = 0; i < hdcp_context->vrl_count; i++, vrl++) {
|
|
if (ksv64 == *vrl)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads aksv from local hdcp tx device
|
|
*
|
|
* @aksv buffer to store aksv
|
|
* @size size of the aksv buffer
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_get_aksv(uint8_t *aksv, uint32_t size)
|
|
{
|
|
bool ret = false;
|
|
if (ipil_hdcp_get_aksv(aksv, HDCP_KSV_SIZE) == true) {
|
|
if (hdcp_validate_ksv(aksv, size) == true)
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads bksv from downstream device
|
|
*
|
|
* @bksv buffer to store bksv
|
|
* @size size of the bksv buffer
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_bksv(uint8_t *bksv, uint32_t size)
|
|
{
|
|
bool ret = false;
|
|
if (bksv != NULL && size == HDCP_KSV_SIZE) {
|
|
if (hdcp_ddc_read(HDCP_RX_BKSV_ADDR,
|
|
bksv, HDCP_KSV_SIZE) == true) {
|
|
if (hdcp_validate_ksv(bksv, size) == true)
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads bcaps from downstream device
|
|
*
|
|
* @bcaps buffer to store bcaps
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_bcaps(uint8_t *bcaps)
|
|
{
|
|
bool ret = false;
|
|
if (bcaps != NULL) {
|
|
if (hdcp_ddc_read(HDCP_RX_BCAPS_ADDR,
|
|
bcaps, HDCP_RX_BCAPS_SIZE) == true)
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads bstatus from downstream device
|
|
*
|
|
* @bstatus buffer to store bstatus
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_bstatus(uint16_t *bstatus)
|
|
{
|
|
bool ret = false;
|
|
if (bstatus != NULL) {
|
|
if (hdcp_ddc_read(HDCP_RX_BSTATUS_ADDR,
|
|
(uint8_t *)bstatus, HDCP_RX_BSTATUS_SIZE) == true)
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads ri from downstream device
|
|
*
|
|
* @rx_ri buffer to store ri
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_rx_ri(uint16_t *rx_ri)
|
|
{
|
|
bool ret = false;
|
|
if (rx_ri != NULL) {
|
|
if (hdcp_ddc_read(HDCP_RX_RI_ADDR,
|
|
(uint8_t *)rx_ri, HDCP_RI_SIZE) == true)
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads r0 from downstream device
|
|
*
|
|
* @rx_r0 buffer to store r0
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_rx_r0(uint16_t *rx_r0)
|
|
{
|
|
return hdcp_read_rx_ri(rx_r0);
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads all ksv's from downstream repeater
|
|
*
|
|
* @ksv_list buffer to store ksv list
|
|
* @size size of the ksv_list to read into the buffer
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_rx_ksv_list(uint8_t *ksv_list, uint32_t size)
|
|
{
|
|
bool ret = false;
|
|
if (ksv_list != NULL && size) {
|
|
if (hdcp_ddc_read(HDCP_RX_KSV_FIFO_ADDR,
|
|
ksv_list, size) == true) {
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function reads sha1 value from downstream device
|
|
*
|
|
* @v buffer to store the sha1 value
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_read_rx_v(uint8_t *v)
|
|
{
|
|
bool ret = false;
|
|
uint8_t *buf = v;
|
|
uint8_t offset = HDCP_RX_V_H0_ADDR;
|
|
|
|
if (v != NULL) {
|
|
for (; offset <= HDCP_RX_V_H4_ADDR; offset += 4) {
|
|
if (hdcp_ddc_read(offset, buf, 4) == false) {
|
|
pr_debug("hdcp: read rx v failure\n");
|
|
break;
|
|
}
|
|
buf += 4;
|
|
}
|
|
if (offset > HDCP_RX_V_H4_ADDR)
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function performs ri match
|
|
*
|
|
* Returns: true on match else false
|
|
*/
|
|
static bool hdcp_stage3_ri_check(void)
|
|
{
|
|
uint16_t rx_ri = 0;
|
|
|
|
if (hdcp_enable_condition_ready() == false ||
|
|
hdcp_context->is_phase1_enabled == false)
|
|
return false;
|
|
|
|
#ifdef OTM_HDCP_DEBUG_MODULE
|
|
if (module_force_ri_mismatch) {
|
|
pr_debug("hdcp: force Ri mismatch\n");
|
|
module_force_ri_mismatch = false;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (hdcp_read_rx_ri(&rx_ri) == true) {
|
|
if (ipil_hdcp_does_ri_match(rx_ri) == true)
|
|
/* pr_debug("hdcp: Ri Matches %04x\n", rx_ri);*/
|
|
return true;
|
|
|
|
/* If first Ri check fails,we re-check it after ri_retry (msec).
|
|
* This is because some receivers do not immediately have valid
|
|
* Ri' at frame 128.
|
|
* */
|
|
pr_debug("re-check Ri after %d (msec)\n",
|
|
hdcp_context->ri_retry);
|
|
|
|
msleep(hdcp_context->ri_retry);
|
|
if (hdcp_read_rx_ri(&rx_ri) == true)
|
|
if (ipil_hdcp_does_ri_match(rx_ri) == true)
|
|
return true;
|
|
}
|
|
|
|
/* ri check failed update phase3 status */
|
|
hdcp_context->is_phase3_valid = false;
|
|
|
|
pr_debug("hdcp: error!!! Ri check failed %x\n", rx_ri);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function sends an aksv to downstream device
|
|
*
|
|
* @an AN value to send
|
|
* @an_size size of an
|
|
* @aksv AKSV value to send
|
|
* @aksv_size size of aksv
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_send_an_aksv(uint8_t *an, uint8_t an_size,
|
|
uint8_t *aksv, uint8_t aksv_size)
|
|
{
|
|
bool ret = false;
|
|
if (an != NULL && an_size == HDCP_AN_SIZE &&
|
|
aksv != NULL && aksv_size == HDCP_KSV_SIZE) {
|
|
if (hdcp_ddc_write(HDCP_RX_AN_ADDR, an, HDCP_AN_SIZE) ==
|
|
true) {
|
|
/* wait 20ms for i2c write for An to complete */
|
|
/* msleep(20); */
|
|
if (hdcp_ddc_write(HDCP_RX_AKSV_ADDR, aksv,
|
|
HDCP_KSV_SIZE) == true)
|
|
ret = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: this function resets hdcp state machine to initial state
|
|
*
|
|
* Returns: none
|
|
*/
|
|
static void hdcp_reset(void)
|
|
{
|
|
pr_debug("hdcp: reset\n");
|
|
|
|
/* blank TV screen */
|
|
ipil_enable_planes_on_pipe(1, false);
|
|
|
|
/* Stop HDCP */
|
|
if (hdcp_context->is_phase1_enabled == true ||
|
|
hdcp_context->force_reset == true) {
|
|
pr_debug("hdcp: off state\n");
|
|
ipil_hdcp_disable();
|
|
hdcp_context->force_reset = false;
|
|
}
|
|
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
/* this flag will be re-enabled by upper layers */
|
|
hdcp_context->is_required = false;
|
|
#endif
|
|
hdcp_context->is_phase1_enabled = false;
|
|
hdcp_context->is_phase2_enabled = false;
|
|
hdcp_context->is_phase3_valid = false;
|
|
hdcp_context->prev_ri_frm_cnt_status = 0;
|
|
memset(hdcp_context->bksv, 0, HDCP_KSV_SIZE);
|
|
}
|
|
|
|
/**
|
|
* Description: this function schedules repeater authentication
|
|
*
|
|
* @first whether its first time schedule or not, delay for check is
|
|
* varied between first and subsequent times
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_rep_check(bool first)
|
|
{
|
|
int msg = HDCP_REPEATER_CHECK;
|
|
int delay = (first) ? 50 : 100;
|
|
unsigned int *auth_id = kmalloc(sizeof(unsigned int), GFP_KERNEL);
|
|
if (auth_id != NULL) {
|
|
*auth_id = hdcp_context->auth_id;
|
|
return wq_send_message_delayed(msg, (void *)auth_id, delay);
|
|
} else
|
|
pr_debug("hdcp: %s failed to alloc mem\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function creates a watch dog timer for repeater auth
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_rep_watch_dog(void)
|
|
{
|
|
int msg = HDCP_REPEATER_WDT_EXPIRED;
|
|
unsigned int *auth_id = kmalloc(sizeof(unsigned int), GFP_KERNEL);
|
|
if (auth_id != NULL) {
|
|
*auth_id = hdcp_context->auth_id;
|
|
/* set a watch dog timer for 5.2 secs, added additional
|
|
0.2 seconds to be safe */
|
|
return wq_send_message_delayed(msg, (void *)auth_id, 5200);
|
|
} else
|
|
pr_debug("hdcp: %s failed to alloc mem\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function initiates repeater authentication
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_initiate_rep_auth(void)
|
|
{
|
|
pr_debug("hdcp: initiating repeater check\n");
|
|
hdcp_context->wdt_expired = false;
|
|
if (hdcp_rep_check(true) == true) {
|
|
if (hdcp_rep_watch_dog() == true)
|
|
return true;
|
|
else
|
|
pr_debug("hdcp: failed to start repeater wdt\n");
|
|
} else
|
|
pr_debug("hdcp: failed to start repeater check\n");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function schedules ri check
|
|
*
|
|
* @first_check whether its the first time, interval is varied if first time
|
|
*
|
|
* Returns: true on success else false
|
|
*/
|
|
static bool hdcp_stage3_schedule_ri_check(bool first_check)
|
|
{
|
|
int msg = HDCP_RI_CHECK;
|
|
unsigned int *msg_data = kmalloc(sizeof(unsigned int), GFP_KERNEL);
|
|
/* Do the first check immediately while adding some randomness */
|
|
int ri_check_interval = (first_check) ? (20 + (jiffies % 10)) :
|
|
hdcp_context->ri_check_interval;
|
|
if (msg_data != NULL) {
|
|
*msg_data = hdcp_context->auth_id;
|
|
return wq_send_message_delayed(msg, (void *)msg_data,
|
|
ri_check_interval);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: this function performs 1st stage HDCP authentication i.e.
|
|
* exchanging keys and r0 match
|
|
*
|
|
* @is_repeater variable to return type of downstream device, i.e. repeater
|
|
* or not
|
|
*
|
|
* Returns: true on successful authentication else false
|
|
*/
|
|
static bool hdcp_stage1_authentication(bool *is_repeater)
|
|
{
|
|
uint8_t bksv[HDCP_KSV_SIZE], aksv[HDCP_KSV_SIZE], an[HDCP_AN_SIZE];
|
|
struct hdcp_rx_bstatus_t bstatus;
|
|
struct hdcp_rx_bcaps_t bcaps;
|
|
uint8_t retry = 0;
|
|
uint16_t rx_r0 = 0;
|
|
|
|
/* Wait (up to 2s) for HDMI sink to be in HDMI mode */
|
|
retry = 40;
|
|
if (hdcp_context->hdmi) {
|
|
while (--retry) {
|
|
if (hdcp_read_bstatus(&bstatus.value) == false) {
|
|
pr_err("hdcp: failed to read bstatus\n");
|
|
return false;
|
|
}
|
|
if (bstatus.hdmi_mode)
|
|
break;
|
|
msleep(50);
|
|
pr_debug("hdcp: waiting for sink to be in HDMI mode\n");
|
|
}
|
|
}
|
|
|
|
if (retry == 0)
|
|
pr_err("hdcp: sink is not in HDMI mode\n");
|
|
|
|
pr_debug("hdcp: bstatus: %04x\n", bstatus.value);
|
|
|
|
/* Read BKSV */
|
|
if (hdcp_read_bksv(bksv, HDCP_KSV_SIZE) == false) {
|
|
pr_err("hdcp: failed to read bksv\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: bksv: %02x%02x%02x%02x%02x\n",
|
|
bksv[0], bksv[1], bksv[2], bksv[3], bksv[4]);
|
|
|
|
memcpy(hdcp_context->bksv, bksv, HDCP_KSV_SIZE);
|
|
|
|
/* Read An */
|
|
if (ipil_hdcp_get_an(an, HDCP_AN_SIZE) == false) {
|
|
pr_err("hdcp: failed to get an\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: an: %02x%02x%02x%02x%02x%02x%02x%02x\n",
|
|
an[0], an[1], an[2], an[3], an[4], an[5], an[6], an[7]);
|
|
|
|
/* Read AKSV */
|
|
if (hdcp_get_aksv(aksv, HDCP_KSV_SIZE) == false) {
|
|
pr_err("hdcp: failed to get aksv\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: aksv: %02x%02x%02x%02x%02x\n",
|
|
aksv[0], aksv[1], aksv[2], aksv[3], aksv[4]);
|
|
|
|
/* Write An AKSV to Downstream Rx */
|
|
if (hdcp_send_an_aksv(an, HDCP_AN_SIZE, aksv, HDCP_KSV_SIZE)
|
|
== false) {
|
|
pr_err("hdcp: failed to send an and aksv\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: sent an aksv\n");
|
|
|
|
/* Read BKSV */
|
|
if (hdcp_read_bksv(bksv, HDCP_KSV_SIZE) == false) {
|
|
pr_err("hdcp: failed to read bksv\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: bksv: %02x%02x%02x%02x%02x\n",
|
|
bksv[0], bksv[1], bksv[2], bksv[3], bksv[4]);
|
|
|
|
/* Read BCAPS */
|
|
if (hdcp_read_bcaps(&bcaps.value) == false) {
|
|
pr_err("hdcp: failed to read bcaps\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: bcaps: %x\n", bcaps.value);
|
|
|
|
|
|
/* Update repeater present status */
|
|
*is_repeater = bcaps.is_repeater;
|
|
|
|
/* Set Repeater Bit */
|
|
if (ipil_hdcp_set_repeater(bcaps.is_repeater) == false) {
|
|
pr_err("hdcp: failed to set repeater bit\n");
|
|
return false;
|
|
}
|
|
|
|
/* Write BKSV to Self (hdcp tx) */
|
|
if (ipil_hdcp_set_bksv(bksv) == false) {
|
|
pr_err("hdcp: failed to write bksv to self\n");
|
|
return false;
|
|
}
|
|
|
|
pr_debug("hdcp: set repeater & bksv\n");
|
|
|
|
/* Start Authentication i.e. computations using hdcp keys */
|
|
if (ipil_hdcp_start_authentication() == false) {
|
|
pr_err("hdcp: failed to start authentication\n");
|
|
return false;
|
|
}
|
|
|
|
pr_debug("hdcp: auth started\n");
|
|
|
|
/* Wait for 120ms before reading R0' */
|
|
msleep(120);
|
|
|
|
/* Check if R0 Ready in hdcp tx */
|
|
retry = 20;
|
|
do {
|
|
if (ipil_hdcp_is_r0_ready() == true)
|
|
break;
|
|
msleep(5);
|
|
retry--;
|
|
} while (retry);
|
|
|
|
if (retry == 0 && ipil_hdcp_is_r0_ready() == false) {
|
|
pr_err("hdcp: R0 is not ready\n");
|
|
return false;
|
|
}
|
|
|
|
pr_debug("hdcp: tx_r0 ready\n");
|
|
|
|
/* Read Ro' from Receiver hdcp rx */
|
|
if (hdcp_read_rx_r0(&rx_r0) == false) {
|
|
pr_err("hdcp: failed to read R0 from receiver\n");
|
|
return false;
|
|
}
|
|
|
|
pr_debug("hdcp: rx_r0 = %04x\n", rx_r0);
|
|
|
|
/* Check if R0 Matches */
|
|
if (ipil_hdcp_does_ri_match(rx_r0) == false) {
|
|
pr_err("hdcp: R0 does not match\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: R0 matched\n");
|
|
|
|
/* Enable Encryption & Check status */
|
|
if (ipil_hdcp_enable_encryption() == false) {
|
|
pr_err("hdcp: failed to enable encryption\n");
|
|
return false;
|
|
}
|
|
pr_debug("hdcp: encryption enabled\n");
|
|
|
|
hdcp_context->is_phase1_enabled = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Description: this function performs repeater authentication i.e. 2nd
|
|
* stage HDCP authentication
|
|
*
|
|
* Returns: true if repeater authentication is in progress or succesful
|
|
* else false. If in progress repeater authentication would be
|
|
* rescheduled
|
|
*/
|
|
static bool hdcp_stage2_repeater_authentication(void)
|
|
{
|
|
uint8_t *rep_ksv_list = NULL;
|
|
uint32_t rep_prime_v[HDCP_V_H_SIZE] = {0};
|
|
struct hdcp_rx_bstatus_t bstatus;
|
|
struct hdcp_rx_bcaps_t bcaps;
|
|
bool ret = false;
|
|
|
|
/* Repeater Authentication */
|
|
if (hdcp_enable_condition_ready() == false ||
|
|
hdcp_context->is_phase1_enabled == false ||
|
|
hdcp_context->wdt_expired == true) {
|
|
pr_debug("hdcp: stage2 auth condition not ready\n");
|
|
return false;
|
|
}
|
|
|
|
/* Read BCAPS */
|
|
if (hdcp_read_bcaps(&bcaps.value) == false)
|
|
return false;
|
|
|
|
if (!bcaps.is_repeater)
|
|
return false;
|
|
|
|
/* Check if fifo ready */
|
|
if (!bcaps.ksv_fifo_ready) {
|
|
/* not ready: reschedule but return true */
|
|
pr_debug("hdcp: rescheduling repeater auth\n");
|
|
hdcp_rep_check(false);
|
|
return true;
|
|
}
|
|
|
|
/* Read BSTATUS */
|
|
if (hdcp_read_bstatus(&bstatus.value) == false)
|
|
return false;
|
|
|
|
/* Check validity of repeater depth & device count */
|
|
if (bstatus.max_devs_exceeded)
|
|
return false;
|
|
|
|
if (bstatus.max_cascade_exceeded)
|
|
return false;
|
|
|
|
if (0 == bstatus.device_count)
|
|
return true;
|
|
|
|
if (bstatus.device_count > HDCP_MAX_DEVICES)
|
|
return false;
|
|
|
|
/* allocate memory for ksv_list */
|
|
rep_ksv_list = kzalloc(bstatus.device_count * HDCP_KSV_SIZE,
|
|
GFP_KERNEL);
|
|
if (!rep_ksv_list) {
|
|
pr_debug("hdcp: rep ksv list alloc failure\n");
|
|
return false;
|
|
}
|
|
|
|
/* Read ksv list from repeater */
|
|
if (hdcp_read_rx_ksv_list(rep_ksv_list,
|
|
bstatus.device_count * HDCP_KSV_SIZE)
|
|
== false) {
|
|
pr_debug("hdcp: rep ksv list read failure\n");
|
|
goto exit;
|
|
}
|
|
|
|
/* TODO: SRM check */
|
|
|
|
/* Compute tx sha1 (V) */
|
|
if (ipil_hdcp_compute_tx_v(rep_ksv_list, bstatus.device_count,
|
|
bstatus.value) == false) {
|
|
pr_debug("hdcp: rep compute tx v failure\n");
|
|
goto exit;
|
|
}
|
|
|
|
/* Read rx sha1 (V') */
|
|
if (hdcp_read_rx_v((uint8_t *)rep_prime_v) == false) {
|
|
pr_debug("hdcp: rep read rx v failure\n");
|
|
goto exit;
|
|
}
|
|
|
|
/* Verify SHA1 tx(V) = rx(V') */
|
|
if (ipil_hdcp_compare_v(rep_prime_v) == false) {
|
|
pr_debug("hdcp: rep compare v failure\n");
|
|
goto exit;
|
|
}
|
|
|
|
pr_debug("hdcp: repeater auth success\n");
|
|
hdcp_context->is_phase2_enabled = true;
|
|
ret = true;
|
|
|
|
exit:
|
|
kfree(rep_ksv_list);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: Main function that initiates all stages of HDCP authentication
|
|
*
|
|
* Returns: true on succesful authentication else false
|
|
*/
|
|
static bool hdcp_start(void)
|
|
{
|
|
bool is_repeater = false;
|
|
|
|
/* Make sure TMDS is available
|
|
* Remove this delay since HWC already has the delay
|
|
*/
|
|
/* msleep(hdcp_context->hdcp_delay); */
|
|
|
|
pr_debug("hdcp: start\n");
|
|
|
|
/* Increment Auth Check Counter */
|
|
hdcp_context->auth_id++;
|
|
|
|
/* blank TV screen */
|
|
ipil_enable_planes_on_pipe(1, false);
|
|
|
|
/* Check HDCP Status */
|
|
if (ipil_hdcp_is_ready() == false) {
|
|
pr_err("hdcp: hdcp is not ready\n");
|
|
return false;
|
|
}
|
|
|
|
/* start 1st stage of hdcp authentication */
|
|
if (hdcp_stage1_authentication(&is_repeater) == false) {
|
|
pr_debug("hdcp: stage 1 authentication fails\n");
|
|
return false;
|
|
}
|
|
|
|
/* un-blank TV screen */
|
|
ipil_enable_planes_on_pipe(1, true);
|
|
|
|
|
|
pr_debug("hdcp: initial authentication completed, repeater:%d\n",
|
|
is_repeater);
|
|
|
|
/* Branch Repeater Mode Authentication */
|
|
if (is_repeater == true)
|
|
if (hdcp_initiate_rep_auth() == false)
|
|
return false;
|
|
|
|
/* Initiate phase3_valid with true status */
|
|
hdcp_context->is_phase3_valid = true;
|
|
/* Branch Periodic Ri Check */
|
|
pr_debug("hdcp: starting periodic Ri check\n");
|
|
|
|
/* Schedule Ri check after 2 sec*/
|
|
if (hdcp_stage3_schedule_ri_check(false) == false) {
|
|
pr_err("hdcp: fail to schedule Ri check\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Description: verify conditions necessary for re-authentication and
|
|
* enable HDCP if favourable
|
|
*
|
|
* Returns: none
|
|
*/
|
|
static void hdcp_retry_enable(void)
|
|
{
|
|
int msg = HDCP_ENABLE;
|
|
if (hdcp_enable_condition_ready() == true &&
|
|
hdcp_context->is_phase1_enabled == false &&
|
|
hdcp_context->auto_retry == true) {
|
|
wq_send_message_delayed(msg, NULL, 30);
|
|
pr_debug("hdcp: retry enable\n");
|
|
}
|
|
}
|
|
|
|
/* Based on hardware Ri frame count, adjust ri_check_interval.
|
|
* Also, make sure Ri check happens right after Ri frame count
|
|
* becomes multiples of 128.
|
|
* */
|
|
static bool hdcp_ri_check_reschedule(void)
|
|
{
|
|
#define RI_FRAME_WAIT_LIMIT 150
|
|
|
|
struct hdcp_context_t *ctx = hdcp_context;
|
|
uint32_t prev_ri_frm_cnt_status = ctx->prev_ri_frm_cnt_status;
|
|
uint8_t ri_frm_cnt_status;
|
|
int32_t ri_frm_cnt;
|
|
int32_t adj; /* Adjustment of ri_check_interval in msec */
|
|
uint32_t cnt_ri_wait = 0;
|
|
bool ret = false;
|
|
|
|
|
|
/* Query hardware Ri frame counter.
|
|
* This value is used to adjust ri_check_interval
|
|
* */
|
|
ipil_hdcp_get_ri_frame_count(&ri_frm_cnt_status);
|
|
|
|
/* (frm_cnt_ri - prev_frm_cnt_ri) is expected to be 128. If not,
|
|
* we have to compensate the time difference, which is caused by async
|
|
* behavior of CPU clock, scheduler and HDMI clock. If hardware can
|
|
* provide interrupt signal for Ri check, then this compensation work
|
|
* can be avoided.
|
|
* Hardcode "256" is because hardware Ri frame counter is 8 bits.
|
|
* Hardcode "128" is based on HDCP spec.
|
|
* */
|
|
ri_frm_cnt = ri_frm_cnt_status >= prev_ri_frm_cnt_status ?
|
|
ri_frm_cnt_status - prev_ri_frm_cnt_status :
|
|
256 - prev_ri_frm_cnt_status + ri_frm_cnt_status;
|
|
pr_debug("current ri_frm_cnt = %d, previous ri_frm_cnt = %d\n",
|
|
ri_frm_cnt_status, prev_ri_frm_cnt_status);
|
|
|
|
/* Compute adjustment of ri_check_interval*/
|
|
adj = (128 - ri_frm_cnt) * hdcp_context->video_refresh_interval;
|
|
|
|
/* Adjust ri_check_interval */
|
|
/* adj<0: Ri check speed is slower than HDMI clock speed
|
|
* adj>0: Ri check speed is faster than HDMI clock speed
|
|
* */
|
|
pr_debug("adjustment of ri_check_interval = %d (ms)\n", adj);
|
|
ctx->ri_check_interval += adj;
|
|
if (ctx->ri_check_interval > ctx->ri_check_interval_upper)
|
|
ctx->ri_check_interval = ctx->ri_check_interval_upper;
|
|
|
|
if (ctx->ri_check_interval < ctx->ri_check_interval_lower)
|
|
ctx->ri_check_interval = ctx->ri_check_interval_lower;
|
|
|
|
pr_debug("ri_check_interval=%d(ms)\n", ctx->ri_check_interval);
|
|
|
|
/* Update prev_ri_frm_cnt_status*/
|
|
hdcp_context->prev_ri_frm_cnt_status = ri_frm_cnt_status;
|
|
|
|
/* Queue next Ri check task with new ri_check_interval*/
|
|
ret = hdcp_stage3_schedule_ri_check(false);
|
|
if (!ret)
|
|
goto exit;
|
|
|
|
/* Now, check if ri_frm_cnt_status is multiples of 128.
|
|
* If we are too fast, wait for frame 128 (or a few frames after
|
|
* frame 128) to happen to make sure Ri' is ready.
|
|
* Why using hardcode "64"? : if ri_frm_cnt_status is close to
|
|
* multiples of 128 (ie, ri_frm_cnt_status % 128 > 64), we keep waiting.
|
|
* Otherwise if ri_frm_cnt_status just passes 128
|
|
* (ie, ri_frm_cnt_status % 128 < 64) we continue.
|
|
* Note the assumption here is this thread is scheduled to run at least
|
|
* once in one 64-frame period.
|
|
*
|
|
* RI_FRAME_WAIT_LIMIT is in case HW stops updating ri frame
|
|
* count and causes infinite looping.
|
|
*/
|
|
while ((ri_frm_cnt_status % 128 >= 64) &&
|
|
(cnt_ri_wait < RI_FRAME_WAIT_LIMIT)) {
|
|
msleep(hdcp_context->video_refresh_interval);
|
|
ipil_hdcp_get_ri_frame_count(&ri_frm_cnt_status);
|
|
cnt_ri_wait++;
|
|
pr_debug("current Ri frame count = %d\n", ri_frm_cnt_status);
|
|
}
|
|
|
|
if (RI_FRAME_WAIT_LIMIT == cnt_ri_wait) {
|
|
ret = false;
|
|
goto exit;
|
|
}
|
|
|
|
/* Match Ri with Ri'*/
|
|
ret = hdcp_stage3_ri_check();
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: workqueue event handler to execute all hdcp tasks
|
|
*
|
|
* @work work assigned from workqueue contains the task to be handled
|
|
*
|
|
* Returns: none
|
|
*/
|
|
static void hdcp_task_event_handler(struct work_struct *work)
|
|
{
|
|
struct delayed_work *delayed_work = to_delayed_work(work);
|
|
struct hdcp_wq_struct_t *hwq = container_of(delayed_work,
|
|
struct hdcp_wq_struct_t,
|
|
dwork);
|
|
int msg = 0;
|
|
void *msg_data = NULL;
|
|
bool reset_hdcp = false;
|
|
struct hdcp_context_t *ctx = hdcp_context;
|
|
|
|
if (hwq != NULL) {
|
|
msg = hwq->msg;
|
|
msg_data = hwq->msg_data;
|
|
}
|
|
|
|
if (hdcp_context == NULL || hwq == NULL)
|
|
goto EXIT_HDCP_HANDLER;
|
|
|
|
if (!ospm_power_using_hw_begin(OSPM_DISPLAY_ISLAND,
|
|
OSPM_UHB_FORCE_POWER_ON)) {
|
|
pr_err("Unable to power on display island!");
|
|
goto EXIT_HDCP_HANDLER;
|
|
}
|
|
|
|
switch (msg) {
|
|
case HDCP_ENABLE:
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
if (hdcp_enable_condition_ready() == false) {
|
|
/* if enable condition is not ready have to return
|
|
* from hdcp_enable function immediately */
|
|
reset_hdcp = true;
|
|
break;
|
|
}
|
|
#endif
|
|
if (hdcp_enable_condition_ready() == true &&
|
|
hdcp_context->is_phase1_enabled == false &&
|
|
hdcp_start() == false) {
|
|
reset_hdcp = true;
|
|
hdcp_context->force_reset = true;
|
|
pr_debug("hdcp: failed to start hdcp\n");
|
|
}
|
|
break;
|
|
|
|
case HDCP_RI_CHECK:
|
|
/*pr_debug("hdcp: RI CHECK\n");*/
|
|
if (msg_data == NULL ||
|
|
*(unsigned int *)msg_data != hdcp_context->auth_id)
|
|
/*pr_debug("hdcp: auth count %d mismatch %d\n",
|
|
*(unsigned int *)msg_data,
|
|
hdcp_context->auth_id);*/
|
|
break;
|
|
|
|
/* Do phase 3 only if phase 1 was successful*/
|
|
if (hdcp_context->is_phase1_enabled == false)
|
|
break;
|
|
|
|
if (hdcp_ri_check_reschedule() == false)
|
|
reset_hdcp = true;
|
|
break;
|
|
|
|
case HDCP_REPEATER_CHECK:
|
|
pr_debug("hdcp: repeater check\n");
|
|
if (msg_data == NULL ||
|
|
*(unsigned int *)msg_data != hdcp_context->auth_id)
|
|
/*pr_debug("hdcp: auth count %d mismatch %d\n",
|
|
*(unsigned int *)msg_data,
|
|
hdcp_context->auth_id);*/
|
|
break;
|
|
|
|
if (hdcp_stage2_repeater_authentication() == false)
|
|
reset_hdcp = true;
|
|
break;
|
|
|
|
case HDCP_REPEATER_WDT_EXPIRED:
|
|
if (msg_data != NULL && *(unsigned int *)msg_data ==
|
|
hdcp_context->auth_id) {
|
|
/*pr_debug("hdcp: reapter wdt expired, "
|
|
"auth_id = %d, msg_data = %d\n",
|
|
hdcp_context->auth_id,
|
|
*(unsigned int *)msg_data);*/
|
|
|
|
hdcp_context->wdt_expired = true;
|
|
if(!hdcp_context->is_phase2_enabled &&
|
|
hdcp_context->is_phase1_enabled)
|
|
reset_hdcp = true;
|
|
}
|
|
break;
|
|
|
|
case HDCP_RESET:
|
|
hdcp_reset();
|
|
break;
|
|
|
|
case HDCP_SET_POWER_SAVE_STATUS:/* handle suspend resume */
|
|
/* ignore suspend state if HPD is low */
|
|
if (msg_data != NULL && hdcp_context->hpd == true) {
|
|
hdcp_context->suspend = *((bool *)msg_data);
|
|
pr_debug("hdcp: suspend = %d\n",
|
|
hdcp_context->suspend);
|
|
if (hdcp_context->suspend == true) {
|
|
if (hdcp_context->is_phase1_enabled
|
|
== true)
|
|
reset_hdcp = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HDCP_SET_HPD_STATUS:/* handle hpd status */
|
|
if (msg_data != NULL) {
|
|
hdcp_context->hpd = *((bool *)msg_data);
|
|
pr_debug("hdcp: hpd = %d\n",
|
|
hdcp_context->hpd);
|
|
if (hdcp_context->hpd == false) {
|
|
/* reset suspend state if HPD is Low */
|
|
hdcp_context->suspend = false;
|
|
reset_hdcp = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HDCP_SET_DPMS_STATUS:/* handle display_power_on status */
|
|
if (msg_data != NULL) {
|
|
hdcp_context->display_power_on =
|
|
*((bool *)msg_data);
|
|
pr_debug("hdcp: display_power_on = %d\n",
|
|
hdcp_context->display_power_on);
|
|
if (hdcp_context->display_power_on == false)
|
|
reset_hdcp = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (reset_hdcp == true) {
|
|
msg = HDCP_RESET;
|
|
wq_send_message(msg, NULL);
|
|
} else
|
|
/* if disabled retry HDCP authentication */
|
|
hdcp_retry_enable();
|
|
|
|
/* Update security component of hdmi and hdcp status */
|
|
if ((ctx->is_phase1_enabled != ctx->previous_phase1_status) ||
|
|
(ctx->hpd != ctx->previous_hpd)) {
|
|
ctx->previous_phase1_status = ctx->is_phase1_enabled;
|
|
ctx->previous_hpd = ctx->hpd;
|
|
|
|
otm_hdmi_update_security_hdmi_hdcp_status(
|
|
ctx->is_phase1_enabled, ctx->hpd);
|
|
}
|
|
|
|
ospm_power_using_hw_end(OSPM_DISPLAY_ISLAND);
|
|
EXIT_HDCP_HANDLER:
|
|
if (msg_data != NULL)
|
|
kfree(msg_data);
|
|
if (hwq != NULL)
|
|
kfree(hwq);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Description: function to update HPD status
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @hpd HPD high/low status
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_set_hpd_state(hdmi_context_t *hdmi_context,
|
|
bool hpd)
|
|
{
|
|
int msg = HDCP_SET_HPD_STATUS;
|
|
bool *p_hpd = NULL;
|
|
hpd = !!(hpd);
|
|
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->hpd != hpd) {
|
|
p_hpd = kmalloc(sizeof(bool), GFP_KERNEL);
|
|
if (p_hpd != NULL) {
|
|
*p_hpd = hpd;
|
|
return wq_send_message(msg, (void *)p_hpd);
|
|
} else
|
|
pr_debug("hdcp: %s failed to alloc mem\n", __func__);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: function to update power save (suspend/resume) status
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @suspend suspend/resume status
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_set_power_save(hdmi_context_t *hdmi_context,
|
|
bool suspend)
|
|
{
|
|
int msg = HDCP_SET_POWER_SAVE_STATUS;
|
|
bool *p_suspend = NULL;
|
|
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->suspend != suspend) {
|
|
p_suspend = kmalloc(sizeof(bool), GFP_KERNEL);
|
|
if (p_suspend != NULL) {
|
|
*p_suspend = suspend;
|
|
return wq_send_message(msg, (void *)p_suspend);
|
|
} else
|
|
pr_debug("hdcp: %s failed to alloc mem\n", __func__);
|
|
if (suspend == true)
|
|
/* Cleanup WorkQueue */
|
|
flush_workqueue(hdcp_context->hdcp_wq);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: function to update display_power_on status
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @display_power_on display power on/off status
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_set_dpms(hdmi_context_t *hdmi_context,
|
|
bool display_power_on)
|
|
{
|
|
#ifdef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
int msg = HDCP_SET_DPMS_STATUS;
|
|
bool *p_display_power_on = NULL;
|
|
display_power_on = !!(display_power_on);
|
|
#endif
|
|
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
return true;
|
|
#else
|
|
if (hdcp_context->display_power_on != display_power_on) {
|
|
p_display_power_on = kmalloc(sizeof(bool), GFP_KERNEL);
|
|
if (p_display_power_on != NULL) {
|
|
*p_display_power_on = display_power_on;
|
|
return wq_send_message(msg, (void *)p_display_power_on);
|
|
} else
|
|
pr_debug("hdcp: %s failed to alloc mem\n", __func__);
|
|
if (display_power_on == false)
|
|
/* Cleanup WorkQueue */
|
|
|
|
flush_workqueue(hdcp_context->hdcp_wq);
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Description: Function to check HDCP encryption status
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
*
|
|
* Returns: true if encrypting
|
|
* else false
|
|
*/
|
|
bool otm_hdmi_hdcp_enc_status(hdmi_context_t *hdmi_context)
|
|
{
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->is_required && hdcp_context->is_phase1_enabled)
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: Function to check HDCP Phase3 Link status
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
*
|
|
* Returns: true if link is verified Ri Matches
|
|
* else false
|
|
*/
|
|
bool otm_hdmi_hdcp_link_status(hdmi_context_t *hdmi_context)
|
|
{
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->is_phase3_valid)
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Description: Function to read BKSV and validate it
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @bksv buffer to store bksv
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_read_validate_bksv(hdmi_context_t *hdmi_context,
|
|
uint8_t *bksv)
|
|
{
|
|
bool ret = false;
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
if (hdmi_context == NULL || hdcp_context == NULL || bksv == NULL)
|
|
return false;
|
|
|
|
if(hdcp_context->hpd ==true &&
|
|
hdcp_context->suspend == false &&
|
|
hdcp_context->display_power_on == true &&
|
|
hdcp_context->is_required == false &&
|
|
hdcp_context->is_phase1_enabled == false) {
|
|
hdcp_context->sink_query = true;
|
|
ret = hdcp_read_bksv(bksv, HDCP_KSV_SIZE);
|
|
hdcp_context->sink_query = false;
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Description: function to enable HDCP
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @refresh_rate vertical refresh rate of the video mode
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_enable(hdmi_context_t *hdmi_context,
|
|
int refresh_rate)
|
|
{
|
|
int msg = HDCP_ENABLE;
|
|
otm_hdmi_attribute_t hdmi_attr;
|
|
otm_hdmi_ret_t rc;
|
|
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->is_required == true) {
|
|
pr_debug("hdcp: already enabled\n");
|
|
return true;
|
|
}
|
|
#ifdef OTM_HDCP_DEBUG_MODULE
|
|
if (module_disable_hdcp) {
|
|
pr_debug("hdcp: disabled by module\n");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
hdcp_context->is_required = true;
|
|
|
|
/* compute ri check interval based on refresh rate */
|
|
if (refresh_rate) {
|
|
/*compute msec time for 1 frame*/
|
|
hdcp_context->video_refresh_interval = 1000 / refresh_rate;
|
|
|
|
/* compute msec time for 128 frames per HDCP spec */
|
|
hdcp_context->ri_check_interval = ((128 * 1000) / refresh_rate);
|
|
} else {
|
|
/*compute msec time for 1 frame, assuming refresh rate of 60*/
|
|
hdcp_context->video_refresh_interval = 1000 / 60;
|
|
|
|
/* default to 128 frames @ 60 Hz */
|
|
hdcp_context->ri_check_interval = ((128 * 1000) / 60);
|
|
}
|
|
|
|
/* Set upper and lower bounds for ri_check_interval to
|
|
* avoid dynamic adjustment to go wild.
|
|
* Set adjustment range to 100ms, which is safe if HZ <=100.
|
|
*/
|
|
hdcp_context->ri_check_interval_lower =
|
|
hdcp_context->ri_check_interval - 100;
|
|
hdcp_context->ri_check_interval_upper =
|
|
hdcp_context->ri_check_interval + 100;
|
|
|
|
/* Init prev_ri_frm_cnt_status*/
|
|
hdcp_context->prev_ri_frm_cnt_status = 0;
|
|
|
|
/* Set ri_retry
|
|
* Default to interval of 3 frames if can not read
|
|
* OTM_HDMI_ATTR_ID_HDCP_RI_RETRY */
|
|
rc = otm_hdmi_get_attribute(hdmi_context,
|
|
OTM_HDMI_ATTR_ID_HDCP_RI_RETRY,
|
|
&hdmi_attr, false);
|
|
|
|
hdcp_context->ri_retry = (OTM_HDMI_SUCCESS == rc) ?
|
|
hdmi_attr.content._uint.value :
|
|
3 * hdcp_context->video_refresh_interval;
|
|
|
|
hdcp_context->hdmi = otm_hdmi_is_monitor_hdmi(hdmi_context);
|
|
pr_debug("hdcp: schedule HDCP enable\n");
|
|
|
|
#ifdef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
return wq_send_message(msg, NULL);
|
|
#else
|
|
/* send message and wait for 1st stage authentication to complete */
|
|
if (wq_send_message(msg, NULL)) {
|
|
/* on any failure is_required flag will be reset */
|
|
while (hdcp_context->is_required) {
|
|
/* wait for phase1 to be enabled before
|
|
* returning from this function */
|
|
if(hdcp_context->is_phase1_enabled)
|
|
return true;
|
|
msleep(1);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Description: function to disable HDCP
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_disable(hdmi_context_t *hdmi_context)
|
|
{
|
|
int msg = HDCP_RESET;
|
|
|
|
if (hdmi_context == NULL || hdcp_context == NULL)
|
|
return false;
|
|
|
|
if (hdcp_context->is_required == false) {
|
|
pr_debug("hdcp: already disabled\n");
|
|
return true;
|
|
}
|
|
|
|
/* send reset message */
|
|
wq_send_message(msg, NULL);
|
|
|
|
/* Cleanup WorkQueue */
|
|
/*flush_workqueue(hdcp_context->hdcp_wq);*/
|
|
|
|
/* Wait until hdcp is disabled.
|
|
* No need to wait for workqueue flushed since it may block for 2sec
|
|
* */
|
|
while (hdcp_context->is_phase1_enabled)
|
|
msleep(1);
|
|
|
|
hdcp_context->is_required = false;
|
|
|
|
pr_debug("hdcp: disable\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Description: copy BKSV to the buffer
|
|
*
|
|
* @bksv: buffer to receive bksv
|
|
* @size: length of buffer
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
|
|
bool otm_hdmi_hdcp_get_bksv(uint8_t *bksv, int size)
|
|
{
|
|
if (!hdcp_context || !bksv || size < HDCP_KSV_SIZE)
|
|
return false;
|
|
|
|
memcpy(bksv, hdcp_context->bksv, HDCP_KSV_SIZE);
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Description: hdcp init function
|
|
*
|
|
* @hdmi_context handle hdmi_context
|
|
* @ddc_rd_wr: pointer to ddc read write function
|
|
*
|
|
* Returns: true on success
|
|
* false on failure
|
|
*/
|
|
bool otm_hdmi_hdcp_init(hdmi_context_t *hdmi_context,
|
|
int (*ddc_rd_wr)(bool, uint8_t, uint8_t, uint8_t *, int))
|
|
{
|
|
otm_hdmi_attribute_t hdmi_attr;
|
|
otm_hdmi_ret_t rc;
|
|
|
|
if (hdmi_context == NULL ||
|
|
ddc_rd_wr == NULL ||
|
|
ipil_hdcp_device_can_authenticate() == false ||
|
|
hdcp_context != NULL) {
|
|
pr_debug("hdcp: init error!!! parameters\n");
|
|
return false;
|
|
}
|
|
|
|
/* allocate hdcp context */
|
|
hdcp_context = kmalloc(sizeof(struct hdcp_context_t), GFP_KERNEL);
|
|
|
|
/* Create hdcp workqueue to handle all hdcp tasks.
|
|
* To avoid multiple threads created for multi-core CPU (eg CTP)
|
|
* use create_singlethread_workqueue.
|
|
* */
|
|
if (hdcp_context != NULL) {
|
|
hdcp_context->hdcp_wq =
|
|
create_singlethread_workqueue("HDCP_WQ");
|
|
}
|
|
|
|
if (hdcp_context == NULL || hdcp_context->hdcp_wq == NULL) {
|
|
pr_debug("hdcp: init error!!! allocation\n");
|
|
goto EXIT_INIT;
|
|
}
|
|
|
|
/* initialize hdcp context and hence hdcp state machine */
|
|
hdcp_context->is_required = false;
|
|
hdcp_context->is_phase1_enabled = false;
|
|
hdcp_context->is_phase2_enabled = false;
|
|
hdcp_context->is_phase3_valid = false;
|
|
hdcp_context->suspend = false;
|
|
hdcp_context->hpd = false;
|
|
#ifndef OTM_HDMI_HDCP_ALWAYS_ENC
|
|
hdcp_context->display_power_on = true;
|
|
hdcp_context->auto_retry = false;
|
|
#else
|
|
hdcp_context->display_power_on = false;
|
|
hdcp_context->auto_retry = true;
|
|
#endif
|
|
hdcp_context->wdt_expired = false;
|
|
hdcp_context->sink_query = false;
|
|
hdcp_context->can_authenticate = true;
|
|
hdcp_context->current_srm_ver = 0u;
|
|
hdcp_context->vrl = NULL;
|
|
hdcp_context->vrl_count = 0u;
|
|
hdcp_context->ri_check_interval = 0u;
|
|
hdcp_context->force_reset = false;
|
|
hdcp_context->auth_id = 0;
|
|
|
|
hdcp_context->previous_hpd = false;
|
|
hdcp_context->previous_phase1_status = false;
|
|
|
|
/* store i2c function pointer used for ddc read/write */
|
|
hdcp_context->ddc_read_write = ddc_rd_wr;
|
|
|
|
/* Find hdcp delay
|
|
* If attribute not set, default to 200ms
|
|
*/
|
|
rc = otm_hdmi_get_attribute(hdmi_context,
|
|
OTM_HDMI_ATTR_ID_HDCP_DELAY,
|
|
&hdmi_attr, false);
|
|
|
|
hdcp_context->hdcp_delay = (rc == OTM_HDMI_SUCCESS) ?
|
|
hdmi_attr.content._uint.value :
|
|
200;
|
|
|
|
memset(hdcp_context->bksv, 0, HDCP_KSV_SIZE);
|
|
|
|
/* perform any hardware initializations */
|
|
if (ipil_hdcp_init() == true) {
|
|
pr_debug("hdcp: initialized\n");
|
|
return true;
|
|
}
|
|
|
|
EXIT_INIT:
|
|
/* Cleanup and exit */
|
|
if (hdcp_context != NULL) {
|
|
if (hdcp_context->hdcp_wq != NULL)
|
|
destroy_workqueue(hdcp_context->hdcp_wq);
|
|
kfree(hdcp_context);
|
|
hdcp_context = NULL;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|