added protection against standby without a notification from XBMC and clock changes...
[deb_libcec.git] / src / lib / adapter / Pulse-Eight / USBCECAdapterCommunication.cpp
index fb16fe998321a57f18c42a657b3e81905228e6ff..2e9c790a7342cce914c28eb29c4e8ba031c33712 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libCEC(R) library.
  *
- * libCEC(R) is Copyright (C) 2011-2012 Pulse-Eight Limited.  All rights reserved.
+ * libCEC(R) is Copyright (C) 2011-2013 Pulse-Eight Limited.  All rights reserved.
  * libCEC(R) is an original work, containing original code.
  *
  * libCEC(R) is a trademark of Pulse-Eight Limited.
@@ -36,6 +36,7 @@
 #include "USBCECAdapterCommands.h"
 #include "USBCECAdapterMessageQueue.h"
 #include "USBCECAdapterMessage.h"
+#include "USBCECAdapterDetection.h"
 #include "lib/platform/sockets/serialport.h"
 #include "lib/platform/util/timeutils.h"
 #include "lib/platform/util/util.h"
@@ -53,10 +54,13 @@ using namespace PLATFORM;
 #define CEC_ADAPTER_EEPROM_WRITE_INTERVAL 30000
 #define CEC_ADAPTER_EEPROM_WRITE_RETRY    5000
 
-// firmware version 2
-#define CEC_LATEST_ADAPTER_FW_VERSION 2
-// firmware date Thu Apr 26 20:14:49 2012 +0000
-#define CEC_LATEST_ADAPTER_FW_DATE    0x4F99ACB9
+// firmware version 3
+#define CEC_LATEST_ADAPTER_FW_VERSION 3
+// firmware date Thu Nov 15 11:09:45 2012
+#define CEC_LATEST_ADAPTER_FW_DATE    0x50a4cd79
+
+#define CEC_FW_DATE_EXTENDED_RESPONSE 0x501a4b0c
+#define CEC_FW_DATE_DESCRIPTOR2       0x5045dbf5
 
 #define LIB_CEC m_callback->GetLib()
 
@@ -67,10 +71,9 @@ CUSBCECAdapterCommunication::CUSBCECAdapterCommunication(IAdapterCommunicationCa
     m_lastPollDestination(CECDEVICE_UNKNOWN),
     m_bInitialised(false),
     m_pingThread(NULL),
+    m_eepromWriteThread(NULL),
     m_commands(NULL),
-    m_adapterMessageQueue(NULL),
-    m_iLastEepromWrite(0),
-    m_iScheduleEepromWrite(0)
+    m_adapterMessageQueue(NULL)
 {
   m_logicalAddresses.Clear();
   for (unsigned int iPtr = CECDEVICE_TV; iPtr < CECDEVICE_BROADCAST; iPtr++)
@@ -171,17 +174,27 @@ bool CUSBCECAdapterCommunication::Open(uint32_t iTimeoutMs /* = CEC_DEFAULT_CONN
   }
   else if (bStartListening)
   {
-    /* start a ping thread, that will ping the adapter every 15 seconds
-       if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
-    m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT);
-    if (m_pingThread->CreateThread())
+    /* start the eeprom write thread, that handles all eeprom writes async */
+    m_eepromWriteThread = new CAdapterEepromWriteThread(this);
+    if (!m_eepromWriteThread->CreateThread())
     {
-      bConnectionOpened = true;
+      bConnectionOpened = false;
+      LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create the eeprom write thread");
     }
     else
     {
-      bConnectionOpened = false;
-      LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
+      /* start a ping thread, that will ping the adapter every 15 seconds
+         if it doesn't receive any ping for 30 seconds, it'll switch to auto mode */
+      m_pingThread = new CAdapterPingThread(this, CEC_ADAPTER_PING_TIMEOUT);
+      if (m_pingThread->CreateThread())
+      {
+        bConnectionOpened = true;
+      }
+      else
+      {
+        bConnectionOpened = false;
+        LIB_CEC->AddLog(CEC_LOG_ERROR, "could not create a ping thread");
+      }
     }
   }
 
@@ -210,6 +223,11 @@ void CUSBCECAdapterCommunication::Close(void)
 
   m_adapterMessageQueue->Clear();
 
+  /* stop and delete the write thread */
+  if (m_eepromWriteThread)
+    m_eepromWriteThread->Stop();
+  DELETE_AND_NULL(m_eepromWriteThread);
+
   /* stop and delete the ping thread */
   DELETE_AND_NULL(m_pingThread);
 
@@ -218,24 +236,33 @@ void CUSBCECAdapterCommunication::Close(void)
     m_port->Close();
 }
 
-cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout, bool UNUSED(bIsReply))
+cec_adapter_message_state CUSBCECAdapterCommunication::Write(const cec_command &data, bool &bRetry, uint8_t iLineTimeout, bool bIsReply)
 {
   cec_adapter_message_state retVal(ADAPTER_MESSAGE_STATE_UNKNOWN);
   if (!IsRunning())
     return retVal;
 
   CCECAdapterMessage *output = new CCECAdapterMessage(data, iLineTimeout);
+  output->bFireAndForget = bIsReply;
 
   /* mark as waiting for an ack from the destination */
   MarkAsWaiting(data.destination);
 
   /* send the message */
-  bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
-  if (bRetry)
-    Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
-  retVal = output->state;
+  if (bIsReply)
+  {
+    retVal = m_adapterMessageQueue->Write(output) ?
+        ADAPTER_MESSAGE_STATE_WAITING_TO_BE_SENT : ADAPTER_MESSAGE_STATE_ERROR;
+  }
+  else
+  {
+    bRetry = (!m_adapterMessageQueue->Write(output) || output->NeedsRetry()) && output->transmit_timeout > 0;
+    if (bRetry)
+      Sleep(CEC_DEFAULT_TRANSMIT_RETRY_WAIT);
+    retVal = output->state;
 
-  delete output;
+    delete output;
+  }
   return retVal;
 }
 
