static uint8_t ips_hdcp_get_repeater_control(void)__attribute__((unused));
static void ips_hdcp_set_repeater_control(int value) __attribute__((unused));
static uint8_t ips_hdcp_get_repeater_status(void);
-static int ips_hdcp_repeater_v_match_check(void) __attribute__((unused));
+static int ips_hdcp_repeater_v_match_check(void);
static bool ips_hdcp_repeater_is_busy(void) __attribute__((unused));
-static int ips_hdcp_repeater_rdy_for_nxt_data(void) __attribute__((unused));
+static bool ips_hdcp_repeater_rdy_for_nxt_data(void);
static uint32_t ips_hdcp_get_status(void)
{
return false;
}
-static int ips_hdcp_repeater_rdy_for_nxt_data(void)
+static bool ips_hdcp_repeater_rdy_for_nxt_data(void)
{
uint8_t status = ips_hdcp_get_repeater_status();
if (status == HDCP_REPEATER_STATUS_RDY_NEXT_DATA)
return false;
}
+static bool ips_hdcp_repeater_rdy_for_idle(void)
+{
+ uint8_t status = ips_hdcp_get_repeater_status();
+ if (status == HDCP_REPEATER_STATUS_IDLE)
+ return true;
+ return false;
+}
+
+static bool ips_hdcp_repeater_wait_for_next_data(void)
+{
+ uint16_t i = 0;
+ for (; i < HDCP_MAX_RETRY_STATUS; i++) {
+ if (ips_hdcp_repeater_rdy_for_nxt_data())
+ return true;
+ }
+ return false;
+}
+
+static bool ips_hdcp_repeater_wait_for_idle(void)
+{
+ uint16_t i = 0;
+ for (; i < HDCP_MAX_RETRY_STATUS; i++) {
+ if (ips_hdcp_repeater_rdy_for_idle())
+ return true;
+ }
+ return false;
+}
+
bool ips_hdcp_is_ready(void)
{
struct ips_hdcp_status_reg_t status;
return false;
}
+bool ips_hdcp_compute_tx_v(uint8_t *rep_ksv_list,
+ uint32_t rep_ksv_list_entries,
+ uint16_t topology_data)
+{
+ bool ret = false;
+ const uint8_t BSTAT_M0_LEN = 18; /* 2 (bstatus) + 8 (M0) + 8 (length) */
+ const uint8_t BSTAT_M0 = 10; /* 2 (bstatus) + 8 (M0) */
+ const uint8_t M0 = 8; /* 8 (M0) */
+ uint32_t num_devices = rep_ksv_list_entries;
+ uint32_t lower_num_bytes_for_sha = 0, num_pad_bytes = 0, temp_data = 0;
+ uint32_t rem_text_data = 0, num_mo_bytes_left = M0, value = 0, i = 0;
+ uint8_t *buffer = NULL, *temp_buffer = NULL, *temp_data_ptr = NULL;
+ struct ips_hdcp_repeater_reg_t hdcp_rep_ctrl_reg;
+ struct sqword_t buffer_len;
+
+ /* Clear SHA hash generator for new V calculation and
+ * set the repeater to idle state
+ */
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, 0);
+
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ hdcp_rep_ctrl_reg.control = HDCP_REPEATER_CTRL_IDLE;
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+ if (!ips_hdcp_repeater_wait_for_idle())
+ return false;
+
+ /* Start the SHA buffer creation to find the number of pad bytes */
+ num_pad_bytes = (64 - ((rep_ksv_list_entries * HDCP_KSV_SIZE)
+ + BSTAT_M0_LEN)
+ % 64);
+
+ /* Get the number of bytes for SHA */
+ lower_num_bytes_for_sha = (HDCP_KSV_SIZE * num_devices)
+ + BSTAT_M0_LEN
+ + num_pad_bytes; /* multiple of 64 bytes */
+
+ buffer = kzalloc(lower_num_bytes_for_sha, GFP_KERNEL);
+ if (!buffer)
+ return false;
+
+ /* 1. Copy the KSV buffer
+ * Note: data is in little endian format
+ */
+ temp_buffer = buffer;
+ memcpy((void *)temp_buffer, (void *)rep_ksv_list,
+ num_devices * HDCP_KSV_SIZE);
+ temp_buffer += num_devices * HDCP_KSV_SIZE;
+
+ /* 2. Copy the topology_data
+ */
+ memcpy((void *)temp_buffer, (void *)&topology_data, 2);
+ /* bstatus is copied in little endian format */
+ temp_buffer += 2;
+
+ /* 3. Offset the pointer buffer by 8 bytes
+ * These 8 bytes are zeroed and are place holders for M0
+ */
+ temp_buffer += 8;
+
+ /* 4. Pad the buffer with extra bytes
+ * No need to pad the begining of padding bytes by adding
+ * 0x80. HW automatically appends the same while creating
+ * the buffer.
+ */
+ for (i = 0; i < num_pad_bytes; i++) {
+ *temp_buffer = (uint8_t)0x00;
+ temp_buffer++;
+ }
+
+ /* 5. Construct the length byte */
+ buffer_len.quad_part = (unsigned long long)(rep_ksv_list_entries *
+ HDCP_KSV_SIZE + BSTAT_M0) * 8; /* in bits */
+ temp_data_ptr = (uint8_t *)&buffer_len.quad_part;
+ /* Store in big endian form, it is reversed to little endian
+ * when fed to SHA1
+ */
+ for (i = 1; i <= 8; i++) {
+ *temp_buffer = *(temp_data_ptr + 8 - i);
+ temp_buffer++;
+ }
+
+ /* 6. Write KSV's and bstatus into SHA */
+ temp_buffer = buffer;
+ for (i = 0; i < (HDCP_KSV_SIZE * num_devices + 2)/4; i++) {
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ hdcp_rep_ctrl_reg.control = HDCP_REPEATER_32BIT_TEXT_IP;
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+
+ /* As per HDCP spec sample SHA is in little endian format.
+ * But the data fed to the cipher needs to be in big endian
+ * format for it to compute it correctly
+ */
+ memcpy(&value, temp_buffer, 4);
+ value = HDCP_CONVERT_ENDIANNESS(value);
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, value);
+ temp_buffer += 4;
+
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+ }
+
+ /* 7. Write the remaining bstatus data and M0
+ * Text input must be aligned to LSB of the SHA1
+ * in register when inputting partial text and partial M0
+ */
+ rem_text_data = (HDCP_KSV_SIZE * num_devices + 2) % 4;
+ if (rem_text_data) {
+ /* Update the number of M0 bytes */
+ num_mo_bytes_left = num_mo_bytes_left - (4-rem_text_data);
+
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ switch (rem_text_data) {
+ case 1:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_8BIT_TEXT_24BIT_MO_IP;
+ break;
+ case 2:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_16BIT_TEXT_16BIT_MO_IP;
+ break;
+ case 3:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_24BIT_TEXT_8BIT_MO_IP;
+ break;
+ default:
+ goto exit;
+ }
+
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+ memcpy(&value, temp_buffer, 4);
+
+ /* Swap the text data in big endian format leaving the M0 data
+ * as it is. LSB should contain the data in big endian format.
+ * Since the M0 specfic data is all zeros while it's fed to the
+ * cipher, those bit don't need to be modified.
+ */
+ temp_data = 0;
+ for (i = 0; i < rem_text_data; i++) {
+ temp_data |= ((value & 0xff << (i * 8)) >>
+ (i * 8)) <<
+ ((rem_text_data - i - 1) * 8);
+ }
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, temp_data);
+ temp_buffer += 4;
+ }
+
+ /* write 4 bytes of M0 */
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ hdcp_rep_ctrl_reg.control = HDCP_REPEATER_32BIT_MO_IP;
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, (uint32_t)temp_buffer);
+ temp_buffer += 4;
+ num_mo_bytes_left -= 4;
+
+ if (num_mo_bytes_left) {
+ /* The remaining M0 + padding bytes need to be added */
+ num_pad_bytes = num_pad_bytes - (4 - num_mo_bytes_left);
+
+ /* write 4 bytes of M0 */
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ switch (num_mo_bytes_left) {
+ case 1:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_24BIT_TEXT_8BIT_MO_IP;
+ break;
+ case 2:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_16BIT_TEXT_16BIT_MO_IP;
+ break;
+ case 3:
+ hdcp_rep_ctrl_reg.control =
+ HDCP_REPEATER_8BIT_TEXT_24BIT_MO_IP;
+ break;
+ default:
+ /* should never happen */
+ goto exit;
+ }
+
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, (uint32_t)temp_buffer);
+ temp_buffer += 4;
+ num_mo_bytes_left = 0;
+ }
+
+ /* 8. Write the remaining padding bytes and length */
+ /* Remaining data = remaining padding data + 64 bits of length data */
+ rem_text_data = num_pad_bytes + 8;
+
+ if (rem_text_data % 4) {
+ /* Should not happen */
+ pr_debug("hdcp: compute_tx_v - data not aligned\n");
+ goto exit;
+ }
+
+ for (i = 0; i < rem_text_data / 4; i++) {
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ hdcp_rep_ctrl_reg.control = HDCP_REPEATER_32BIT_TEXT_IP;
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+ memcpy(&value, temp_buffer, 4);
+ /* Do the big endian conversion */
+ value = HDCP_CONVERT_ENDIANNESS(value);
+ hdmi_write32(MDFLD_HDCP_SHA1_IN, value);
+ temp_buffer += 4;
+ }
+
+ /* Done */
+ ret = true;
+
+exit:
+ kfree(buffer);
+ return ret;
+}
+
+bool ips_hdcp_compare_v(uint32_t *rep_prime_v)
+{
+ bool ret = false;
+ uint32_t i = 10, stat;
+ struct ips_hdcp_repeater_reg_t hdcp_rep_ctrl_reg;
+
+ /* Load V' */
+ hdmi_write32(MDFLD_HDCP_VPRIME_H0, *rep_prime_v);
+
+ hdmi_write32(MDFLD_HDCP_VPRIME_H1, *(rep_prime_v + 1));
+
+ hdmi_write32(MDFLD_HDCP_VPRIME_H2, *(rep_prime_v + 2));
+
+ hdmi_write32(MDFLD_HDCP_VPRIME_H3, *(rep_prime_v + 3));
+
+ hdmi_write32(MDFLD_HDCP_VPRIME_H4, *(rep_prime_v + 4));
+
+ if (false == ips_hdcp_repeater_wait_for_next_data())
+ goto exit;
+
+ /* Set HDCP_REP to do the comparison, start
+ * transmitter's V calculation
+ */
+ hdcp_rep_ctrl_reg.value = hdmi_read32(MDFLD_HDCP_REP_REG);
+ hdcp_rep_ctrl_reg.control = HDCP_REPEATER_COMPLETE_SHA1;
+ hdmi_write32(MDFLD_HDCP_REP_REG, hdcp_rep_ctrl_reg.value);
+
+ msleep(5);
+ do {
+ stat = ips_hdcp_repeater_v_match_check();
+ if (1 == stat) {
+ ret = true; /* match */
+ break;
+ } else if (-1 == stat)
+ msleep(5); /* busy, retry */
+ else
+ break; /* mismatch */
+ } while (--i);
+
+exit:
+ return ret;
+}
+
bool ips_hdcp_disable(void)
{
ips_hdcp_off();
/*!< indicates HDCP Authentication currently allowed
*/
bool is_required;
- bool is_enabled;
+ bool is_phase1_enabled;
+ bool is_phase2_enabled;
bool suspend;
bool hpd;
bool display_power_on;
bool auto_retry;
bool wdt_expired;
+ unsigned int wdt_id;
+ /*!< phase 2 auth watchdog timer id */
unsigned int current_srm_ver;
/*!< currently used SRM version (if vrl is not null)
*/
return hdcp_read_rx_ri(rx_r0);
}
+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;
+}
+
+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;
+}
+
static bool hdcp_stage3_ri_check(void)
{
uint16_t rx_ri = 0;
if (hdcp_enable_condition_ready() == false ||
- hdcp_context->is_enabled == false)
+ hdcp_context->is_phase1_enabled == false)
return false;
#ifdef OTM_HDCP_DEBUG_MODULE
/* Stop HDCP */
ipil_hdcp_disable();
- hdcp_context->is_enabled = false;
+ hdcp_context->is_phase1_enabled = false;
+ hdcp_context->is_phase2_enabled = false;
+ hdcp_context->wdt_id++;
}
static bool hdcp_rep_check(void)
static bool hdcp_rep_watch_dog(void)
{
int msg = HDCP_REPEATER_WDT_EXPIRED;
- return wq_send_message_delayed(msg, NULL, 5000);
+ unsigned int *wdt_id = kmalloc(sizeof(unsigned int), GFP_KERNEL);
+ if (wdt_id != NULL) {
+ *wdt_id = hdcp_context->wdt_id;
+ return wq_send_message_delayed(msg, (void *)wdt_id, 5000);
+ } else
+ pr_debug("hdcp: %s failed to alloc mem\n", __func__);
+ return false;
}
static bool hdcp_initiate_rep_auth(void)
return false;
pr_debug("hdcp: encryption enabled\n");
- hdcp_context->is_enabled = true;
+ hdcp_context->is_phase1_enabled = true;
return true;
}
static int hdcp_stage2_repeater_authentication(void)
{
- /* TODO: Repeater Authentication */
+ 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_enabled == false ||
- hdcp_context->wdt_expired == false)
+ hdcp_context->is_phase1_enabled == false ||
+ hdcp_context->wdt_expired == true) {
+ pr_debug("hdcp: stage2 auth condition not ready\n");
return false;
+ }
- /* check validity of repeater depth & device count */
- /* check if fifo ready */
- /* read ksv list */
- /* verify SHA1 */
- /* validate KSV's */
- return true;
+ /* 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) {
+ /* reschedule if not ready */
+ pr_debug("hdcp: rescheduling repeater auth\n");
+ msleep(100);
+ hdcp_rep_check();
+ 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;
+
+ /* Set repeater bit */
+ if (ipil_hdcp_set_repeater(bcaps.is_repeater) == false)
+ return false;
+
+ /* Read ksv list from repeater */
+ 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;
+ }
+
+ 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(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(v') */
+ if (hdcp_read_rx_v((uint8_t *)rep_prime_v) == false) {
+ pr_debug("hdcp: rep read rx v failure\n");
+ ipil_hdcp_set_repeater(0);
+ 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;
}
static bool hdcp_start(void)
{
int msg = HDCP_ENABLE;
if (hdcp_enable_condition_ready() == true &&
- hdcp_context->is_enabled == false &&
+ hdcp_context->is_phase1_enabled == false &&
hdcp_context->auto_retry == true) {
wq_send_message_delayed(msg, NULL, 30);
pr_debug("hdcp: retry enable\n");
switch (msg) {
case HDCP_ENABLE:
if (hdcp_enable_condition_ready() == true &&
- hdcp_context->is_enabled == false &&
+ hdcp_context->is_phase1_enabled == false &&
hdcp_start() == false) {
reset_hdcp = true;
pr_debug("hdcp: failed to start hdcp\n");
break;
case HDCP_REPEATER_WDT_EXPIRED:
- /* TODO */
- hdcp_context->wdt_expired = true;
- pr_debug("hdcp: repeater wdt expired\n");
+ if (msg_data != NULL) {
+ pr_debug("hdcp: reapter wdt expired, "
+ "wdt_id = %d, msg_data = %d\n",
+ hdcp_context->wdt_id,
+ *(unsigned int *)msg_data);
+ hdcp_context->wdt_expired = true;
+ if (hdcp_context->wdt_id == *((unsigned int *)msg_data)
+ && !hdcp_context->is_phase2_enabled)
+ reset_hdcp = true;
+ }
break;
case HDCP_RESET:
pr_debug("hdcp: suspend = %d\n",
hdcp_context->suspend);
if (hdcp_context->suspend == true) {
- if (hdcp_context->is_enabled == true)
+ if (hdcp_context->is_phase1_enabled
+ == true)
reset_hdcp = true;
}
}
}
hdcp_context->is_required = false;
- hdcp_context->is_enabled = false;
+ hdcp_context->is_phase1_enabled = false;
+ hdcp_context->is_phase2_enabled = false;
hdcp_context->suspend = false;
hdcp_context->hpd = false;
hdcp_context->display_power_on = false;
hdcp_context->auto_retry = true;
hdcp_context->wdt_expired = false;
hdcp_context->can_authenticate = true;
+ hdcp_context->wdt_id = 1u;
hdcp_context->current_srm_ver = 0u;
hdcp_context->vrl = NULL;
hdcp_context->vrl_count = 0u;