/* * Copyright 2019 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD * */ #include "hdcp.h" static void push_error_status(struct mod_hdcp *hdcp, enum mod_hdcp_status status) { struct mod_hdcp_trace *trace = &hdcp->connection.trace; if (trace->error_count < MAX_NUM_OF_ERROR_TRACE) { trace->errors[trace->error_count].status = status; trace->errors[trace->error_count].state_id = hdcp->state.id; trace->error_count++; HDCP_ERROR_TRACE(hdcp, status); } if (is_hdcp1(hdcp)) { hdcp->connection.hdcp1_retry_count++; if (hdcp->connection.hdcp1_retry_count == MAX_NUM_OF_ATTEMPTS) hdcp->connection.link.adjust.hdcp1.disable = 1; } else if (is_hdcp2(hdcp)) { hdcp->connection.hdcp2_retry_count++; if (hdcp->connection.hdcp2_retry_count == MAX_NUM_OF_ATTEMPTS) hdcp->connection.link.adjust.hdcp2.disable = 1; } } static uint8_t is_cp_desired_hdcp1(struct mod_hdcp *hdcp) { int i, is_auth_needed = 0; /* if all displays on the link don't need authentication, * hdcp is not desired */ for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { if (hdcp->displays[i].state != MOD_HDCP_DISPLAY_INACTIVE && hdcp->displays[i].adjust.disable != MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION) { is_auth_needed = 1; break; } } return is_auth_needed && !hdcp->connection.link.adjust.hdcp1.disable && !hdcp->connection.is_hdcp1_revoked; } static uint8_t is_cp_desired_hdcp2(struct mod_hdcp *hdcp) { int i, is_auth_needed = 0; /* if all displays on the link don't need authentication, * hdcp is not desired */ for (i = 0; i < MAX_NUM_OF_DISPLAYS; i++) { if (hdcp->displays[i].state != MOD_HDCP_DISPLAY_INACTIVE && hdcp->displays[i].adjust.disable != MOD_HDCP_DISPLAY_DISABLE_AUTHENTICATION) { is_auth_needed = 1; break; } } return is_auth_needed && !hdcp->connection.link.adjust.hdcp2.disable && !hdcp->connection.is_hdcp2_revoked; } static enum mod_hdcp_status execution(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, union mod_hdcp_transition_input *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (is_in_initialized_state(hdcp)) { if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } /* initialize transition input */ memset(input, 0, sizeof(union mod_hdcp_transition_input)); } else if (is_in_cp_not_desired_state(hdcp)) { if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } } else if (is_in_hdcp1_states(hdcp)) { status = mod_hdcp_hdcp1_execution(hdcp, event_ctx, &input->hdcp1); } else if (is_in_hdcp1_dp_states(hdcp)) { status = mod_hdcp_hdcp1_dp_execution(hdcp, event_ctx, &input->hdcp1); } else if (is_in_hdcp2_states(hdcp)) { status = mod_hdcp_hdcp2_execution(hdcp, event_ctx, &input->hdcp2); } else if (is_in_hdcp2_dp_states(hdcp)) { status = mod_hdcp_hdcp2_dp_execution(hdcp, event_ctx, &input->hdcp2); } else { event_ctx->unexpected_event = 1; goto out; } out: return status; } static enum mod_hdcp_status transition(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, union mod_hdcp_transition_input *input, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->unexpected_event) goto out; if (is_in_initialized_state(hdcp)) { if (is_dp_hdcp(hdcp)) if (is_cp_desired_hdcp2(hdcp)) { callback_in_ms(0, output); set_state_id(hdcp, output, D2_A0_DETERMINE_RX_HDCP_CAPABLE); } else if (is_cp_desired_hdcp1(hdcp)) { callback_in_ms(0, output); set_state_id(hdcp, output, D1_A0_DETERMINE_RX_HDCP_CAPABLE); } else { callback_in_ms(0, output); set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); set_auth_complete(hdcp, output); } else if (is_hdmi_dvi_sl_hdcp(hdcp)) if (is_cp_desired_hdcp2(hdcp)) { callback_in_ms(0, output); set_state_id(hdcp, output, H2_A0_KNOWN_HDCP2_CAPABLE_RX); } else if (is_cp_desired_hdcp1(hdcp)) { callback_in_ms(0, output); set_state_id(hdcp, output, H1_A0_WAIT_FOR_ACTIVE_RX); } else { callback_in_ms(0, output); set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); set_auth_complete(hdcp, output); } else { callback_in_ms(0, output); set_state_id(hdcp, output, HDCP_CP_NOT_DESIRED); set_auth_complete(hdcp, output); } } else if (is_in_cp_not_desired_state(hdcp)) { increment_stay_counter(hdcp); } else if (is_in_hdcp1_states(hdcp)) { status = mod_hdcp_hdcp1_transition(hdcp, event_ctx, &input->hdcp1, output); } else if (is_in_hdcp1_dp_states(hdcp)) { status = mod_hdcp_hdcp1_dp_transition(hdcp, event_ctx, &input->hdcp1, output); } else if (is_in_hdcp2_states(hdcp)) { status = mod_hdcp_hdcp2_transition(hdcp, event_ctx, &input->hdcp2, output); } else if (is_in_hdcp2_dp_states(hdcp)) { status = mod_hdcp_hdcp2_dp_transition(hdcp, event_ctx, &input->hdcp2, output); } else { status = MOD_HDCP_STATUS_INVALID_STATE; } out: return status; } static enum mod_hdcp_status reset_authentication(struct mod_hdcp *hdcp, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (is_hdcp1(hdcp)) { if (hdcp->auth.trans_input.hdcp1.create_session != UNKNOWN) { /* TODO - update psp to unify create session failure * recovery between hdcp1 and 2. */ mod_hdcp_hdcp1_destroy_session(hdcp); } HDCP_TOP_RESET_AUTH_TRACE(hdcp); memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); set_state_id(hdcp, output, HDCP_INITIALIZED); } else if (is_hdcp2(hdcp)) { if (hdcp->auth.trans_input.hdcp2.create_session == PASS) { status = mod_hdcp_hdcp2_destroy_session(hdcp); if (status != MOD_HDCP_STATUS_SUCCESS) { output->callback_needed = 0; output->watchdog_timer_needed = 0; goto out; } } HDCP_TOP_RESET_AUTH_TRACE(hdcp); memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); set_state_id(hdcp, output, HDCP_INITIALIZED); } else if (is_in_cp_not_desired_state(hdcp)) { HDCP_TOP_RESET_AUTH_TRACE(hdcp); memset(&hdcp->auth, 0, sizeof(struct mod_hdcp_authentication)); memset(&hdcp->state, 0, sizeof(struct mod_hdcp_state)); set_state_id(hdcp, output, HDCP_INITIALIZED); } out: /* stop callback and watchdog requests from previous authentication*/ output->watchdog_timer_stop = 1; output->callback_stop = 1; return status; } static enum mod_hdcp_status reset_connection(struct mod_hdcp *hdcp, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; memset(output, 0, sizeof(struct mod_hdcp_output)); status = reset_authentication(hdcp, output); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; if (current_state(hdcp) != HDCP_UNINITIALIZED) { HDCP_TOP_RESET_CONN_TRACE(hdcp); set_state_id(hdcp, output, HDCP_UNINITIALIZED); } memset(&hdcp->connection, 0, sizeof(hdcp->connection)); out: return status; } static enum mod_hdcp_status update_display_adjustments(struct mod_hdcp *hdcp, struct mod_hdcp_display *display, struct mod_hdcp_display_adjustment *adj) { enum mod_hdcp_status status = MOD_HDCP_STATUS_NOT_IMPLEMENTED; if (is_in_authenticated_states(hdcp) && is_dp_mst_hdcp(hdcp) && display->adjust.disable == true && adj->disable == false) { display->adjust.disable = false; if (is_hdcp1(hdcp)) status = mod_hdcp_hdcp1_enable_dp_stream_encryption(hdcp); else if (is_hdcp2(hdcp)) status = mod_hdcp_hdcp2_enable_dp_stream_encryption(hdcp); if (status != MOD_HDCP_STATUS_SUCCESS) display->adjust.disable = true; } if (status == MOD_HDCP_STATUS_SUCCESS && memcmp(adj, &display->adjust, sizeof(struct mod_hdcp_display_adjustment)) != 0) status = MOD_HDCP_STATUS_NOT_IMPLEMENTED; return status; } /* * Implementation of functions in mod_hdcp.h */ size_t mod_hdcp_get_memory_size(void) { return sizeof(struct mod_hdcp); } enum mod_hdcp_status mod_hdcp_setup(struct mod_hdcp *hdcp, struct mod_hdcp_config *config) { struct mod_hdcp_output output; enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; memset(&output, 0, sizeof(output)); hdcp->config = *config; HDCP_TOP_INTERFACE_TRACE(hdcp); status = reset_connection(hdcp, &output); if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_teardown(struct mod_hdcp *hdcp) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; struct mod_hdcp_output output; HDCP_TOP_INTERFACE_TRACE(hdcp); memset(&output, 0, sizeof(output)); status = reset_connection(hdcp, &output); if (status == MOD_HDCP_STATUS_SUCCESS) memset(hdcp, 0, sizeof(struct mod_hdcp)); else push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_add_display(struct mod_hdcp *hdcp, struct mod_hdcp_link *link, struct mod_hdcp_display *display, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; struct mod_hdcp_display *display_container = NULL; HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, display->index); memset(output, 0, sizeof(struct mod_hdcp_output)); /* skip inactive display */ if (display->state != MOD_HDCP_DISPLAY_ACTIVE) { status = MOD_HDCP_STATUS_SUCCESS; goto out; } /* check existing display container */ if (get_active_display_at_index(hdcp, display->index)) { status = MOD_HDCP_STATUS_SUCCESS; goto out; } /* find an empty display container */ display_container = get_empty_display_container(hdcp); if (!display_container) { status = MOD_HDCP_STATUS_DISPLAY_OUT_OF_BOUND; goto out; } /* reset existing authentication status */ status = reset_authentication(hdcp, output); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; /* reset retry counters */ reset_retry_counts(hdcp); /* reset error trace */ memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); /* add display to connection */ hdcp->connection.link = *link; *display_container = *display; status = mod_hdcp_add_display_to_topology(hdcp, display_container); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; /* request authentication */ if (current_state(hdcp) != HDCP_INITIALIZED) set_state_id(hdcp, output, HDCP_INITIALIZED); callback_in_ms(hdcp->connection.link.adjust.auth_delay * 1000, output); out: if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_remove_display(struct mod_hdcp *hdcp, uint8_t index, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; struct mod_hdcp_display *display = NULL; HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, index); memset(output, 0, sizeof(struct mod_hdcp_output)); /* find display in connection */ display = get_active_display_at_index(hdcp, index); if (!display) { status = MOD_HDCP_STATUS_SUCCESS; goto out; } /* stop current authentication */ status = reset_authentication(hdcp, output); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; /* clear retry counters */ reset_retry_counts(hdcp); /* reset error trace */ memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); /* remove display */ status = mod_hdcp_remove_display_from_topology(hdcp, index); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; memset(display, 0, sizeof(struct mod_hdcp_display)); /* request authentication when connection is not reset */ if (current_state(hdcp) != HDCP_UNINITIALIZED) callback_in_ms(hdcp->connection.link.adjust.auth_delay * 1000, output); out: if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_update_display(struct mod_hdcp *hdcp, uint8_t index, struct mod_hdcp_link_adjustment *link_adjust, struct mod_hdcp_display_adjustment *display_adjust, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; struct mod_hdcp_display *display = NULL; HDCP_TOP_INTERFACE_TRACE_WITH_INDEX(hdcp, index); memset(output, 0, sizeof(struct mod_hdcp_output)); /* find display in connection */ display = get_active_display_at_index(hdcp, index); if (!display) { status = MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; goto out; } /* skip if no changes */ if (memcmp(link_adjust, &hdcp->connection.link.adjust, sizeof(struct mod_hdcp_link_adjustment)) == 0 && memcmp(display_adjust, &display->adjust, sizeof(struct mod_hdcp_display_adjustment)) == 0) { status = MOD_HDCP_STATUS_SUCCESS; goto out; } if (memcmp(link_adjust, &hdcp->connection.link.adjust, sizeof(struct mod_hdcp_link_adjustment)) == 0 && memcmp(display_adjust, &display->adjust, sizeof(struct mod_hdcp_display_adjustment)) != 0) { status = update_display_adjustments(hdcp, display, display_adjust); if (status != MOD_HDCP_STATUS_NOT_IMPLEMENTED) goto out; } /* stop current authentication */ status = reset_authentication(hdcp, output); if (status != MOD_HDCP_STATUS_SUCCESS) goto out; /* clear retry counters */ reset_retry_counts(hdcp); /* reset error trace */ memset(&hdcp->connection.trace, 0, sizeof(hdcp->connection.trace)); /* set new adjustment */ hdcp->connection.link.adjust = *link_adjust; display->adjust = *display_adjust; /* request authentication when connection is not reset */ if (current_state(hdcp) != HDCP_UNINITIALIZED) /* wait 100ms to debounce simultaneous updates for different indices */ callback_in_ms(100, output); out: if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_query_display(struct mod_hdcp *hdcp, uint8_t index, struct mod_hdcp_display_query *query) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; struct mod_hdcp_display *display = NULL; /* find display in connection */ display = get_active_display_at_index(hdcp, index); if (!display) { status = MOD_HDCP_STATUS_DISPLAY_NOT_FOUND; goto out; } /* populate query */ query->link = &hdcp->connection.link; query->display = display; query->trace = &hdcp->connection.trace; query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; if (is_display_encryption_enabled(display)) { if (is_hdcp1(hdcp)) { query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP1_ON; } else if (is_hdcp2(hdcp)) { if (query->link->adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_0) query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE0_ON; else if (query->link->adjust.hdcp2.force_type == MOD_HDCP_FORCE_TYPE_1) query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_TYPE1_ON; else query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP2_ON; } } else { query->encryption_status = MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF; } out: return status; } enum mod_hdcp_status mod_hdcp_reset_connection(struct mod_hdcp *hdcp, struct mod_hdcp_output *output) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; HDCP_TOP_INTERFACE_TRACE(hdcp); status = reset_connection(hdcp, output); if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); return status; } enum mod_hdcp_status mod_hdcp_process_event(struct mod_hdcp *hdcp, enum mod_hdcp_event event, struct mod_hdcp_output *output) { enum mod_hdcp_status exec_status, trans_status, reset_status, status; struct mod_hdcp_event_context event_ctx; HDCP_EVENT_TRACE(hdcp, event); memset(output, 0, sizeof(struct mod_hdcp_output)); memset(&event_ctx, 0, sizeof(struct mod_hdcp_event_context)); event_ctx.event = event; /* execute and transition */ exec_status = execution(hdcp, &event_ctx, &hdcp->auth.trans_input); trans_status = transition( hdcp, &event_ctx, &hdcp->auth.trans_input, output); if (trans_status == MOD_HDCP_STATUS_SUCCESS) { status = MOD_HDCP_STATUS_SUCCESS; } else if (exec_status == MOD_HDCP_STATUS_SUCCESS) { status = MOD_HDCP_STATUS_INTERNAL_POLICY_FAILURE; push_error_status(hdcp, status); } else { status = exec_status; push_error_status(hdcp, status); } /* reset authentication if needed */ if (trans_status == MOD_HDCP_STATUS_RESET_NEEDED) { mod_hdcp_log_ddc_trace(hdcp); reset_status = reset_authentication(hdcp, output); if (reset_status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, reset_status); } /* Clear CP_IRQ status if needed */ if (event_ctx.event == MOD_HDCP_EVENT_CPIRQ) { status = mod_hdcp_clear_cp_irq_status(hdcp); if (status != MOD_HDCP_STATUS_SUCCESS) push_error_status(hdcp, status); } return status; } enum mod_hdcp_operation_mode mod_hdcp_signal_type_to_operation_mode( enum signal_type signal) { enum mod_hdcp_operation_mode mode = MOD_HDCP_MODE_OFF; switch (signal) { case SIGNAL_TYPE_DVI_SINGLE_LINK: case SIGNAL_TYPE_HDMI_TYPE_A: mode = MOD_HDCP_MODE_DEFAULT; break; case SIGNAL_TYPE_EDP: case SIGNAL_TYPE_DISPLAY_PORT: case SIGNAL_TYPE_DISPLAY_PORT_MST: mode = MOD_HDCP_MODE_DP; break; default: break; } return mode; }