@@ -244,7 +271,6 @@ void *CUSBCECAdapterCommunication::Process(void)
   CCECAdapterMessage msg;
   LIB_CEC->AddLog(CEC_LOG_DEBUG, "communication thread started");
 
-  bool bWriteEeprom(false);
   while (!IsStopped())
   {
     /* read from the serial port */
@@ -257,33 +283,9 @@ void *CUSBCECAdapterCommunication::Process(void)
       break;
     }
 
-    // check if we need to do another eeprom write
-    {
-      CLockObject lock(m_mutex);
-      int64_t iNow = GetTimeMs();
-      if (m_iScheduleEepromWrite > 0 &&
-          m_iScheduleEepromWrite >= iNow)
-      {
-        m_iScheduleEepromWrite = 0;
-        m_iLastEepromWrite = iNow;
-        bWriteEeprom = true;
-      }
-    }
-
-    if (bWriteEeprom)
-    {
-      LIB_CEC->AddLog(CEC_LOG_DEBUG, "updating the eeprom (scheduled)");
-      bWriteEeprom = false;
-      if (!m_commands->WriteEEPROM())
-      {
-        // failed, retry later
-        CLockObject lock(m_mutex);
-        m_iScheduleEepromWrite = GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY;
-      }
-    }
-
     /* TODO sleep 5 ms so other threads can get a lock */
-    Sleep(5);
+    if (!IsStopped())
+      Sleep(5);
   }
 
   m_adapterMessageQueue->Clear();
@@ -380,12 +382,13 @@ bool CUSBCECAdapterCommunication::WriteToDevice(CCECAdapterMessage *message)
   {
     LIB_CEC->AddLog(CEC_LOG_DEBUG, "error writing command '%s' to serial port '%s': %s", CCECAdapterMessage::ToString(message->Message()), m_port->GetName().c_str(), m_port->GetError().c_str());
     message->state = ADAPTER_MESSAGE_STATE_ERROR;
-    // this will trigger an alert in the reader thread
-    m_port->Close();
+    // let the higher level close the port
     return false;
   }
 
