/* // Copyright (c) 2014 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include <HwcTrace.h> #include <DrmConfig.h> #include <Hwcomposer.h> #include <DisplayQuery.h> #include <common/DrmControl.h> #include <common/HdcpControl.h> #include <cutils/properties.h> namespace android { namespace intel { HdcpControl::HdcpControl() : mCallback(NULL), mUserData(NULL), mCallbackState(CALLBACK_PENDING), mMutex(), mStoppedCondition(), mCompletedCondition(), mWaitForCompletion(false), mStopped(true), mAuthenticated(false), mActionDelay(0), mAuthRetryCount(0) { } HdcpControl::~HdcpControl() { } bool HdcpControl::startHdcp() { // this is a blocking and synchronous call Mutex::Autolock lock(mMutex); char prop[PROPERTY_VALUE_MAX]; if (property_get("debug.hwc.hdcp.enable", prop, "1") > 0) { if (atoi(prop) == 0) { WTRACE("HDCP is disabled"); return false; } } if (!mStopped) { WTRACE("HDCP has been started"); return true; } mStopped = false; mAuthenticated = false; mWaitForCompletion = false; mThread = new HdcpControlThread(this); if (!mThread.get()) { ETRACE("failed to create hdcp control thread"); return false; } if (!runHdcp()) { ETRACE("failed to run HDCP"); mStopped = true; mThread = NULL; return false; } mAuthRetryCount = 0; mWaitForCompletion = !mAuthenticated; if (mAuthenticated) { mActionDelay = HDCP_VERIFICATION_DELAY_MS; } else { mActionDelay = HDCP_AUTHENTICATION_SHORT_DELAY_MS; } mThread->run("HdcpControl", PRIORITY_NORMAL); if (!mWaitForCompletion) { // HDCP is authenticated. return true; } status_t err = mCompletedCondition.waitRelative(mMutex, milliseconds(HDCP_AUTHENTICATION_TIMEOUT_MS)); if (err == -ETIMEDOUT) { WTRACE("timeout waiting for completion"); } mWaitForCompletion = false; return mAuthenticated; } bool HdcpControl::startHdcpAsync(HdcpStatusCallback cb, void *userData) { char prop[PROPERTY_VALUE_MAX]; if (property_get("debug.hwc.hdcp.enable", prop, "1") > 0) { if (atoi(prop) == 0) { WTRACE("HDCP is disabled"); return false; } } if (cb == NULL || userData == NULL) { ETRACE("invalid callback or user data"); return false; } Mutex::Autolock lock(mMutex); if (!mStopped) { WTRACE("HDCP has been started"); return true; } mThread = new HdcpControlThread(this); if (!mThread.get()) { ETRACE("failed to create hdcp control thread"); return false; } mAuthRetryCount = 0; mCallback = cb; mUserData = userData; mCallbackState = CALLBACK_PENDING; mWaitForCompletion = false; mAuthenticated = false; mStopped = false; mActionDelay = HDCP_ASYNC_START_DELAY_MS; mThread->run("HdcpControl", PRIORITY_NORMAL); return true; } bool HdcpControl::stopHdcp() { do { Mutex::Autolock lock(mMutex); if (mStopped) { return true; } mStopped = true; mStoppedCondition.signal(); mAuthenticated = false; mWaitForCompletion = false; mCallback = NULL; mUserData = NULL; disableAuthentication(); } while (0); if (mThread.get()) { mThread->requestExitAndWait(); mThread = NULL; } return true; } bool HdcpControl::enableAuthentication() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); int ret = drmCommandNone(fd, DRM_PSB_ENABLE_HDCP); if (ret != 0) { ETRACE("failed to enable HDCP authentication"); return false; } return true; } bool HdcpControl::disableAuthentication() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); int ret = drmCommandNone(fd, DRM_PSB_DISABLE_HDCP); if (ret != 0) { ETRACE("failed to stop disable authentication"); return false; } return true; } bool HdcpControl::enableOverlay() { return true; } bool HdcpControl::disableOverlay() { return true; } bool HdcpControl::enableDisplayIED() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); int ret = drmCommandNone(fd, DRM_PSB_HDCP_DISPLAY_IED_ON); if (ret != 0) { ETRACE("failed to enable overlay IED"); return false; } return true; } bool HdcpControl::disableDisplayIED() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); int ret = drmCommandNone(fd, DRM_PSB_HDCP_DISPLAY_IED_OFF); if (ret != 0) { ETRACE("failed to disable overlay IED"); return false; } return true; } bool HdcpControl::isHdcpSupported() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); unsigned int caps = 0; int ret = drmCommandRead(fd, DRM_PSB_QUERY_HDCP, &caps, sizeof(caps)); if (ret != 0) { ETRACE("failed to query HDCP capability"); return false; } if (caps == 0) { WTRACE("HDCP is not supported"); return false; } else { ITRACE("HDCP is supported"); return true; } } bool HdcpControl::checkAuthenticated() { int fd = Hwcomposer::getInstance().getDrm()->getDrmFd(); unsigned int match = 0; int ret = drmCommandRead(fd, DRM_PSB_GET_HDCP_LINK_STATUS, &match, sizeof(match)); if (ret != 0) { ETRACE("failed to get hdcp link status"); return false; } if (match) { VTRACE("HDCP is authenticated"); mAuthenticated = true; } else { ETRACE("HDCP is not authenticated"); mAuthenticated = false; } return mAuthenticated; } bool HdcpControl::runHdcp() { // Default return value is true so HDCP can be re-authenticated in the working thread bool ret = true; preRunHdcp(); for (int i = 0; i < HDCP_INLOOP_RETRY_NUMBER; i++) { VTRACE("enable and verify HDCP, iteration# %d", i); if (mStopped) { WTRACE("HDCP authentication has been stopped"); ret = false; break; } if (!enableAuthentication()) { ETRACE("HDCP authentication failed. Retry"); mAuthenticated = false; ret = true; } else { ITRACE("HDCP is authenticated"); mAuthenticated = true; ret = true; break; } if (mStopped) { WTRACE("HDCP authentication has been stopped"); ret = false; break; } if (i < HDCP_INLOOP_RETRY_NUMBER - 1) { // Adding delay to make sure panel receives video signal so it can start HDCP authentication. // (HDCP spec 1.3, section 2.3) usleep(HDCP_INLOOP_RETRY_DELAY_US); } } postRunHdcp(); return ret; } bool HdcpControl::preRunHdcp() { // TODO: for CTP platform, IED needs to be disabled during HDCP authentication. return true; } bool HdcpControl::postRunHdcp() { // TODO: for CTP platform, IED needs to be disabled during HDCP authentication. return true; } void HdcpControl::signalCompletion() { if (mWaitForCompletion) { ITRACE("signal HDCP authentication completed, status = %d", mAuthenticated); mCompletedCondition.signal(); mWaitForCompletion = false; } } bool HdcpControl::threadLoop() { Mutex::Autolock lock(mMutex); status_t err = mStoppedCondition.waitRelative(mMutex, milliseconds(mActionDelay)); if (err != -ETIMEDOUT) { ITRACE("Hdcp is stopped."); signalCompletion(); return false; } // default is to keep thread active bool ret = true; if (!mAuthenticated) { ret = runHdcp(); mAuthRetryCount++; } else { mAuthRetryCount = 0; checkAuthenticated(); } // set next action delay if (mAuthenticated) { mActionDelay = HDCP_VERIFICATION_DELAY_MS; } else { // If HDCP can not authenticate after "HDCP_RETRY_LIMIT" attempts // reduce HDCP retry frequency to 2 sec if (mAuthRetryCount >= HDCP_RETRY_LIMIT) { mActionDelay = HDCP_AUTHENTICATION_LONG_DELAY_MS; } else { mActionDelay = HDCP_AUTHENTICATION_SHORT_DELAY_MS; } } // TODO: move out of lock? if (!ret || mAuthenticated) { signalCompletion(); } if (mCallback) { if ((mAuthenticated && mCallbackState == CALLBACK_AUTHENTICATED) || (!mAuthenticated && mCallbackState == CALLBACK_NOT_AUTHENTICATED)) { // ignore callback as state is not changed } else { mCallbackState = mAuthenticated ? CALLBACK_AUTHENTICATED : CALLBACK_NOT_AUTHENTICATED; (*mCallback)(mAuthenticated, mUserData); } } return ret; } } // namespace intel } // namespace android