/* * 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 inline enum mod_hdcp_status validate_bksv(struct mod_hdcp *hdcp) { uint64_t n = 0; uint8_t count = 0; u8 bksv[sizeof(n)] = { }; memcpy(bksv, hdcp->auth.msg.hdcp1.bksv, sizeof(hdcp->auth.msg.hdcp1.bksv)); n = *(uint64_t *)bksv; while (n) { count++; n &= (n - 1); } return (count == 20) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_HDCP1_INVALID_BKSV; } static inline enum mod_hdcp_status check_ksv_ready(struct mod_hdcp *hdcp) { if (is_dp_hdcp(hdcp)) return (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_READY) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY; return (hdcp->auth.msg.hdcp1.bcaps & DRM_HDCP_DDC_BCAPS_KSV_FIFO_READY) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_HDCP1_KSV_LIST_NOT_READY; } static inline enum mod_hdcp_status check_hdcp_capable_dp(struct mod_hdcp *hdcp) { return (hdcp->auth.msg.hdcp1.bcaps & DP_BCAPS_HDCP_CAPABLE) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_HDCP1_NOT_CAPABLE; } static inline enum mod_hdcp_status check_r0p_available_dp(struct mod_hdcp *hdcp) { enum mod_hdcp_status status; if (is_dp_hdcp(hdcp)) { status = (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_R0_PRIME_READY) ? MOD_HDCP_STATUS_SUCCESS : MOD_HDCP_STATUS_HDCP1_R0_PRIME_PENDING; } else { status = MOD_HDCP_STATUS_INVALID_OPERATION; } return status; } static inline enum mod_hdcp_status check_link_integrity_dp( struct mod_hdcp *hdcp) { return (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_LINK_FAILURE) ? MOD_HDCP_STATUS_HDCP1_LINK_INTEGRITY_FAILURE : MOD_HDCP_STATUS_SUCCESS; } static inline enum mod_hdcp_status check_no_reauthentication_request_dp( struct mod_hdcp *hdcp) { return (hdcp->auth.msg.hdcp1.bstatus & DP_BSTATUS_REAUTH_REQ) ? MOD_HDCP_STATUS_HDCP1_REAUTH_REQUEST_ISSUED : MOD_HDCP_STATUS_SUCCESS; } static inline enum mod_hdcp_status check_no_max_cascade(struct mod_hdcp *hdcp) { enum mod_hdcp_status status; if (is_dp_hdcp(hdcp)) status = DRM_HDCP_MAX_CASCADE_EXCEEDED(hdcp->auth.msg.hdcp1.binfo_dp >> 8) ? MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE : MOD_HDCP_STATUS_SUCCESS; else status = DRM_HDCP_MAX_CASCADE_EXCEEDED(hdcp->auth.msg.hdcp1.bstatus >> 8) ? MOD_HDCP_STATUS_HDCP1_MAX_CASCADE_EXCEEDED_FAILURE : MOD_HDCP_STATUS_SUCCESS; return status; } static inline enum mod_hdcp_status check_no_max_devs(struct mod_hdcp *hdcp) { enum mod_hdcp_status status; if (is_dp_hdcp(hdcp)) status = DRM_HDCP_MAX_DEVICE_EXCEEDED(hdcp->auth.msg.hdcp1.binfo_dp) ? MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE : MOD_HDCP_STATUS_SUCCESS; else status = DRM_HDCP_MAX_DEVICE_EXCEEDED(hdcp->auth.msg.hdcp1.bstatus) ? MOD_HDCP_STATUS_HDCP1_MAX_DEVS_EXCEEDED_FAILURE : MOD_HDCP_STATUS_SUCCESS; return status; } static inline uint8_t get_device_count(struct mod_hdcp *hdcp) { return is_dp_hdcp(hdcp) ? DRM_HDCP_NUM_DOWNSTREAM(hdcp->auth.msg.hdcp1.binfo_dp) : DRM_HDCP_NUM_DOWNSTREAM(hdcp->auth.msg.hdcp1.bstatus); } static inline enum mod_hdcp_status check_device_count(struct mod_hdcp *hdcp) { /* Avoid device count == 0 to do authentication */ if (0 == get_device_count(hdcp)) { return MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE; } /* Some MST display may choose to report the internal panel as an HDCP RX. * To update this condition with 1(because the immediate repeater's internal * panel is possibly not included in DEVICE_COUNT) + get_device_count(hdcp). * Device count must be greater than or equal to tracked hdcp displays. */ return ((1 + get_device_count(hdcp)) < get_active_display_count(hdcp)) ? MOD_HDCP_STATUS_HDCP1_DEVICE_COUNT_MISMATCH_FAILURE : MOD_HDCP_STATUS_SUCCESS; } static enum mod_hdcp_status wait_for_active_rx(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } if (!mod_hdcp_execute_and_set(mod_hdcp_read_bksv, &input->bksv_read, &status, hdcp, "bksv_read")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, &input->bcaps_read, &status, hdcp, "bcaps_read")) goto out; out: return status; } static enum mod_hdcp_status exchange_ksvs(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_create_session, &input->create_session, &status, hdcp, "create_session")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_write_an, &input->an_write, &status, hdcp, "an_write")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_write_aksv, &input->aksv_write, &status, hdcp, "aksv_write")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_read_bksv, &input->bksv_read, &status, hdcp, "bksv_read")) goto out; if (!mod_hdcp_execute_and_set(validate_bksv, &input->bksv_validation, &status, hdcp, "bksv_validation")) goto out; if (hdcp->auth.msg.hdcp1.ainfo) { if (!mod_hdcp_execute_and_set(mod_hdcp_write_ainfo, &input->ainfo_write, &status, hdcp, "ainfo_write")) goto out; } out: return status; } static enum mod_hdcp_status computations_validate_rx_test_for_repeater( struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } if (!mod_hdcp_execute_and_set(mod_hdcp_read_r0p, &input->r0p_read, &status, hdcp, "r0p_read")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_validate_rx, &input->rx_validation, &status, hdcp, "rx_validation")) goto out; if (hdcp->connection.is_repeater) { if (!hdcp->connection.link.adjust.hdcp1.postpone_encryption) if (!mod_hdcp_execute_and_set( mod_hdcp_hdcp1_enable_encryption, &input->encryption, &status, hdcp, "encryption")) goto out; } else { if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_enable_encryption, &input->encryption, &status, hdcp, "encryption")) goto out; if (is_dp_mst_hdcp(hdcp)) if (!mod_hdcp_execute_and_set( mod_hdcp_hdcp1_enable_dp_stream_encryption, &input->stream_encryption_dp, &status, hdcp, "stream_encryption_dp")) goto out; } out: return status; } static enum mod_hdcp_status authenticated(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } mod_hdcp_execute_and_set(mod_hdcp_hdcp1_link_maintenance, &input->link_maintenance, &status, hdcp, "link_maintenance"); out: return status; } static enum mod_hdcp_status wait_for_ready(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK && event_ctx->event != MOD_HDCP_EVENT_CPIRQ && event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { event_ctx->unexpected_event = 1; goto out; } if (is_dp_hdcp(hdcp)) { if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, &input->bstatus_read, &status, hdcp, "bstatus_read")) goto out; if (!mod_hdcp_execute_and_set(check_link_integrity_dp, &input->link_integrity_check, &status, hdcp, "link_integrity_check")) goto out; if (!mod_hdcp_execute_and_set(check_no_reauthentication_request_dp, &input->reauth_request_check, &status, hdcp, "reauth_request_check")) goto out; } else { if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, &input->bcaps_read, &status, hdcp, "bcaps_read")) goto out; } if (!mod_hdcp_execute_and_set(check_ksv_ready, &input->ready_check, &status, hdcp, "ready_check")) goto out; out: return status; } static enum mod_hdcp_status read_ksv_list(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; uint8_t device_count; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } if (is_dp_hdcp(hdcp)) { if (!mod_hdcp_execute_and_set(mod_hdcp_read_binfo, &input->binfo_read_dp, &status, hdcp, "binfo_read_dp")) goto out; } else { if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, &input->bstatus_read, &status, hdcp, "bstatus_read")) goto out; } if (!mod_hdcp_execute_and_set(check_no_max_cascade, &input->max_cascade_check, &status, hdcp, "max_cascade_check")) goto out; if (!mod_hdcp_execute_and_set(check_no_max_devs, &input->max_devs_check, &status, hdcp, "max_devs_check")) goto out; if (!mod_hdcp_execute_and_set(check_device_count, &input->device_count_check, &status, hdcp, "device_count_check")) goto out; device_count = get_device_count(hdcp); hdcp->auth.msg.hdcp1.ksvlist_size = device_count*5; if (!mod_hdcp_execute_and_set(mod_hdcp_read_ksvlist, &input->ksvlist_read, &status, hdcp, "ksvlist_read")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_read_vp, &input->vp_read, &status, hdcp, "vp_read")) goto out; if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_validate_ksvlist_vp, &input->ksvlist_vp_validation, &status, hdcp, "ksvlist_vp_validation")) goto out; if (input->encryption != PASS) if (!mod_hdcp_execute_and_set(mod_hdcp_hdcp1_enable_encryption, &input->encryption, &status, hdcp, "encryption")) goto out; if (is_dp_mst_hdcp(hdcp)) if (!mod_hdcp_execute_and_set( mod_hdcp_hdcp1_enable_dp_stream_encryption, &input->stream_encryption_dp, &status, hdcp, "stream_encryption_dp")) goto out; out: return status; } static enum mod_hdcp_status determine_rx_hdcp_capable_dp(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CALLBACK) { event_ctx->unexpected_event = 1; goto out; } if (!mod_hdcp_execute_and_set(mod_hdcp_read_bcaps, &input->bcaps_read, &status, hdcp, "bcaps_read")) goto out; if (!mod_hdcp_execute_and_set(check_hdcp_capable_dp, &input->hdcp_capable_dp, &status, hdcp, "hdcp_capable_dp")) goto out; out: return status; } static enum mod_hdcp_status wait_for_r0_prime_dp(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CPIRQ && event_ctx->event != MOD_HDCP_EVENT_WATCHDOG_TIMEOUT) { event_ctx->unexpected_event = 1; goto out; } if (!mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, &input->bstatus_read, &status, hdcp, "bstatus_read")) goto out; if (!mod_hdcp_execute_and_set(check_r0p_available_dp, &input->r0p_available_dp, &status, hdcp, "r0p_available_dp")) goto out; out: return status; } static enum mod_hdcp_status authenticated_dp(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; if (event_ctx->event != MOD_HDCP_EVENT_CPIRQ) { event_ctx->unexpected_event = 1; goto out; } mod_hdcp_execute_and_set(mod_hdcp_read_bstatus, &input->bstatus_read, &status, hdcp, "bstatus_read"); mod_hdcp_execute_and_set(check_link_integrity_dp, &input->link_integrity_check, &status, hdcp, "link_integrity_check"); mod_hdcp_execute_and_set(check_no_reauthentication_request_dp, &input->reauth_request_check, &status, hdcp, "reauth_request_check"); out: return status; } uint8_t mod_hdcp_execute_and_set( mod_hdcp_action func, uint8_t *flag, enum mod_hdcp_status *status, struct mod_hdcp *hdcp, char *str) { *status = func(hdcp); if (*status == MOD_HDCP_STATUS_SUCCESS && *flag != PASS) { HDCP_INPUT_PASS_TRACE(hdcp, str); *flag = PASS; } else if (*status != MOD_HDCP_STATUS_SUCCESS && *flag != FAIL) { HDCP_INPUT_FAIL_TRACE(hdcp, str); *flag = FAIL; } return (*status == MOD_HDCP_STATUS_SUCCESS); } enum mod_hdcp_status mod_hdcp_hdcp1_execution(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; switch (current_state(hdcp)) { case H1_A0_WAIT_FOR_ACTIVE_RX: status = wait_for_active_rx(hdcp, event_ctx, input); break; case H1_A1_EXCHANGE_KSVS: status = exchange_ksvs(hdcp, event_ctx, input); break; case H1_A2_COMPUTATIONS_A3_VALIDATE_RX_A6_TEST_FOR_REPEATER: status = computations_validate_rx_test_for_repeater(hdcp, event_ctx, input); break; case H1_A45_AUTHENTICATED: status = authenticated(hdcp, event_ctx, input); break; case H1_A8_WAIT_FOR_READY: status = wait_for_ready(hdcp, event_ctx, input); break; case H1_A9_READ_KSV_LIST: status = read_ksv_list(hdcp, event_ctx, input); break; default: status = MOD_HDCP_STATUS_INVALID_STATE; break; } return status; } enum mod_hdcp_status mod_hdcp_hdcp1_dp_execution(struct mod_hdcp *hdcp, struct mod_hdcp_event_context *event_ctx, struct mod_hdcp_transition_input_hdcp1 *input) { enum mod_hdcp_status status = MOD_HDCP_STATUS_SUCCESS; switch (current_state(hdcp)) { case D1_A0_DETERMINE_RX_HDCP_CAPABLE: status = determine_rx_hdcp_capable_dp(hdcp, event_ctx, input); break; case D1_A1_EXCHANGE_KSVS: status = exchange_ksvs(hdcp, event_ctx, input); break; case D1_A23_WAIT_FOR_R0_PRIME: status = wait_for_r0_prime_dp(hdcp, event_ctx, input); break; case D1_A2_COMPUTATIONS_A3_VALIDATE_RX_A5_TEST_FOR_REPEATER: status = computations_validate_rx_test_for_repeater( hdcp, event_ctx, input); break; case D1_A4_AUTHENTICATED: status = authenticated_dp(hdcp, event_ctx, input); break; case D1_A6_WAIT_FOR_READY: status = wait_for_ready(hdcp, event_ctx, input); break; case D1_A7_READ_KSV_LIST: status = read_ksv_list(hdcp, event_ctx, input); break; default: status = MOD_HDCP_STATUS_INVALID_STATE; break; } return status; }