+#ifdef CEC_DEBUGGING
   LIB_CEC->AddLog(CEC_LOG_DEBUG, "command '%s' sent", message->IsTranmission() ? "CEC transmission" : CCECAdapterMessage::ToString(message->Message()));
+#endif
   message->state = ADAPTER_MESSAGE_STATE_SENT;
   return true;
 }
@@ -403,12 +406,16 @@ bool CUSBCECAdapterCommunication::ReadFromDevice(uint32_t iTimeout, size_t iSize
     if (!IsOpen())
       return false;
 
-    iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
+    do {
+      /* retry Read() if it was interrupted */
+      iBytesRead = m_port->Read(buff, sizeof(uint8_t) * iSize, iTimeout);
+    } while(m_port->GetErrorNumber() == EINTR);
+
 
     if (m_port->GetErrorNumber())
     {
       LIB_CEC->AddLog(CEC_LOG_ERROR, "error reading from serial port: %s", m_port->GetError().c_str());
-      m_port->Close();
+      // let the higher level close the port
       return false;
     }
   }
@@ -492,8 +499,14 @@ bool CUSBCECAdapterCommunication::CheckAdapter(uint32_t iTimeoutMs /* = CEC_DEFA
   else
     bReturn = true;
 
-  /* try to read the build date */
-  m_commands->RequestBuildDate();
+  if (m_commands->GetFirmwareVersion() >= 2)
+  {
+    /* try to read the build date */
+    m_commands->RequestBuildDate();
+
+    /* try to read the adapter type */
+    m_commands->RequestAdapterType();
+  }
 
   SetInitialised(bReturn);
   return bReturn;
@@ -571,47 +584,66 @@ uint16_t CUSBCECAdapterCommunication::GetFirmwareVersion(void)
 
 uint32_t CUSBCECAdapterCommunication::GetFirmwareBuildDate(void)
 {
-  return IsOpen() ? m_commands->RequestBuildDate() : m_commands ? m_commands->GetPersistedBuildDate() : 0;
+  uint32_t iBuildDate(0);
+  if (m_commands)
+    iBuildDate = m_commands->GetPersistedBuildDate();
+  if (iBuildDate == 0 && IsOpen())
+    iBuildDate = m_commands->RequestBuildDate();
+
+  return iBuildDate;
+}
+
+cec_adapter_type CUSBCECAdapterCommunication::GetAdapterType(void)
+{
+  cec_adapter_type type(ADAPTERTYPE_UNKNOWN);
+  if (m_commands)
+    type = (cec_adapter_type)m_commands->GetPersistedAdapterType();
+  if (type == ADAPTERTYPE_UNKNOWN && IsOpen())
+    type = (cec_adapter_type)m_commands->RequestAdapterType();
+
+  return type;
+}
+
+bool CUSBCECAdapterCommunication::ProvidesExtendedResponse(void)
+{
+  uint32_t iBuildDate(0);
+  if (m_commands)
+    iBuildDate = m_commands->GetPersistedBuildDate();
+
+  return iBuildDate >= CEC_FW_DATE_EXTENDED_RESPONSE;
+}
+
+uint16_t CUSBCECAdapterCommunication::GetAdapterVendorId(void) const
+{
+  return CEC_VID;
+}
+
+uint16_t CUSBCECAdapterCommunication::GetAdapterProductId(void) const
+{
+  uint32_t iBuildDate(0);
+  if (m_commands)
+    iBuildDate = m_commands->GetPersistedBuildDate();
+
+  return iBuildDate >= CEC_FW_DATE_DESCRIPTOR2 ? CEC_PID2 : CEC_PID;
+}
+
+void CUSBCECAdapterCommunication::SetActiveSource(bool bSetTo, bool bClientUnregistered)
+{
+  if (m_commands)
+    m_commands->SetActiveSource(bSetTo, bClientUnregistered);
 }
 
 bool CUSBCECAdapterCommunication::IsRunningLatestFirmware(void)
 {
-  return GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION &&
-      GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE;
+  return GetFirmwareBuildDate() >= CEC_LATEST_ADAPTER_FW_DATE &&
+      GetFirmwareVersion() >= CEC_LATEST_ADAPTER_FW_VERSION;
 }
 
 bool CUSBCECAdapterCommunication::PersistConfiguration(const libcec_configuration &configuration)
 {
-  if (IsOpen())
-  {
-    // returns true when something changed
-    if (m_commands->PersistConfiguration(configuration))
-    {
-      {
-        CLockObject lock(m_mutex);
-        uint64_t iNow = GetTimeMs();
-        if (iNow - m_iLastEepromWrite < CEC_ADAPTER_EEPROM_WRITE_INTERVAL)
-        {
-          // if there was more than 1 write within the last 30 seconds, schedule another one
-          if (m_iScheduleEepromWrite == 0)
-            m_iScheduleEepromWrite = m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL;
-          return true;
-        }
-        else
-        {
-          m_iLastEepromWrite = iNow;
-        }
-      }
-
-      if (!m_commands->WriteEEPROM())
-      {
-        // write failed, retry later
-        CLockObject lock(m_mutex);
-        m_iScheduleEepromWrite = GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY;
-      }
-    }
-  }
-  return IsOpen() ? m_commands->PersistConfiguration(configuration) : false;
+  return IsOpen() ?
+      m_commands->PersistConfiguration(configuration) && m_eepromWriteThread->Write() :
+      false;
 }
 
 bool CUSBCECAdapterCommunication::GetConfiguration(libcec_configuration &configuration)
@@ -696,11 +728,74 @@ void *CAdapterPingThread::Process(void)
         /* failed to ping the adapter 3 times in a row. something must be wrong with the connection */
         m_com->LIB_CEC->AddLog(CEC_LOG_ERROR, "failed to ping the adapter 3 times in a row. closing the connection.");
         m_com->StopThread(false);
+
+        libcec_parameter param;
+        param.paramData = NULL; param.paramType = CEC_PARAMETER_TYPE_UNKOWN;
+        m_com->LIB_CEC->Alert(CEC_ALERT_CONNECTION_LOST, param);
+
         break;
       }
     }
 
-    Sleep(500);
+    Sleep(5);
+  }
+  return NULL;
+}
+
+void CAdapterEepromWriteThread::Stop(void)
+{
+  StopThread(-1);
+  {
+    CLockObject lock(m_mutex);
+    if (m_iScheduleEepromWrite > 0)
+      m_com->LIB_CEC->AddLog(CEC_LOG_WARNING, "write thread stopped while a write was queued");
+    m_bWrite = true;
+    m_condition.Signal();
+  }
+  StopThread();
+}
+
+void *CAdapterEepromWriteThread::Process(void)
+{
+  while (!IsStopped())
+  {
+    CLockObject lock(m_mutex);
+    if ((m_iScheduleEepromWrite > 0 && m_iScheduleEepromWrite < GetTimeMs()) ||
+        m_condition.Wait(m_mutex, m_bWrite, 100))
+    {
+      if (IsStopped())
+        break;
+      m_bWrite = false;
+      if (m_com->m_commands->WriteEEPROM())
+      {
+        m_iLastEepromWrite = GetTimeMs();
+        m_iScheduleEepromWrite = 0;
+      }
+      else
+      {
+        m_iScheduleEepromWrite = GetTimeMs() + CEC_ADAPTER_EEPROM_WRITE_RETRY;
+      }
+    }
   }
   return NULL;
 }
+
+bool CAdapterEepromWriteThread::Write(void)
+{
+  CLockObject lock(m_mutex);
+  if (m_iScheduleEepromWrite == 0)
+  {
+    int64_t iNow = GetTimeMs();
+    if (m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL > iNow)
+    {
+      m_com->LIB_CEC->AddLog(CEC_LOG_DEBUG, "delaying eeprom write by %ld ms", m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL - iNow);
+      m_iScheduleEepromWrite = m_iLastEepromWrite + CEC_ADAPTER_EEPROM_WRITE_INTERVAL;
+    }
+    else
+    {
+      m_bWrite = true;
+      m_condition.Signal();
+    }
+  }
+  return true;
+}